[go: nahoru, domu]

blob: 043265c0469ea5e7f43b9911b10f2d4437d59baf [file] [log] [blame]
Avi Drissmand387f0922022-09-14 20:51:311// Copyright 2021 The Chromium Authors
Will Cassella42480ee2022-01-07 21:56:112// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "media/formats/hls/tags.h"
6
7#include <cstddef>
Will Cassellaf28a25262022-01-26 20:24:318#include <type_traits>
9#include <utility>
Will Cassella9af5f782022-09-08 21:10:3610
Will Cassellaf28a25262022-01-26 20:24:3111#include "base/notreached.h"
Will Cassella23cdc9a12022-06-15 23:03:2612#include "base/time/time.h"
Will Cassella9af5f782022-09-08 21:10:3613#include "media/base/mime_util.h"
Will Cassellad9d31172022-03-04 20:52:2514#include "media/formats/hls/items.h"
Will Cassella42480ee2022-01-07 21:56:1115#include "media/formats/hls/parse_status.h"
Will Cassella58c7fc632022-04-06 18:48:3916#include "media/formats/hls/variable_dictionary.h"
Will Cassella42480ee2022-01-07 21:56:1117
Will Cassellad9d31172022-03-04 20:52:2518namespace media::hls {
Will Cassella42480ee2022-01-07 21:56:1119
Will Cassella058de252022-01-22 01:38:2020namespace {
21
22template <typename T>
23ParseStatus::Or<T> ParseEmptyTag(TagItem tag) {
Will Cassellaee56fa572022-04-12 19:17:2824 DCHECK(tag.GetName() == ToTagName(T::kName));
25 if (tag.GetContent().has_value()) {
Will Cassella42480ee2022-01-07 21:56:1126 return ParseStatusCode::kMalformedTag;
27 }
28
Will Cassella058de252022-01-22 01:38:2029 return T{};
30}
31
Will Cassellad7bc2c02022-05-11 00:52:0232template <typename T>
33ParseStatus::Or<T> ParseDecimalIntegerTag(TagItem tag,
34 types::DecimalInteger T::*field) {
35 DCHECK(tag.GetName() == ToTagName(T::kName));
36 if (!tag.GetContent().has_value()) {
37 return ParseStatusCode::kMalformedTag;
38 }
39
Will Cassella0d3ed6a2022-06-27 19:39:2940 auto value =
41 types::ParseDecimalInteger(tag.GetContent()->SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:2542 if (!value.has_value()) {
Will Cassellad7bc2c02022-05-11 00:52:0243 return ParseStatus(ParseStatusCode::kMalformedTag)
44 .AddCause(std::move(value).error());
45 }
46
47 T out;
48 out.*field = std::move(value).value();
49 return out;
50}
51
Ted Meyere55bc2b2024-02-24 00:31:2552template <typename T>
53ParseStatus::Or<T> ParseISO8601DateTimeTag(TagItem tag, base::Time T::*field) {
54 CHECK(tag.GetName() == ToTagName(T::kName));
55 if (!tag.GetContent().has_value()) {
56 return ParseStatusCode::kMalformedTag;
57 }
58 const auto content = tag.GetContent()->SkipVariableSubstitution().Str();
59 std::string content_nullterm = std::string(content);
60 T out;
61 base::Time time;
62 if (base::Time::FromString(content_nullterm.c_str(), &time)) {
63 out.*field = time;
64 } else {
65 return ParseStatusCode::kMalformedTag;
66 }
67 return out;
68}
69
Will Cassellaf28a25262022-01-26 20:24:3170// Attributes expected in `EXT-X-DEFINE` tag contents.
71// These must remain sorted alphabetically.
72enum class XDefineTagAttribute {
73 kImport,
74 kName,
75 kValue,
76 kMaxValue = kValue,
77};
78
Ted Meyerda164052024-03-04 05:46:2479constexpr std::string_view GetAttributeName(XDefineTagAttribute attribute) {
Will Cassellaf28a25262022-01-26 20:24:3180 switch (attribute) {
81 case XDefineTagAttribute::kImport:
82 return "IMPORT";
83 case XDefineTagAttribute::kName:
84 return "NAME";
85 case XDefineTagAttribute::kValue:
86 return "VALUE";
87 }
88
Peter Boström0ce6af602023-05-15 21:27:2489 NOTREACHED_NORETURN();
Will Cassellaf28a25262022-01-26 20:24:3190}
91
Will Cassella2f4c3202022-08-11 00:20:1992// Attributes expected in `EXT-X-MEDIA` tag contents.
93// These must remain sorted alphabetically.
94enum class XMediaTagAttribute {
95 kAssocLanguage,
96 kAutoselect,
97 kChannels,
98 kCharacteristics,
99 kDefault,
100 kForced,
101 kGroupId,
102 kInstreamId,
103 kLanguage,
104 kName,
105 kStableRenditionId,
106 kType,
107 kUri,
108 kMaxValue = kUri,
109};
110
Ted Meyerda164052024-03-04 05:46:24111constexpr std::string_view GetAttributeName(XMediaTagAttribute attribute) {
Will Cassella2f4c3202022-08-11 00:20:19112 switch (attribute) {
113 case XMediaTagAttribute::kAssocLanguage:
114 return "ASSOC-LANGUAGE";
115 case XMediaTagAttribute::kAutoselect:
116 return "AUTOSELECT";
117 case XMediaTagAttribute::kChannels:
118 return "CHANNELS";
119 case XMediaTagAttribute::kCharacteristics:
120 return "CHARACTERISTICS";
121 case XMediaTagAttribute::kDefault:
122 return "DEFAULT";
123 case XMediaTagAttribute::kForced:
124 return "FORCED";
125 case XMediaTagAttribute::kGroupId:
126 return "GROUP-ID";
127 case XMediaTagAttribute::kInstreamId:
128 return "INSTREAM-ID";
129 case XMediaTagAttribute::kLanguage:
130 return "LANGUAGE";
131 case XMediaTagAttribute::kName:
132 return "NAME";
133 case XMediaTagAttribute::kStableRenditionId:
134 return "STABLE-RENDITION-ID";
135 case XMediaTagAttribute::kType:
136 return "TYPE";
137 case XMediaTagAttribute::kUri:
138 return "URI";
139 }
140
Peter Boström0ce6af602023-05-15 21:27:24141 NOTREACHED_NORETURN();
Will Cassella2f4c3202022-08-11 00:20:19142}
143
Will Cassella58c7fc632022-04-06 18:48:39144// Attributes expected in `EXT-X-STREAM-INF` tag contents.
145// These must remain sorted alphabetically.
146enum class XStreamInfTagAttribute {
Will Cassellaad9c3402022-09-08 22:52:53147 kAudio,
Will Cassella58c7fc632022-04-06 18:48:39148 kAverageBandwidth,
149 kBandwidth,
150 kCodecs,
Will Cassellacf1c3912022-04-28 20:55:58151 kFrameRate,
Will Cassella58c7fc632022-04-06 18:48:39152 kProgramId, // Ignored for backwards compatibility
Will Cassellacf1c3912022-04-28 20:55:58153 kResolution,
Will Cassella58c7fc632022-04-06 18:48:39154 kScore,
Ted Meyer274348b2023-02-03 00:34:31155 kVideo,
156 kMaxValue = kVideo,
Will Cassella58c7fc632022-04-06 18:48:39157};
158
Ted Meyerda164052024-03-04 05:46:24159constexpr std::string_view GetAttributeName(XStreamInfTagAttribute attribute) {
Will Cassella58c7fc632022-04-06 18:48:39160 switch (attribute) {
Will Cassellaad9c3402022-09-08 22:52:53161 case XStreamInfTagAttribute::kAudio:
162 return "AUDIO";
Ted Meyer274348b2023-02-03 00:34:31163 case XStreamInfTagAttribute::kVideo:
164 return "VIDEO";
Will Cassella58c7fc632022-04-06 18:48:39165 case XStreamInfTagAttribute::kAverageBandwidth:
166 return "AVERAGE-BANDWIDTH";
167 case XStreamInfTagAttribute::kBandwidth:
168 return "BANDWIDTH";
169 case XStreamInfTagAttribute::kCodecs:
170 return "CODECS";
Will Cassellacf1c3912022-04-28 20:55:58171 case XStreamInfTagAttribute::kFrameRate:
172 return "FRAME-RATE";
Will Cassella58c7fc632022-04-06 18:48:39173 case XStreamInfTagAttribute::kProgramId:
174 return "PROGRAM-ID";
Will Cassellacf1c3912022-04-28 20:55:58175 case XStreamInfTagAttribute::kResolution:
176 return "RESOLUTION";
Will Cassella58c7fc632022-04-06 18:48:39177 case XStreamInfTagAttribute::kScore:
178 return "SCORE";
179 }
180
Peter Boström0ce6af602023-05-15 21:27:24181 NOTREACHED_NORETURN();
Will Cassella58c7fc632022-04-06 18:48:39182}
183
Ted Meyerf7566d32023-12-06 02:31:12184// Attributes expected in `EXT-X-SKIP` tag contents.
185// These must remain sorted alphabetically.
186enum class XSkipTagAttribute {
187 kRecentlyRemovedDateranges,
188 kSkippedSegments,
189 kMaxValue = kSkippedSegments,
190};
191
192constexpr std::string_view GetAttributeName(XSkipTagAttribute attribute) {
193 switch (attribute) {
194 case XSkipTagAttribute::kRecentlyRemovedDateranges:
195 return "RECENTLY-REMOVED-DATERANGES";
196 case XSkipTagAttribute::kSkippedSegments:
197 return "SKIPPED-SEGMENTS";
198 }
199 NOTREACHED_NORETURN();
200}
201
Ted Meyere55bc2b2024-02-24 00:31:25202enum class XRenditionReportTagAttribute {
203 kLastMSN,
204 kLastPart,
205 kUri,
206 kMaxValue = kUri,
207};
208
209constexpr std::string_view GetAttributeName(XRenditionReportTagAttribute attr) {
210 switch (attr) {
211 case XRenditionReportTagAttribute::kUri:
212 return "URI";
213 case XRenditionReportTagAttribute::kLastMSN:
214 return "LAST-MSN";
215 case XRenditionReportTagAttribute::kLastPart:
216 return "LAST-PART";
217 }
218 NOTREACHED_NORETURN();
219}
220
Will Cassellafc185a692022-07-21 00:29:51221// Attributes expected in `EXT-X-MAP` tag contents.
222// These must remain sorted alphabetically.
223enum class XMapTagAttribute {
224 kByteRange,
225 kUri,
226 kMaxValue = kUri,
227};
228
Ted Meyerda164052024-03-04 05:46:24229constexpr std::string_view GetAttributeName(XMapTagAttribute attribute) {
Will Cassellafc185a692022-07-21 00:29:51230 switch (attribute) {
231 case XMapTagAttribute::kByteRange:
232 return "BYTERANGE";
233 case XMapTagAttribute::kUri:
234 return "URI";
235 }
236
Peter Boström0ce6af602023-05-15 21:27:24237 NOTREACHED_NORETURN();
Will Cassellafc185a692022-07-21 00:29:51238}
239
Will Cassella53a6da12022-07-14 00:53:36240// Attributes expected in `EXT-X-PART` tag contents.
241// These must remain sorted alphabetically.
242enum class XPartTagAttribute {
243 kByteRange,
244 kDuration,
245 kGap,
246 kIndependent,
247 kUri,
248 kMaxValue = kUri,
249};
250
Ted Meyerda164052024-03-04 05:46:24251constexpr std::string_view GetAttributeName(XPartTagAttribute attribute) {
Will Cassella53a6da12022-07-14 00:53:36252 switch (attribute) {
253 case XPartTagAttribute::kByteRange:
254 return "BYTERANGE";
255 case XPartTagAttribute::kDuration:
256 return "DURATION";
257 case XPartTagAttribute::kGap:
258 return "GAP";
259 case XPartTagAttribute::kIndependent:
260 return "INDEPENDENT";
261 case XPartTagAttribute::kUri:
262 return "URI";
263 }
264}
265
Will Cassella0412e58a2022-07-20 21:35:38266// Attributes expected in `EXT-X-PART-INF` tag contents.
267// These must remain sorted alphabetically.
268enum class XPartInfTagAttribute {
269 kPartTarget,
270 kMaxValue = kPartTarget,
271};
272
Ted Meyerda164052024-03-04 05:46:24273constexpr std::string_view GetAttributeName(XPartInfTagAttribute attribute) {
Will Cassella0412e58a2022-07-20 21:35:38274 switch (attribute) {
275 case XPartInfTagAttribute::kPartTarget:
276 return "PART-TARGET";
277 }
278
Peter Boström0ce6af602023-05-15 21:27:24279 NOTREACHED_NORETURN();
Will Cassella0412e58a2022-07-20 21:35:38280}
281
Will Cassellafc185a692022-07-21 00:29:51282// Attributes expected in `EXT-X-SERVER-CONTROL` tag contents.
Will Cassellacfa1b1b42022-06-17 20:50:52283// These must remain sorted alphabetically.
284enum class XServerControlTagAttribute {
285 kCanBlockReload,
286 kCanSkipDateRanges,
287 kCanSkipUntil,
288 kHoldBack,
289 kPartHoldBack,
290 kMaxValue = kPartHoldBack,
291};
292
Ted Meyerda164052024-03-04 05:46:24293constexpr std::string_view GetAttributeName(
Will Cassellacfa1b1b42022-06-17 20:50:52294 XServerControlTagAttribute attribute) {
295 switch (attribute) {
296 case XServerControlTagAttribute::kCanBlockReload:
297 return "CAN-BLOCK-RELOAD";
298 case XServerControlTagAttribute::kCanSkipDateRanges:
299 return "CAN-SKIP-DATERANGES";
300 case XServerControlTagAttribute::kCanSkipUntil:
301 return "CAN-SKIP-UNTIL";
302 case XServerControlTagAttribute::kHoldBack:
303 return "HOLD-BACK";
304 case XServerControlTagAttribute::kPartHoldBack:
305 return "PART-HOLD-BACK";
306 }
307
Peter Boström0ce6af602023-05-15 21:27:24308 NOTREACHED_NORETURN();
Will Cassellacfa1b1b42022-06-17 20:50:52309}
310
Will Cassellaf28a25262022-01-26 20:24:31311template <typename T, size_t kLast>
312constexpr bool IsAttributeEnumSorted(std::index_sequence<kLast>) {
313 return true;
314}
315
316template <typename T, size_t kLHS, size_t kRHS, size_t... kRest>
317constexpr bool IsAttributeEnumSorted(
318 std::index_sequence<kLHS, kRHS, kRest...>) {
319 const auto lhs = GetAttributeName(static_cast<T>(kLHS));
320 const auto rhs = GetAttributeName(static_cast<T>(kRHS));
321 return lhs < rhs &&
322 IsAttributeEnumSorted<T>(std::index_sequence<kRHS, kRest...>{});
323}
324
325// Wraps `AttributeMap::MakeStorage` by mapping the (compile-time) sequence
326// of size_t's to a sequence of the corresponding attribute enum names.
327template <typename T, std::size_t... Indices>
328constexpr std::array<types::AttributeMap::Item, sizeof...(Indices)>
329MakeTypedAttributeMapStorage(std::index_sequence<Indices...> seq) {
330 static_assert(IsAttributeEnumSorted<T>(seq),
331 "Enum keys must be sorted alphabetically");
332 return types::AttributeMap::MakeStorage(
333 GetAttributeName(static_cast<T>(Indices))...);
334}
335
336// Helper for using AttributeMap with an enum of keys.
337// The result of running `GetAttributeName` across `0..T::kMaxValue` (inclusive)
338// must produced an ordered set of strings.
339template <typename T>
340struct TypedAttributeMap {
341 static_assert(std::is_enum<T>::value, "T must be an enum");
342 static_assert(std::is_same<decltype(GetAttributeName(std::declval<T>())),
Ted Meyerda164052024-03-04 05:46:24343 std::string_view>::value,
Will Cassellaf28a25262022-01-26 20:24:31344 "GetAttributeName must be overloaded for T to return a "
Ted Meyerda164052024-03-04 05:46:24345 "std::string_view");
Will Cassellaf28a25262022-01-26 20:24:31346 static constexpr size_t kNumKeys = static_cast<size_t>(T::kMaxValue) + 1;
347
348 TypedAttributeMap()
349 : attributes_(MakeTypedAttributeMapStorage<T>(
350 std::make_index_sequence<kNumKeys>())) {}
351
352 // Wraps `AttributeMap::FillUntilError` using the built-in storage object.
353 ParseStatus FillUntilError(types::AttributeListIterator* iter) {
354 types::AttributeMap map(attributes_);
355 return map.FillUntilError(iter);
356 }
357
358 // Returns whether the entry corresponding to the given key has a value.
359 bool HasValue(T key) const {
360 return attributes_[static_cast<size_t>(key)].second.has_value();
361 }
362
363 // Returns the value stored in the entry for the given key.
364 SourceString GetValue(T key) const {
365 return attributes_[static_cast<size_t>(key)].second.value();
366 }
367
368 private:
369 std::array<types::AttributeMap::Item, kNumKeys> attributes_;
370};
371
Ted Meyer38a01192024-06-06 22:53:44372#define RETURN_IF_ERROR(var_not_expr) \
373 do { \
374 if (!var_not_expr.has_value()) { \
375 return std::move(var_not_expr).error().AddHere(); \
376 } \
377 } while (0)
378
379template <template <typename...> typename SpecifiedContainer, typename Type>
380struct is_specialization_of : std::false_type {};
381
382template <template <typename...> typename SpecifiedContainer, typename... Types>
383struct is_specialization_of<SpecifiedContainer, SpecifiedContainer<Types...>>
384 : std::true_type {};
385
386// ParseField exists to help with the pattern where we need to parse some field
387// from a dictionary of tag attributes where some of those fields are optional.
388// It is designed to provide support for either optional or required fields,
389// depending on the return-type specialization:
390// When Result=std::optional<X>, ParseField will return a nullopt rather than
391// an error if the `field_name` field can't be found in `map`.
392// When Result=T (where T is not std::optional<X>), ParseField will fail with
393// a ParseStatus if `field_name` is not found in map.
394// A function pointer must also be provided which does the parsing from
395// SourceString => Result, provided that the `field_name` key is present.
396template <typename Result,
397 typename AttrEnum,
398 typename ParseFn,
399 typename... ParseFnArgs>
400ParseStatus::Or<Result> ParseField(AttrEnum field_name,
401 TypedAttributeMap<AttrEnum> map,
402 ParseFn parser,
403 ParseFnArgs&&... args) {
404 if (map.HasValue(field_name)) {
405 auto maybe =
406 parser(map.GetValue(field_name), std::forward<ParseFnArgs>(args)...);
407 if (!maybe.has_value()) {
408 ParseStatus result = ParseStatusCode::kMalformedTag;
409 return std::move(result).AddCause(std::move(maybe).error());
410 }
411 return Result(std::move(maybe).value());
412 }
413 if constexpr (is_specialization_of<std::optional, Result>::value) {
414 return Result(std::nullopt);
415 }
416 return ParseStatusCode::kMalformedTag;
417}
418
Will Cassella058de252022-01-22 01:38:20419} // namespace
420
Will Cassella5343f8f2022-07-20 00:11:33421// static
Will Cassella058de252022-01-22 01:38:20422ParseStatus::Or<M3uTag> M3uTag::Parse(TagItem tag) {
423 return ParseEmptyTag<M3uTag>(tag);
Will Cassella42480ee2022-01-07 21:56:11424}
425
Will Cassella5343f8f2022-07-20 00:11:33426// static
Will Cassellaf28a25262022-01-26 20:24:31427XDefineTag XDefineTag::CreateDefinition(types::VariableName name,
Ted Meyerda164052024-03-04 05:46:24428 std::string_view value) {
Will Cassellaf28a25262022-01-26 20:24:31429 return XDefineTag{.name = name, .value = value};
430}
431
Will Cassella5343f8f2022-07-20 00:11:33432// static
Will Cassellaf28a25262022-01-26 20:24:31433XDefineTag XDefineTag::CreateImport(types::VariableName name) {
Arthur Sonzogni581b9e82024-01-30 17:25:15434 return XDefineTag{.name = name, .value = std::nullopt};
Will Cassellaf28a25262022-01-26 20:24:31435}
436
Will Cassella5343f8f2022-07-20 00:11:33437// static
Will Cassellaf28a25262022-01-26 20:24:31438ParseStatus::Or<XDefineTag> XDefineTag::Parse(TagItem tag) {
Will Cassellaee56fa572022-04-12 19:17:28439 DCHECK(tag.GetName() == ToTagName(XDefineTag::kName));
440
441 if (!tag.GetContent().has_value()) {
442 return ParseStatusCode::kMalformedTag;
443 }
Will Cassella85694d62022-04-02 01:08:01444
445 // Parse the attribute-list.
446 // Quoted strings in EXT-X-DEFINE tags are unique in that they aren't subject
447 // to variable substitution. For that reason, we use the
448 // `ParseQuotedStringWithoutSubstitution` function here.
Will Cassellaf28a25262022-01-26 20:24:31449 TypedAttributeMap<XDefineTagAttribute> map;
Will Cassellaee56fa572022-04-12 19:17:28450 types::AttributeListIterator iter(*tag.GetContent());
Will Cassellaf28a25262022-01-26 20:24:31451 auto result = map.FillUntilError(&iter);
452
453 if (result.code() != ParseStatusCode::kReachedEOF) {
454 return ParseStatus(ParseStatusCode::kMalformedTag)
455 .AddCause(std::move(result));
456 }
457
458 // "NAME" and "IMPORT" are mutually exclusive
459 if (map.HasValue(XDefineTagAttribute::kName) &&
460 map.HasValue(XDefineTagAttribute::kImport)) {
461 return ParseStatusCode::kMalformedTag;
462 }
463
464 if (map.HasValue(XDefineTagAttribute::kName)) {
Will Cassella85694d62022-04-02 01:08:01465 auto var_name = types::ParseQuotedStringWithoutSubstitution(
466 map.GetValue(XDefineTagAttribute::kName))
467 .MapValue(types::VariableName::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25468 if (!var_name.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31469 return ParseStatus(ParseStatusCode::kMalformedTag)
470 .AddCause(std::move(var_name).error());
471 }
472
473 // If "NAME" is defined, "VALUE" must also be defined
474 if (!map.HasValue(XDefineTagAttribute::kValue)) {
475 return ParseStatusCode::kMalformedTag;
476 }
477
Will Cassella85694d62022-04-02 01:08:01478 auto value = types::ParseQuotedStringWithoutSubstitution(
Will Cassella335a0142022-07-01 18:20:52479 map.GetValue(XDefineTagAttribute::kValue), /*allow_empty*/ true);
Ted Meyer0eb6fcc2022-10-21 03:21:25480 if (!value.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31481 return ParseStatus(ParseStatusCode::kMalformedTag);
482 }
483
484 return XDefineTag::CreateDefinition(std::move(var_name).value(),
485 std::move(value).value().Str());
486 }
487
488 if (map.HasValue(XDefineTagAttribute::kImport)) {
Will Cassella85694d62022-04-02 01:08:01489 auto var_name = types::ParseQuotedStringWithoutSubstitution(
490 map.GetValue(XDefineTagAttribute::kImport))
491 .MapValue(types::VariableName::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25492 if (!var_name.has_value()) {
Will Cassellaf28a25262022-01-26 20:24:31493 return ParseStatus(ParseStatusCode::kMalformedTag)
494 .AddCause(std::move(var_name).error());
495 }
496
497 // "VALUE" doesn't make any sense here, but the spec doesn't explicitly
498 // forbid it. It may be used in the future to provide a default value for
499 // undefined imported variables, so we won't error on it.
500 return XDefineTag::CreateImport(std::move(var_name).value());
501 }
502
503 // Without "NAME" or "IMPORT", the tag is malformed
504 return ParseStatusCode::kMalformedTag;
505}
506
Will Cassella5343f8f2022-07-20 00:11:33507// static
Will Cassella0412e58a2022-07-20 21:35:38508ParseStatus::Or<XIndependentSegmentsTag> XIndependentSegmentsTag::Parse(
509 TagItem tag) {
510 return ParseEmptyTag<XIndependentSegmentsTag>(tag);
511}
Will Cassella04297882022-04-06 01:39:37512
Will Cassella0412e58a2022-07-20 21:35:38513// static
514ParseStatus::Or<XVersionTag> XVersionTag::Parse(TagItem tag) {
515 auto result = ParseDecimalIntegerTag(tag, &XVersionTag::version);
Ted Meyer0eb6fcc2022-10-21 03:21:25516 if (!result.has_value()) {
Will Cassella0412e58a2022-07-20 21:35:38517 return std::move(result).error();
Will Cassella04297882022-04-06 01:39:37518 }
519
Will Cassella0412e58a2022-07-20 21:35:38520 // Reject invalid version numbers.
521 // For valid version numbers, caller will decide if the version is supported.
522 auto out = std::move(result).value();
523 if (out.version == 0) {
524 return ParseStatusCode::kInvalidPlaylistVersion;
Will Cassella04297882022-04-06 01:39:37525 }
526
Will Cassella0412e58a2022-07-20 21:35:38527 return out;
Will Cassella04297882022-04-06 01:39:37528}
529
Will Cassella2f4c3202022-08-11 00:20:19530struct XMediaTag::CtorArgs {
531 decltype(XMediaTag::type) type;
532 decltype(XMediaTag::uri) uri;
533 decltype(XMediaTag::instream_id) instream_id;
534 decltype(XMediaTag::group_id) group_id;
535 decltype(XMediaTag::language) language;
536 decltype(XMediaTag::associated_language) associated_language;
537 decltype(XMediaTag::name) name;
538 decltype(XMediaTag::stable_rendition_id) stable_rendition_id;
539 decltype(XMediaTag::is_default) is_default;
540 decltype(XMediaTag::autoselect) autoselect;
541 decltype(XMediaTag::forced) forced;
542 decltype(XMediaTag::characteristics) characteristics;
543 decltype(XMediaTag::channels) channels;
544};
545
546XMediaTag::XMediaTag(CtorArgs args)
547 : type(std::move(args.type)),
548 uri(std::move(args.uri)),
549 instream_id(std::move(args.instream_id)),
550 group_id(std::move(args.group_id)),
551 language(std::move(args.language)),
552 associated_language(std::move(args.associated_language)),
553 name(std::move(args.name)),
554 stable_rendition_id(std::move(args.stable_rendition_id)),
555 is_default(std::move(args.is_default)),
556 autoselect(std::move(args.autoselect)),
557 forced(std::move(args.forced)),
558 characteristics(std::move(args.characteristics)),
559 channels(std::move(args.channels)) {}
560
561XMediaTag::~XMediaTag() = default;
562
563XMediaTag::XMediaTag(const XMediaTag&) = default;
564
565XMediaTag::XMediaTag(XMediaTag&&) = default;
566
567XMediaTag& XMediaTag::operator=(const XMediaTag&) = default;
568
569XMediaTag& XMediaTag::operator=(XMediaTag&&) = default;
570
571// static
572ParseStatus::Or<XMediaTag> XMediaTag::Parse(
573 TagItem tag,
574 const VariableDictionary& variable_dict,
575 VariableDictionary::SubstitutionBuffer& sub_buffer) {
576 DCHECK(tag.GetName() == ToTagName(XMediaTag::kName));
577 if (!tag.GetContent().has_value()) {
578 return ParseStatusCode::kMalformedTag;
579 }
580
581 // Parse the attribute-list
582 TypedAttributeMap<XMediaTagAttribute> map;
583 types::AttributeListIterator iter(*tag.GetContent());
584 auto map_result = map.FillUntilError(&iter);
585
586 if (map_result.code() != ParseStatusCode::kReachedEOF) {
587 return ParseStatus(ParseStatusCode::kMalformedTag)
588 .AddCause(std::move(map_result));
589 }
590
591 // Parse the 'TYPE' attribute
592 MediaType type;
593 if (map.HasValue(XMediaTagAttribute::kType)) {
594 auto str = map.GetValue(XMediaTagAttribute::kType);
595 if (str.Str() == "AUDIO") {
596 type = MediaType::kAudio;
597 } else if (str.Str() == "VIDEO") {
598 type = MediaType::kVideo;
599 } else if (str.Str() == "SUBTITLES") {
600 type = MediaType::kSubtitles;
601 } else if (str.Str() == "CLOSED-CAPTIONS") {
602 type = MediaType::kClosedCaptions;
603 } else {
604 return ParseStatusCode::kMalformedTag;
605 }
606 } else {
607 return ParseStatusCode::kMalformedTag;
608 }
609
610 // Parse the 'URI' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15611 std::optional<ResolvedSourceString> uri;
Will Cassella2f4c3202022-08-11 00:20:19612 if (map.HasValue(XMediaTagAttribute::kUri)) {
613 // This attribute MUST NOT be defined for closed-captions renditions
614 if (type == MediaType::kClosedCaptions) {
615 return ParseStatusCode::kMalformedTag;
616 }
617
618 auto result = types::ParseQuotedString(
619 map.GetValue(XMediaTagAttribute::kUri), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25620 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19621 return ParseStatus(ParseStatusCode::kMalformedTag)
622 .AddCause(std::move(result).error());
623 }
624
625 uri = std::move(result).value();
626 } else {
627 // URI MUST be defined for subtitle renditions
628 if (type == MediaType::kSubtitles) {
629 return ParseStatusCode::kMalformedTag;
630 }
631 }
632
633 // Parse the 'GROUP-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15634 std::optional<ResolvedSourceString> group_id;
Will Cassella2f4c3202022-08-11 00:20:19635 if (map.HasValue(XMediaTagAttribute::kGroupId)) {
636 auto result = types::ParseQuotedString(
637 map.GetValue(XMediaTagAttribute::kGroupId), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25638 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19639 return ParseStatus(ParseStatusCode::kMalformedTag)
640 .AddCause(std::move(result).error());
641 }
642
643 group_id = std::move(result).value();
644 } else {
645 return ParseStatusCode::kMalformedTag;
646 }
647
648 // Parse the 'LANGUAGE' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15649 std::optional<ResolvedSourceString> language;
Will Cassella2f4c3202022-08-11 00:20:19650 if (map.HasValue(XMediaTagAttribute::kLanguage)) {
651 auto result = types::ParseQuotedString(
652 map.GetValue(XMediaTagAttribute::kLanguage), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25653 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19654 return ParseStatus(ParseStatusCode::kMalformedTag)
655 .AddCause(std::move(result).error());
656 }
657
658 language = std::move(result).value();
659 }
660
661 // Parse the 'ASSOC-LANGUAGE' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15662 std::optional<ResolvedSourceString> assoc_language;
Will Cassella2f4c3202022-08-11 00:20:19663 if (map.HasValue(XMediaTagAttribute::kAssocLanguage)) {
664 auto result = types::ParseQuotedString(
665 map.GetValue(XMediaTagAttribute::kAssocLanguage), variable_dict,
666 sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25667 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19668 return ParseStatus(ParseStatusCode::kMalformedTag)
669 .AddCause(std::move(result).error());
670 }
671
672 assoc_language = std::move(result).value();
673 }
674
675 // Parse the 'NAME' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15676 std::optional<ResolvedSourceString> name;
Will Cassella2f4c3202022-08-11 00:20:19677 if (map.HasValue(XMediaTagAttribute::kName)) {
678 auto result = types::ParseQuotedString(
679 map.GetValue(XMediaTagAttribute::kName), variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25680 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19681 return ParseStatus(ParseStatusCode::kMalformedTag)
682 .AddCause(std::move(result).error());
683 }
684
685 name = std::move(result).value();
686 } else {
687 return ParseStatusCode::kMalformedTag;
688 }
689
690 // Parse the 'STABLE-RENDITION-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15691 std::optional<types::StableId> stable_rendition_id;
Will Cassella2f4c3202022-08-11 00:20:19692 if (map.HasValue(XMediaTagAttribute::kStableRenditionId)) {
693 auto result = types::ParseQuotedString(
694 map.GetValue(XMediaTagAttribute::kStableRenditionId),
695 variable_dict, sub_buffer)
696 .MapValue(types::StableId::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25697 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19698 return ParseStatus(ParseStatusCode::kMalformedTag)
699 .AddCause(std::move(result).error());
700 }
701 stable_rendition_id = std::move(result).value();
702 }
703
704 // Parse the 'DEFAULT' attribute
705 bool is_default = false;
706 if (map.HasValue(XMediaTagAttribute::kDefault)) {
707 if (map.GetValue(XMediaTagAttribute::kDefault).Str() == "YES") {
708 is_default = true;
709 }
710 }
711
712 // Parse the 'AUTOSELECT' attribute
Will Cassellaf1cd38372022-09-08 20:19:55713 bool autoselect = is_default;
Will Cassella2f4c3202022-08-11 00:20:19714 if (map.HasValue(XMediaTagAttribute::kAutoselect)) {
715 if (map.GetValue(XMediaTagAttribute::kAutoselect).Str() == "YES") {
716 autoselect = true;
717 } else if (is_default) {
718 // If the 'DEFAULT' attribute is 'YES', then the value of this attribute
719 // must also be 'YES', if present.
720 return ParseStatusCode::kMalformedTag;
721 }
722 }
723
724 // Parse the 'FORCED' attribute
725 bool forced = false;
726 if (map.HasValue(XMediaTagAttribute::kForced)) {
727 // The FORCED attribute MUST NOT be present unless TYPE=SUBTITLES
728 if (type != MediaType::kSubtitles) {
729 return ParseStatusCode::kMalformedTag;
730 }
731
732 if (map.GetValue(XMediaTagAttribute::kForced).Str() == "YES") {
733 forced = true;
734 }
735 }
736
737 // Parse the 'INSTREAM-ID' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15738 std::optional<types::InstreamId> instream_id;
Will Cassella2f4c3202022-08-11 00:20:19739 if (map.HasValue(XMediaTagAttribute::kInstreamId)) {
740 // The INSTREAM-ID attribute MUST NOT be present unless TYPE=CLOSED-CAPTIONS
741 if (type != MediaType::kClosedCaptions) {
742 return ParseStatusCode::kMalformedTag;
743 }
744
745 auto result =
746 types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kInstreamId),
747 variable_dict, sub_buffer)
748 .MapValue(types::InstreamId::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25749 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19750 return ParseStatus(ParseStatusCode::kMalformedTag)
751 .AddCause(std::move(result).error());
752 }
753
754 instream_id = std::move(result).value();
755 }
756 // This attribute is REQUIRED if TYPE=CLOSED-CAPTIONS
757 else if (type == MediaType::kClosedCaptions) {
758 return ParseStatusCode::kMalformedTag;
759 }
760
761 // Parse the 'CHARACTERISTICS' attribute
762 std::vector<std::string> characteristics;
763 if (map.HasValue(XMediaTagAttribute::kCharacteristics)) {
764 auto result = types::ParseQuotedString(
765 map.GetValue(XMediaTagAttribute::kCharacteristics), variable_dict,
766 sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25767 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19768 return ParseStatus(ParseStatusCode::kMalformedTag)
769 .AddCause(std::move(result).error());
770 }
771 auto value = std::move(result).value();
772
773 while (!value.Empty()) {
774 const auto mct = value.ConsumeDelimiter(',');
775 if (mct.Empty()) {
776 return ParseStatusCode::kMalformedTag;
777 }
778
779 characteristics.emplace_back(mct.Str());
780 }
781 }
782
783 // Parse the 'CHANNELS' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:15784 std::optional<types::AudioChannels> channels;
Will Cassella2f4c3202022-08-11 00:20:19785 if (map.HasValue(XMediaTagAttribute::kChannels)) {
786 // Currently only supported type with channel information is `kAudio`.
787 if (type == MediaType::kAudio) {
788 auto result =
789 types::ParseQuotedString(map.GetValue(XMediaTagAttribute::kChannels),
790 variable_dict, sub_buffer)
791 .MapValue(types::AudioChannels::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:25792 if (!result.has_value()) {
Will Cassella2f4c3202022-08-11 00:20:19793 return ParseStatus(ParseStatusCode::kMalformedTag)
794 .AddCause(std::move(result).error());
795 }
796
797 channels = std::move(result).value();
798 }
799 }
800
801 return XMediaTag(XMediaTag::CtorArgs{
802 .type = type,
803 .uri = uri,
804 .instream_id = instream_id,
805 .group_id = group_id.value(),
806 .language = language,
807 .associated_language = assoc_language,
808 .name = name.value(),
809 .stable_rendition_id = std::move(stable_rendition_id),
810 .is_default = is_default,
811 .autoselect = autoselect,
812 .forced = forced,
813 .characteristics = std::move(characteristics),
814 .channels = std::move(channels),
815 });
816}
817
Will Cassella58c7fc632022-04-06 18:48:39818XStreamInfTag::XStreamInfTag() = default;
819
820XStreamInfTag::~XStreamInfTag() = default;
821
822XStreamInfTag::XStreamInfTag(const XStreamInfTag&) = default;
823
824XStreamInfTag::XStreamInfTag(XStreamInfTag&&) = default;
825
826XStreamInfTag& XStreamInfTag::operator=(const XStreamInfTag&) = default;
827
828XStreamInfTag& XStreamInfTag::operator=(XStreamInfTag&&) = default;
829
Will Cassella5343f8f2022-07-20 00:11:33830// static
Will Cassella58c7fc632022-04-06 18:48:39831ParseStatus::Or<XStreamInfTag> XStreamInfTag::Parse(
832 TagItem tag,
833 const VariableDictionary& variable_dict,
834 VariableDictionary::SubstitutionBuffer& sub_buffer) {
Will Cassellaee56fa572022-04-12 19:17:28835 DCHECK(tag.GetName() == ToTagName(XStreamInfTag::kName));
Will Cassella58c7fc632022-04-06 18:48:39836 XStreamInfTag out;
837
Will Cassellaee56fa572022-04-12 19:17:28838 if (!tag.GetContent().has_value()) {
839 return ParseStatusCode::kMalformedTag;
840 }
841
Will Cassella58c7fc632022-04-06 18:48:39842 // Parse the attribute-list
843 TypedAttributeMap<XStreamInfTagAttribute> map;
Will Cassellaee56fa572022-04-12 19:17:28844 types::AttributeListIterator iter(*tag.GetContent());
Will Cassella58c7fc632022-04-06 18:48:39845 auto map_result = map.FillUntilError(&iter);
846
847 if (map_result.code() != ParseStatusCode::kReachedEOF) {
848 return ParseStatus(ParseStatusCode::kMalformedTag)
849 .AddCause(std::move(map_result));
850 }
851
852 // Extract the 'BANDWIDTH' attribute
853 if (map.HasValue(XStreamInfTagAttribute::kBandwidth)) {
854 auto bandwidth = types::ParseDecimalInteger(
Will Cassella0d3ed6a2022-06-27 19:39:29855 map.GetValue(XStreamInfTagAttribute::kBandwidth)
856 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25857 if (!bandwidth.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39858 return ParseStatus(ParseStatusCode::kMalformedTag)
859 .AddCause(std::move(bandwidth).error());
860 }
861
862 out.bandwidth = std::move(bandwidth).value();
863 } else {
864 return ParseStatusCode::kMalformedTag;
865 }
866
867 // Extract the 'AVERAGE-BANDWIDTH' attribute
868 if (map.HasValue(XStreamInfTagAttribute::kAverageBandwidth)) {
869 auto average_bandwidth = types::ParseDecimalInteger(
Will Cassella0d3ed6a2022-06-27 19:39:29870 map.GetValue(XStreamInfTagAttribute::kAverageBandwidth)
871 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25872 if (!average_bandwidth.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39873 return ParseStatus(ParseStatusCode::kMalformedTag)
874 .AddCause(std::move(average_bandwidth).error());
875 }
876
877 out.average_bandwidth = std::move(average_bandwidth).value();
878 }
879
880 // Extract the 'SCORE' attribute
881 if (map.HasValue(XStreamInfTagAttribute::kScore)) {
882 auto score = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:29883 map.GetValue(XStreamInfTagAttribute::kScore)
884 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25885 if (!score.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39886 return ParseStatus(ParseStatusCode::kMalformedTag)
887 .AddCause(std::move(score).error());
888 }
889
890 out.score = std::move(score).value();
891 }
892
893 // Extract the 'CODECS' attribute
894 if (map.HasValue(XStreamInfTagAttribute::kCodecs)) {
Will Cassella9af5f782022-09-08 21:10:36895 auto codecs_string =
Will Cassella58c7fc632022-04-06 18:48:39896 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kCodecs),
897 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25898 if (!codecs_string.has_value()) {
Will Cassella58c7fc632022-04-06 18:48:39899 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella9af5f782022-09-08 21:10:36900 .AddCause(std::move(codecs_string).error());
Will Cassella58c7fc632022-04-06 18:48:39901 }
Will Cassella9af5f782022-09-08 21:10:36902
903 // Split the list of codecs
904 std::vector<std::string> codecs;
905 SplitCodecs(std::move(codecs_string).value().Str(), &codecs);
906 out.codecs = std::move(codecs);
Will Cassella58c7fc632022-04-06 18:48:39907 }
908
Will Cassellacf1c3912022-04-28 20:55:58909 // Extract the 'RESOLUTION' attribute
910 if (map.HasValue(XStreamInfTagAttribute::kResolution)) {
911 auto resolution = types::DecimalResolution::Parse(
Will Cassella0d3ed6a2022-06-27 19:39:29912 map.GetValue(XStreamInfTagAttribute::kResolution)
913 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25914 if (!resolution.has_value()) {
Will Cassellacf1c3912022-04-28 20:55:58915 return ParseStatus(ParseStatusCode::kMalformedTag)
916 .AddCause(std::move(resolution).error());
917 }
918 out.resolution = std::move(resolution).value();
919 }
920
921 // Extract the 'FRAME-RATE' attribute
922 if (map.HasValue(XStreamInfTagAttribute::kFrameRate)) {
923 auto frame_rate = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:29924 map.GetValue(XStreamInfTagAttribute::kFrameRate)
925 .SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25926 if (!frame_rate.has_value()) {
Will Cassellacf1c3912022-04-28 20:55:58927 return ParseStatus(ParseStatusCode::kMalformedTag)
928 .AddCause(std::move(frame_rate).error());
929 }
930 out.frame_rate = std::move(frame_rate).value();
931 }
932
Will Cassellaad9c3402022-09-08 22:52:53933 // Extract the 'AUDIO' attribute
934 if (map.HasValue(XStreamInfTagAttribute::kAudio)) {
935 auto audio =
936 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kAudio),
937 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:25938 if (!audio.has_value()) {
Will Cassellaad9c3402022-09-08 22:52:53939 return ParseStatus(ParseStatusCode::kMalformedTag)
940 .AddCause(std::move(audio).error());
941 }
942 out.audio = std::move(audio).value();
943 }
944
Ted Meyer274348b2023-02-03 00:34:31945 // Extract the 'VIDEO' attribute
946 if (map.HasValue(XStreamInfTagAttribute::kVideo)) {
947 auto video =
948 types::ParseQuotedString(map.GetValue(XStreamInfTagAttribute::kVideo),
949 variable_dict, sub_buffer);
950 if (!video.has_value()) {
951 return ParseStatus(ParseStatusCode::kMalformedTag)
952 .AddCause(std::move(video).error());
953 }
954 out.video = std::move(video).value();
955 }
956
Will Cassella58c7fc632022-04-06 18:48:39957 return out;
958}
959
Will Cassella5343f8f2022-07-20 00:11:33960// static
Will Cassella0412e58a2022-07-20 21:35:38961ParseStatus::Or<InfTag> InfTag::Parse(TagItem tag) {
962 DCHECK(tag.GetName() == ToTagName(InfTag::kName));
963
964 if (!tag.GetContent()) {
965 return ParseStatusCode::kMalformedTag;
966 }
967 auto content = *tag.GetContent();
968
969 // Inf tags have the form #EXTINF:<duration>,[<title>]
970 // Find the comma.
971 auto comma = content.Str().find_first_of(',');
Ted Meyerbc1dcd02023-07-25 18:56:26972 SourceString duration_str = content;
973 SourceString title_str = content;
Ted Meyerda164052024-03-04 05:46:24974 if (comma == std::string_view::npos) {
Ted Meyerbc1dcd02023-07-25 18:56:26975 // While the HLS spec does require commas at the end of inf tags, it's
976 // incredibly common for sites to elide the comma if there is no title
977 // attribute present. In this case, we should assert that there is at least
978 // a trailing newline, and then strip it to generate a nameless tag.
979 title_str = content.Substr(0, 0);
980 if (*content.Str().end() != '\n') {
981 duration_str = content;
982 } else {
983 duration_str = content.Substr(0, content.Str().length() - 1);
984 }
985 } else {
986 duration_str = content.Substr(0, comma);
987 title_str = content.Substr(comma + 1);
Will Cassella23cdc9a12022-06-15 23:03:26988 }
989
Will Cassella0412e58a2022-07-20 21:35:38990 // Extract duration
Alison Gale59c007a2024-04-20 03:05:40991 // TODO(crbug.com/40210233): Below version 3 this should be rounded to an
Will Cassella0412e58a2022-07-20 21:35:38992 // integer
993 auto duration_result =
994 types::ParseDecimalFloatingPoint(duration_str.SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:25995 if (!duration_result.has_value()) {
Will Cassella23cdc9a12022-06-15 23:03:26996 return ParseStatus(ParseStatusCode::kMalformedTag)
997 .AddCause(std::move(duration_result).error());
998 }
Will Cassella0412e58a2022-07-20 21:35:38999 const auto duration = base::Seconds(std::move(duration_result).value());
Will Cassella23cdc9a12022-06-15 23:03:261000
Will Cassella23cdc9a12022-06-15 23:03:261001 if (duration.is_max()) {
1002 return ParseStatusCode::kValueOverflowsTimeDelta;
1003 }
1004
Will Cassella0412e58a2022-07-20 21:35:381005 return InfTag{.duration = duration, .title = title_str};
Will Cassellad7bc2c02022-05-11 00:52:021006}
Will Cassella3ff524e2022-04-21 21:52:131007
Will Cassella5343f8f2022-07-20 00:11:331008// static
Will Cassella0412e58a2022-07-20 21:35:381009ParseStatus::Or<XBitrateTag> XBitrateTag::Parse(TagItem tag) {
1010 return ParseDecimalIntegerTag(tag, &XBitrateTag::bitrate);
1011}
1012
1013// static
1014ParseStatus::Or<XByteRangeTag> XByteRangeTag::Parse(TagItem tag) {
1015 DCHECK(tag.GetName() == ToTagName(XByteRangeTag::kName));
Will Cassella5c7327e2022-05-26 19:28:461016 if (!tag.GetContent().has_value()) {
1017 return ParseStatusCode::kMalformedTag;
1018 }
1019
Ted Meyer0aa70612024-06-10 23:16:191020 auto range = types::parsing::ByteRangeExpression::Parse(
Will Cassella0412e58a2022-07-20 21:35:381021 tag.GetContent()->SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:251022 if (!range.has_value()) {
Will Cassella5c7327e2022-05-26 19:28:461023 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella0412e58a2022-07-20 21:35:381024 .AddCause(std::move(range).error());
Will Cassella5c7327e2022-05-26 19:28:461025 }
1026
Will Cassella0412e58a2022-07-20 21:35:381027 return XByteRangeTag{.range = std::move(range).value()};
1028}
Will Cassella23cdc9a12022-06-15 23:03:261029
Will Cassella0412e58a2022-07-20 21:35:381030// static
1031ParseStatus::Or<XDiscontinuityTag> XDiscontinuityTag::Parse(TagItem tag) {
1032 return ParseEmptyTag<XDiscontinuityTag>(tag);
1033}
Will Cassella23cdc9a12022-06-15 23:03:261034
Will Cassella0412e58a2022-07-20 21:35:381035// static
1036ParseStatus::Or<XDiscontinuitySequenceTag> XDiscontinuitySequenceTag::Parse(
1037 TagItem tag) {
1038 return ParseDecimalIntegerTag(tag, &XDiscontinuitySequenceTag::number);
1039}
Will Cassella23cdc9a12022-06-15 23:03:261040
Will Cassella0412e58a2022-07-20 21:35:381041// static
1042ParseStatus::Or<XEndListTag> XEndListTag::Parse(TagItem tag) {
1043 return ParseEmptyTag<XEndListTag>(tag);
1044}
Will Cassella5c7327e2022-05-26 19:28:461045
Will Cassella0412e58a2022-07-20 21:35:381046// static
1047ParseStatus::Or<XGapTag> XGapTag::Parse(TagItem tag) {
1048 return ParseEmptyTag<XGapTag>(tag);
1049}
1050
1051// static
1052ParseStatus::Or<XIFramesOnlyTag> XIFramesOnlyTag::Parse(TagItem tag) {
1053 return ParseEmptyTag<XIFramesOnlyTag>(tag);
1054}
1055
1056// static
Will Cassellafc185a692022-07-21 00:29:511057ParseStatus::Or<XMapTag> XMapTag::Parse(
1058 TagItem tag,
1059 const VariableDictionary& variable_dict,
1060 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1061 DCHECK(tag.GetName() == ToTagName(XMapTag::kName));
1062 if (!tag.GetContent().has_value()) {
1063 return ParseStatusCode::kMalformedTag;
1064 }
1065
1066 // Parse the attribute-list
1067 TypedAttributeMap<XMapTagAttribute> map;
1068 types::AttributeListIterator iter(*tag.GetContent());
1069 auto map_result = map.FillUntilError(&iter);
1070
1071 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1072 return ParseStatus(ParseStatusCode::kMalformedTag)
1073 .AddCause(std::move(map_result));
1074 }
1075
Arthur Sonzogni581b9e82024-01-30 17:25:151076 std::optional<ResolvedSourceString> uri;
Will Cassellafc185a692022-07-21 00:29:511077 if (map.HasValue(XMapTagAttribute::kUri)) {
1078 auto result = types::ParseQuotedString(map.GetValue(XMapTagAttribute::kUri),
1079 variable_dict, sub_buffer);
Ted Meyer0eb6fcc2022-10-21 03:21:251080 if (!result.has_value()) {
Will Cassellafc185a692022-07-21 00:29:511081 return ParseStatus(ParseStatusCode::kMalformedTag)
1082 .AddCause(std::move(result).error());
1083 }
1084
1085 uri = std::move(result).value();
1086 } else {
1087 return ParseStatusCode::kMalformedTag;
1088 }
1089
Ted Meyer0aa70612024-06-10 23:16:191090 std::optional<types::parsing::ByteRangeExpression> byte_range;
Will Cassellafc185a692022-07-21 00:29:511091 if (map.HasValue(XMapTagAttribute::kByteRange)) {
1092 auto result =
1093 types::ParseQuotedString(map.GetValue(XMapTagAttribute::kByteRange),
1094 variable_dict, sub_buffer)
Ted Meyer0aa70612024-06-10 23:16:191095 .MapValue(types::parsing::ByteRangeExpression::Parse);
Ted Meyer0eb6fcc2022-10-21 03:21:251096 if (!result.has_value()) {
Will Cassellafc185a692022-07-21 00:29:511097 return ParseStatus(ParseStatusCode::kMalformedTag)
1098 .AddCause(std::move(result).error());
1099 }
1100
1101 byte_range = std::move(result).value();
1102 }
1103
1104 return XMapTag{.uri = uri.value(), .byte_range = byte_range};
1105}
1106
1107// static
Will Cassella0412e58a2022-07-20 21:35:381108ParseStatus::Or<XMediaSequenceTag> XMediaSequenceTag::Parse(TagItem tag) {
1109 return ParseDecimalIntegerTag(tag, &XMediaSequenceTag::number);
Will Cassella5c7327e2022-05-26 19:28:461110}
1111
Will Cassella5343f8f2022-07-20 00:11:331112// static
Will Cassella53a6da12022-07-14 00:53:361113ParseStatus::Or<XPartTag> XPartTag::Parse(
1114 TagItem tag,
Ted Meyer38a01192024-06-06 22:53:441115 const VariableDictionary& vars,
1116 VariableDictionary::SubstitutionBuffer& subs) {
Will Cassella53a6da12022-07-14 00:53:361117 DCHECK(tag.GetName() == ToTagName(XPartTag::kName));
1118 if (!tag.GetContent().has_value()) {
1119 return ParseStatusCode::kMalformedTag;
1120 }
1121
1122 TypedAttributeMap<XPartTagAttribute> map;
1123 types::AttributeListIterator iter(*tag.GetContent());
1124 auto map_result = map.FillUntilError(&iter);
1125
1126 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1127 return ParseStatus(ParseStatusCode::kMalformedTag)
1128 .AddCause(std::move(map_result));
1129 }
1130
Ted Meyer0aa70612024-06-10 23:16:191131 auto uri = ParseField<ResolvedSourceString>(
1132 XPartTagAttribute::kUri, map,
1133 &types::parsing::Quoted<types::parsing::RawStr>::ParseWithSubstitution,
1134 vars, subs);
Ted Meyer38a01192024-06-06 22:53:441135 RETURN_IF_ERROR(uri);
Will Cassella53a6da12022-07-14 00:53:361136
Ted Meyer0aa70612024-06-10 23:16:191137 auto duration = ParseField<base::TimeDelta>(
1138 XPartTagAttribute::kDuration, map,
1139 &types::parsing::TimeDelta::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441140 RETURN_IF_ERROR(duration);
Will Cassella53a6da12022-07-14 00:53:361141
Ted Meyer0aa70612024-06-10 23:16:191142 auto byte_range =
1143 ParseField<std::optional<types::parsing::ByteRangeExpression>>(
1144 XPartTagAttribute::kByteRange, map,
1145 &types::parsing::Quoted<
1146 types::parsing::ByteRangeExpression>::ParseWithSubstitution,
1147 vars, subs);
Ted Meyer38a01192024-06-06 22:53:441148 RETURN_IF_ERROR(byte_range);
Will Cassella53a6da12022-07-14 00:53:361149
Ted Meyer38a01192024-06-06 22:53:441150 auto independent = ParseField<std::optional<bool>>(
Ted Meyer0aa70612024-06-10 23:16:191151 XPartTagAttribute::kIndependent, map,
1152 &types::parsing::YesOrNo::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441153 RETURN_IF_ERROR(independent);
Will Cassella53a6da12022-07-14 00:53:361154
Ted Meyer0aa70612024-06-10 23:16:191155 auto gap = ParseField<std::optional<bool>>(
1156 XPartTagAttribute::kGap, map,
1157 &types::parsing::YesOrNo::ParseWithoutSubstitution);
Ted Meyer38a01192024-06-06 22:53:441158 RETURN_IF_ERROR(gap);
Will Cassella53a6da12022-07-14 00:53:361159
Ted Meyer38a01192024-06-06 22:53:441160 return XPartTag{.uri = std::move(uri).value(),
1161 .duration = std::move(duration).value(),
1162 .byte_range = std::move(byte_range).value(),
1163 .independent = (*independent).value_or(false),
1164 .gap = (*gap).value_or(false)};
Will Cassella53a6da12022-07-14 00:53:361165}
1166
Will Cassella5343f8f2022-07-20 00:11:331167// static
Will Cassella0412e58a2022-07-20 21:35:381168ParseStatus::Or<XPartInfTag> XPartInfTag::Parse(TagItem tag) {
1169 DCHECK(tag.GetName() == ToTagName(XPartInfTag::kName));
1170 if (!tag.GetContent().has_value()) {
1171 return ParseStatusCode::kMalformedTag;
1172 }
1173
1174 // Parse the attribute-list
1175 TypedAttributeMap<XPartInfTagAttribute> map;
1176 types::AttributeListIterator iter(*tag.GetContent());
1177 auto map_result = map.FillUntilError(&iter);
1178
1179 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1180 return ParseStatus(ParseStatusCode::kMalformedTag)
1181 .AddCause(std::move(map_result));
1182 }
1183
1184 // Extract the 'PART-TARGET' attribute
1185 base::TimeDelta part_target;
1186 if (map.HasValue(XPartInfTagAttribute::kPartTarget)) {
1187 auto result = types::ParseDecimalFloatingPoint(
1188 map.GetValue(XPartInfTagAttribute::kPartTarget)
1189 .SkipVariableSubstitution());
1190
Ted Meyer0eb6fcc2022-10-21 03:21:251191 if (!result.has_value()) {
Will Cassella0412e58a2022-07-20 21:35:381192 return ParseStatus(ParseStatusCode::kMalformedTag)
1193 .AddCause(std::move(result).error());
1194 }
1195
1196 part_target = base::Seconds(std::move(result).value());
1197
1198 if (part_target.is_max()) {
1199 return ParseStatusCode::kValueOverflowsTimeDelta;
1200 }
1201 } else {
1202 return ParseStatusCode::kMalformedTag;
1203 }
1204
1205 return XPartInfTag{.target_duration = part_target};
1206}
1207
1208// static
1209ParseStatus::Or<XPlaylistTypeTag> XPlaylistTypeTag::Parse(TagItem tag) {
1210 DCHECK(tag.GetName() == ToTagName(XPlaylistTypeTag::kName));
1211
1212 // This tag requires content
1213 if (!tag.GetContent().has_value() || tag.GetContent()->Empty()) {
1214 return ParseStatusCode::kMalformedTag;
1215 }
1216
1217 if (tag.GetContent()->Str() == "EVENT") {
1218 return XPlaylistTypeTag{.type = PlaylistType::kEvent};
1219 }
1220 if (tag.GetContent()->Str() == "VOD") {
1221 return XPlaylistTypeTag{.type = PlaylistType::kVOD};
1222 }
1223
1224 return ParseStatusCode::kUnknownPlaylistType;
1225}
1226
1227// static
Will Cassellacfa1b1b42022-06-17 20:50:521228ParseStatus::Or<XServerControlTag> XServerControlTag::Parse(TagItem tag) {
1229 DCHECK(tag.GetName() == ToTagName(XServerControlTag::kName));
1230 if (!tag.GetContent().has_value()) {
1231 return ParseStatusCode::kMalformedTag;
1232 }
1233
1234 // Parse the attribute-list
1235 TypedAttributeMap<XServerControlTagAttribute> map;
1236 types::AttributeListIterator iter(*tag.GetContent());
1237 auto map_result = map.FillUntilError(&iter);
1238
1239 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1240 return ParseStatus(ParseStatusCode::kMalformedTag)
1241 .AddCause(std::move(map_result));
1242 }
1243
1244 // Extract the 'CAN-SKIP-UNTIL' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151245 std::optional<base::TimeDelta> can_skip_until;
Will Cassellacfa1b1b42022-06-17 20:50:521246 if (map.HasValue(XServerControlTagAttribute::kCanSkipUntil)) {
1247 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291248 map.GetValue(XServerControlTagAttribute::kCanSkipUntil)
1249 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521250
Ted Meyer0eb6fcc2022-10-21 03:21:251251 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521252 return ParseStatus(ParseStatusCode::kMalformedTag)
1253 .AddCause(std::move(result).error());
1254 }
1255
1256 can_skip_until = base::Seconds(std::move(result).value());
1257
1258 if (can_skip_until->is_max()) {
1259 return ParseStatusCode::kValueOverflowsTimeDelta;
1260 }
1261 }
1262
1263 // Extract the 'CAN-SKIP-DATERANGES' attribute
1264 bool can_skip_dateranges = false;
1265 if (map.HasValue(XServerControlTagAttribute::kCanSkipDateRanges)) {
1266 if (map.GetValue(XServerControlTagAttribute::kCanSkipDateRanges).Str() ==
1267 "YES") {
1268 // The existence of this attribute requires the 'CAN-SKIP-UNTIL'
1269 // attribute.
1270 if (!can_skip_until.has_value()) {
1271 return ParseStatusCode::kMalformedTag;
1272 }
1273
1274 can_skip_dateranges = true;
1275 }
1276 }
1277
1278 // Extract the 'HOLD-BACK' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151279 std::optional<base::TimeDelta> hold_back;
Will Cassellacfa1b1b42022-06-17 20:50:521280 if (map.HasValue(XServerControlTagAttribute::kHoldBack)) {
1281 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291282 map.GetValue(XServerControlTagAttribute::kHoldBack)
1283 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521284
Ted Meyer0eb6fcc2022-10-21 03:21:251285 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521286 return ParseStatus(ParseStatusCode::kMalformedTag)
1287 .AddCause(std::move(result).error());
1288 }
1289
1290 hold_back = base::Seconds(std::move(result).value());
1291
1292 if (hold_back->is_max()) {
1293 return ParseStatusCode::kValueOverflowsTimeDelta;
1294 }
1295 }
1296
1297 // Extract the 'PART-HOLD-BACK' attribute
Arthur Sonzogni581b9e82024-01-30 17:25:151298 std::optional<base::TimeDelta> part_hold_back;
Will Cassellacfa1b1b42022-06-17 20:50:521299 if (map.HasValue(XServerControlTagAttribute::kPartHoldBack)) {
1300 auto result = types::ParseDecimalFloatingPoint(
Will Cassella0d3ed6a2022-06-27 19:39:291301 map.GetValue(XServerControlTagAttribute::kPartHoldBack)
1302 .SkipVariableSubstitution());
Will Cassellacfa1b1b42022-06-17 20:50:521303
Ted Meyer0eb6fcc2022-10-21 03:21:251304 if (!result.has_value()) {
Will Cassellacfa1b1b42022-06-17 20:50:521305 return ParseStatus(ParseStatusCode::kMalformedTag)
1306 .AddCause(std::move(result).error());
1307 }
1308
1309 part_hold_back = base::Seconds(std::move(result).value());
1310
1311 if (part_hold_back->is_max()) {
1312 return ParseStatusCode::kValueOverflowsTimeDelta;
1313 }
1314 }
1315
1316 // Extract the 'CAN-BLOCK-RELOAD' attribute
1317 bool can_block_reload = false;
1318 if (map.HasValue(XServerControlTagAttribute::kCanBlockReload)) {
1319 if (map.GetValue(XServerControlTagAttribute::kCanBlockReload).Str() ==
1320 "YES") {
1321 can_block_reload = true;
1322 }
1323 }
1324
1325 return XServerControlTag{
1326 .skip_boundary = can_skip_until,
1327 .can_skip_dateranges = can_skip_dateranges,
1328 .hold_back = hold_back,
1329 .part_hold_back = part_hold_back,
1330 .can_block_reload = can_block_reload,
1331 };
1332}
1333
Will Cassella5343f8f2022-07-20 00:11:331334// static
Will Cassella0412e58a2022-07-20 21:35:381335ParseStatus::Or<XTargetDurationTag> XTargetDurationTag::Parse(TagItem tag) {
1336 DCHECK(tag.GetName() == ToTagName(XTargetDurationTag::kName));
Will Cassella6fa09342022-05-24 23:36:101337 if (!tag.GetContent().has_value()) {
1338 return ParseStatusCode::kMalformedTag;
1339 }
1340
Will Cassella0412e58a2022-07-20 21:35:381341 auto duration_result = types::ParseDecimalInteger(
1342 tag.GetContent().value().SkipVariableSubstitution());
Ted Meyer0eb6fcc2022-10-21 03:21:251343 if (!duration_result.has_value()) {
Will Cassella6fa09342022-05-24 23:36:101344 return ParseStatus(ParseStatusCode::kMalformedTag)
Will Cassella0412e58a2022-07-20 21:35:381345 .AddCause(std::move(duration_result).error());
Will Cassella6fa09342022-05-24 23:36:101346 }
1347
Will Cassella0412e58a2022-07-20 21:35:381348 auto duration = base::Seconds(std::move(duration_result).value());
1349 if (duration.is_max()) {
1350 return ParseStatusCode::kValueOverflowsTimeDelta;
1351 }
Will Cassella6fa09342022-05-24 23:36:101352
Will Cassella0412e58a2022-07-20 21:35:381353 return XTargetDurationTag{.duration = duration};
Will Cassella20b2d7382022-05-25 00:25:191354}
1355
Ted Meyerf7566d32023-12-06 02:31:121356XSkipTag::XSkipTag() = default;
1357XSkipTag::~XSkipTag() = default;
1358XSkipTag::XSkipTag(const XSkipTag&) = default;
1359XSkipTag::XSkipTag(XSkipTag&&) = default;
1360
1361ParseStatus::Or<XSkipTag> XSkipTag::Parse(
1362 TagItem tag,
1363 const VariableDictionary& variable_dict,
1364 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1365 DCHECK(tag.GetName() == ToTagName(XSkipTag::kName));
1366 if (!tag.GetContent().has_value()) {
1367 return ParseStatusCode::kMalformedTag;
1368 }
1369
1370 XSkipTag out;
1371 TypedAttributeMap<XSkipTagAttribute> map;
1372 types::AttributeListIterator iter(*tag.GetContent());
1373 auto map_result = map.FillUntilError(&iter);
1374
1375 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1376 return ParseStatus(ParseStatusCode::kMalformedTag)
1377 .AddCause(std::move(map_result));
1378 }
1379
1380 if (!map.HasValue(XSkipTagAttribute::kSkippedSegments)) {
1381 return ParseStatusCode::kMalformedTag;
1382 }
1383
1384 auto skip_result = types::ParseDecimalInteger(
1385 map.GetValue(XSkipTagAttribute::kSkippedSegments)
1386 .SkipVariableSubstitution());
1387
1388 if (!skip_result.has_value()) {
1389 return ParseStatus(ParseStatusCode::kMalformedTag)
1390 .AddCause(std::move(skip_result).error());
1391 }
1392
1393 out.skipped_segments = std::move(skip_result).value();
1394
1395 if (map.HasValue(XSkipTagAttribute::kRecentlyRemovedDateranges)) {
1396 // TODO(bug/314833987): Should this list have substitution?
1397 auto removed_result = types::ParseQuotedString(
1398 map.GetValue(XSkipTagAttribute::kRecentlyRemovedDateranges),
1399 variable_dict, sub_buffer, /*allow_empty=*/true);
1400 if (!removed_result.has_value()) {
1401 return ParseStatus(ParseStatusCode::kMalformedTag)
1402 .AddCause(std::move(removed_result).error());
1403 }
1404
1405 auto tab_joined_daterange_ids = std::move(removed_result).value();
1406 std::vector<std::string> removed_dateranges = {};
1407
1408 while (!tab_joined_daterange_ids.Empty()) {
1409 const auto daterange_id = tab_joined_daterange_ids.ConsumeDelimiter('\t');
1410 if (daterange_id.Empty()) {
1411 return ParseStatusCode::kMalformedTag;
1412 }
1413 // TODO(bug/314833987): What type should this be parsed into?
1414 removed_dateranges.emplace_back(daterange_id.Str());
1415 }
1416 out.recently_removed_dateranges = std::move(removed_dateranges);
1417 }
1418
1419 return out;
1420}
1421
Ted Meyere55bc2b2024-02-24 00:31:251422ParseStatus::Or<XRenditionReportTag> XRenditionReportTag::Parse(
1423 TagItem tag,
1424 const VariableDictionary& variable_dict,
1425 VariableDictionary::SubstitutionBuffer& sub_buffer) {
1426 CHECK(tag.GetName() == ToTagName(XRenditionReportTag::kName));
1427 if (!tag.GetContent().has_value()) {
1428 return ParseStatusCode::kMalformedTag;
1429 }
1430 XRenditionReportTag out;
1431 TypedAttributeMap<XRenditionReportTagAttribute> map;
1432 types::AttributeListIterator iter(*tag.GetContent());
1433 auto map_result = map.FillUntilError(&iter);
1434
1435 if (map_result.code() != ParseStatusCode::kReachedEOF) {
1436 return std::move(map_result).AddHere();
1437 }
1438
1439 if (map.HasValue(XRenditionReportTagAttribute::kUri)) {
1440 auto uri_result = types::ParseQuotedString(
1441 map.GetValue(XRenditionReportTagAttribute::kUri), variable_dict,
1442 sub_buffer);
1443 if (!uri_result.has_value()) {
1444 return std::move(uri_result).error().AddHere();
1445 }
1446 out.uri = std::move(uri_result).value();
1447 }
1448
1449 if (map.HasValue(XRenditionReportTagAttribute::kLastMSN)) {
1450 auto msn_result = types::ParseDecimalInteger(
1451 map.GetValue(XRenditionReportTagAttribute::kLastMSN)
1452 .SkipVariableSubstitution());
1453 if (!msn_result.has_value()) {
1454 return std::move(msn_result).error().AddHere();
1455 }
1456 out.last_msn = std::move(msn_result).value();
1457 }
1458
1459 if (map.HasValue(XRenditionReportTagAttribute::kLastPart)) {
1460 auto part_result = types::ParseDecimalInteger(
1461 map.GetValue(XRenditionReportTagAttribute::kLastPart)
1462 .SkipVariableSubstitution());
1463 if (!part_result.has_value()) {
1464 return std::move(part_result).error().AddHere();
1465 }
1466 out.last_part = std::move(part_result).value();
1467 }
1468
1469 return out;
1470}
1471
1472ParseStatus::Or<XProgramDateTimeTag> XProgramDateTimeTag::Parse(TagItem tag) {
1473 return ParseISO8601DateTimeTag(tag, &XProgramDateTimeTag::time);
1474}
1475
Ted Meyer38a01192024-06-06 22:53:441476#undef RETURN_IF_ERROR
1477
Will Cassellad9d31172022-03-04 20:52:251478} // namespace media::hls