Skip to content

Commit f7d1918

Browse files
committed
Added InterstitialTagBuilder
1 parent acc69dc commit f7d1918

File tree

2 files changed

+301
-1
lines changed

2 files changed

+301
-1
lines changed

mamba.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@
6464
E65FB2462CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
6565
E65FB2472CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
6666
E65FB2482CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
67+
E65FB24A2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
68+
E65FB24B2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
69+
E65FB24C2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
6770
EC03B63D1E5CC55800BF1F97 /* RapidParserMasterParseArray.c in Sources */ = {isa = PBXBuildFile; fileRef = EC03B63B1E5CC55800BF1F97 /* RapidParserMasterParseArray.c */; };
6871
EC03B63E1E5CC55800BF1F97 /* RapidParserMasterParseArray.c in Sources */ = {isa = PBXBuildFile; fileRef = EC03B63B1E5CC55800BF1F97 /* RapidParserMasterParseArray.c */; };
6972
EC03B63F1E5CC55800BF1F97 /* RapidParserMasterParseArray.h in Headers */ = {isa = PBXBuildFile; fileRef = EC03B63C1E5CC55800BF1F97 /* RapidParserMasterParseArray.h */; };
@@ -678,6 +681,7 @@
678681
D4BB018C1E2EABD500CA006E /* PlaylistTagArray+RenditionGroups.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PlaylistTagArray+RenditionGroups.swift"; sourceTree = "<group>"; };
679682
E65FB2412CD51E4200BF6F56 /* InterstitialValueTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialValueTypes.swift; sourceTree = "<group>"; };
680683
E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialValueTests.swift; sourceTree = "<group>"; };
684+
E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialTagBuilder.swift; sourceTree = "<group>"; };
681685
EC03B62D1E5CC54900BF1F97 /* PrototypeRapidParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrototypeRapidParseArray.include; sourceTree = "<group>"; };
682686
EC03B62E1E5CC54900BF1F97 /* RapidParser_LookingForEForEXTINFState_ParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = RapidParser_LookingForEForEXTINFState_ParseArray.include; sourceTree = "<group>"; };
683687
EC03B62F1E5CC54900BF1F97 /* RapidParser_LookingForEForEXTState_ParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = RapidParser_LookingForEForEXTState_ParseArray.include; sourceTree = "<group>"; };
@@ -1118,6 +1122,7 @@
11181122
EC7ECA011D30177A000EEB7D /* Utils */,
11191123
EC7491B11DD29D5C00AF4E20 /* ValueTypes.swift */,
11201124
E65FB2412CD51E4200BF6F56 /* InterstitialValueTypes.swift */,
1125+
E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */,
11211126
);
11221127
path = mambaSharedFramework;
11231128
sourceTree = "<group>";
@@ -1816,6 +1821,7 @@
18161821
EC349AD62236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
18171822
ECDE18442238114E008566BB /* VariantPlaylist.swift in Sources */,
18181823
EC7491721DD29B5D00AF4E20 /* OrderedDictionary.swift in Sources */,
1824+
E65FB24A2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
18191825
EC3B01BF1DD4D49A00B512E3 /* PlaylistTagCardinalityValidator.swift in Sources */,
18201826
EC7491FA1DD29DD300AF4E20 /* GenericSingleTagValidator.swift in Sources */,
18211827
EC9547851E5CC83C00962535 /* NoOpTagParser.swift in Sources */,
@@ -1995,6 +2001,7 @@
19952001
EC349AD72236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
19962002
ECDE18452238114E008566BB /* VariantPlaylist.swift in Sources */,
19972003
EC7491CA1DD29D5C00AF4E20 /* PlaylistWriter.swift in Sources */,
2004+
E65FB24C2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
19982005
EC3B01A61DD4D47900B512E3 /* EXT_X_KEYValidator.swift in Sources */,
19992006
EC7491731DD29B5D00AF4E20 /* OrderedDictionary.swift in Sources */,
20002007
EC3B01C01DD4D49A00B512E3 /* PlaylistTagCardinalityValidator.swift in Sources */,
@@ -2174,6 +2181,7 @@
21742181
EC1CCD60209A2CF9006B59FF /* PlaylistValidationIssue.swift in Sources */,
21752182
EC349AD82236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
21762183
ECDE18462238114E008566BB /* VariantPlaylist.swift in Sources */,
2184+
E65FB24B2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
21772185
EC1CCD40209A2CF9006B59FF /* EXT_X_MEDIARenditionGroupTYPEValidator.swift in Sources */,
21782186
EC1CCD21209A2CF9006B59FF /* RapidParserStateHandlers.c in Sources */,
21792187
EC1CCD23209A2CF9006B59FF /* CollectionType+FindExtensions.swift in Sources */,

mambaSharedFramework/InterstitialTagBuilder.swift

+293-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// InterstitialTagBuilder.swift
33
// mamba
44
//
5-
// Created by Migneco, Ray on 11/1/24.
5+
// Created by Migneco, Ray on 10/22/24.
66
// Copyright © 2024 Comcast Corporation.
77
// Licensed under the Apache License, Version 2.0 (the "License");
88
// you may not use this file except in compliance with the License.
@@ -18,3 +18,295 @@
1818
//
1919

2020
import Foundation
21+
22+
/// A utility class for configuring and constructing an interstitial tag
23+
/// The properties in this class are in accordance with the HLS spec
24+
/// outlined in `draft-pantos-hls-rfc8216bis-15` Appendix D
25+
/// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#appendix-D
26+
public final class InterstitialTagBuilder {
27+
28+
/// An Interstitial EXT-X-DATERANGE tag MUST have a CLASS attribute whose
29+
/// value is "com.apple.hls.interstitial".
30+
static let appleHLSInterstitialClassIdentifier = "com.apple.hls.interstitial"
31+
32+
/// A quoted-string that uniquely identifies a Date Range in the
33+
/// Playlist. This attribute is REQUIRED
34+
let id: String
35+
36+
/// required to be "com.apple.hls.interstitial"
37+
let classId: String
38+
39+
/// date/time at which the Date Range begins. This attribute is REQUIRED.
40+
let startDate: Date
41+
42+
/// The value of the X-ASSET-URI is a quoted-string absolute URI for a
43+
/// single interstitial asset. An Interstitial EXT-X-DATERANGE tag
44+
/// MUST have either the X-ASSET-URI attribute or the X-ASSET-LIST
45+
/// attribute. It MUST NOT have both.
46+
let assetUri: String?
47+
48+
/// The value of the X-ASSET-LIST is a quoted-string URI to a JSON
49+
/// object.
50+
let assetList: String?
51+
52+
/// the duration of the interstitial content in seconds
53+
var duration: Double?
54+
55+
/// the expected duration of the interstitial content in seconds which can indicate a value when the actual duration is not yet known
56+
var plannedDuration: Double?
57+
58+
/// The value of X-RESUME-OFFSET is a decimal-floating-point of seconds that specifies where primary playback is to resume
59+
/// following the playback of the interstitial.
60+
var resumeOffset: Double?
61+
62+
/// The value of X-PLAYOUT-LIMIT is a decimal-floating-point of seconds that specifies a limit for the playout time of the entire interstitial.
63+
var playoutLimit: Double?
64+
65+
/// The value of the X-SNAP attribute is an enumerated-string-list of Snap Identifiers.
66+
/// The defined Snap Identifiers are: OUT and IN. This attribute is OPTIONAL.
67+
var alignment: HLSInterstitialAlignment?
68+
69+
/// The value of the X-RESTRICT attribute is an enumerated-string-list of Navigation Restriction Identifiers. The defined Navigation
70+
/// Restriction Identifiers are: SKIP and JUMP. These restrictions are enforced at the player UI level.
71+
var restrictions: HLSInterstitialSeekRestrictions?
72+
73+
/// This attribute indicates whether the interstitial is intended to be presented as distinct from the content ("HIGHLIGHT") or not differentiated ("PRIMARY").
74+
var timelineStyle: HLSInterstitialTimelineStyle?
75+
76+
/// The attribute indicates whether the interstitial should be presented as a single point on the timeline or as a range.
77+
var timelineOccupation: HLSInterstitialTimelineOccupation?
78+
79+
/// Provides a hint to the client to know how coordinated playback of the same asset will behave across multiple players
80+
var contentMayVary: Bool?
81+
82+
/// The "X-" prefix defines a namespace reserved for client-defined attributes. The client-attribute MUST be a legal AttributeName.
83+
/// Clients SHOULD use a reverse-DNS syntax when defining their own attribute names to avoid collisions. The attribute value MUST be
84+
/// a quoted-string, a hexadecimal-sequence, or a decimal-floating- point. An example of a client-defined attribute is X-COM-EXAMPLE-
85+
/// AD-ID="XYZ123". These attributes are OPTIONAL.
86+
var clientAttributes: [String: LosslessStringConvertible]?
87+
88+
/// Creates a Tag Builder using an asset Uri
89+
///
90+
/// - Parameters:
91+
/// - id: the identifier for the interstitial
92+
/// - startDate: `Date` at which the interstitial begins
93+
/// - assetUri: the URI locating the interstitial
94+
public init(id: String, startDate: Date, assetUri: String) {
95+
self.id = id
96+
self.startDate = startDate
97+
self.assetUri = assetUri
98+
self.assetList = nil
99+
self.classId = Self.appleHLSInterstitialClassIdentifier
100+
}
101+
102+
/// Creates a Tag Builder using an Asset List Uri
103+
///
104+
/// - Parameters:
105+
/// - id: the identifier for the interstitial
106+
/// - startDate: `Date` indicating when the interstitial begins
107+
/// - assetList: the URI to a JSON object containing the assets
108+
public init(id: String, startDate: Date, assetList: String) {
109+
self.id = id
110+
self.startDate = startDate
111+
self.assetList = assetList
112+
self.assetUri = nil
113+
self.classId = Self.appleHLSInterstitialClassIdentifier
114+
}
115+
116+
/// Specifies the duration of the interstitial
117+
///
118+
/// - Parameter duration: `Double` indicating duration
119+
///
120+
/// - Returns: an instance of the builder
121+
@discardableResult
122+
public func withDuration(_ duration: Double) -> Self {
123+
self.duration = duration
124+
125+
return self
126+
}
127+
128+
/// Specifies the planned duration of the interstitial
129+
///
130+
/// - Parameter duration: `Double` indicating duration
131+
///
132+
/// - Returns: an instance of the builder
133+
@discardableResult
134+
public func withPlannedDuration(_ plannedDuration: Double) -> Self {
135+
self.plannedDuration = plannedDuration
136+
137+
return self
138+
}
139+
140+
/// Configures the interstitial with a resume offset
141+
///
142+
/// - Parameter offset: `Double` indicating the resume offset
143+
///
144+
/// - Returns: an instance of the builder
145+
@discardableResult
146+
public func withResumeOffset(_ offset: Double) -> Self {
147+
self.resumeOffset = offset
148+
149+
return self
150+
}
151+
152+
/// Configures the interstitial with a playout limit
153+
///
154+
/// - Parameter limit: `Double` indicating playout limit
155+
///
156+
/// - Returns: an instance of the builder
157+
@discardableResult
158+
public func withPlayoutLimit(_ limit: Double) -> Self {
159+
self.playoutLimit = limit
160+
161+
return self
162+
}
163+
164+
/// Specifies the alignment of the interstitial with respect to content
165+
///
166+
/// - Parameter alignment: `HLSInterstitialAlignment` specifying alignment guides
167+
///
168+
/// - Returns: an instance of the builder
169+
@discardableResult
170+
public func withAlignment(_ alignment: HLSInterstitialAlignment) -> Self {
171+
self.alignment = alignment
172+
173+
return self
174+
}
175+
176+
/// Specifies seek restrictions applied to the interstitial
177+
///
178+
/// - Parameter restrictions: instance of `HLSInterstitialSeekRestrictions`
179+
///
180+
/// - Returns: an instance of the builder
181+
public func withRestrictions(_ restrictions: HLSInterstitialSeekRestrictions) -> Self {
182+
self.restrictions = restrictions
183+
184+
return self
185+
}
186+
187+
/// Specifies how the interstitial is styled on the timeline
188+
///
189+
/// - Parameter style: `HLSInterstitialTimelineStyle` type
190+
///
191+
/// - Returns: an instance of the builder
192+
@discardableResult
193+
public func withTimelineStyle(_ style: HLSInterstitialTimelineStyle) -> Self {
194+
self.timelineStyle = style
195+
196+
return self
197+
}
198+
199+
/// Describes how the interstitial occupies the content timeline
200+
///
201+
/// - Parameter occupation: `HLSInterstitialTimelineOccupation` type
202+
///
203+
/// - Returns: an instance of the builder
204+
@discardableResult
205+
public func withTimelineOccupation(_ occupation: HLSInterstitialTimelineOccupation) -> Self {
206+
self.timelineOccupation = occupation
207+
208+
return self
209+
}
210+
211+
/// Indicates if the interstitial content varies or stays the same during a shared watching activity
212+
///
213+
/// - Parameter variation: `Bool` indicating if there's variation
214+
///
215+
/// - Returns: an instance of the builder
216+
@discardableResult
217+
public func withContentVariation(_ variation: Bool) -> Self {
218+
self.contentMayVary = variation
219+
220+
return self
221+
}
222+
223+
/// Specifies client attributes describing the interstitial
224+
///
225+
/// - Parameter attributes: a map of `[String: LosslessStringConvertible]` describing the attributes
226+
///
227+
/// - Returns: an instance of the builder
228+
@discardableResult
229+
public func withClientAttributes(_ attributes: [String: LosslessStringConvertible]) -> Self {
230+
self.clientAttributes = attributes
231+
232+
return self
233+
}
234+
235+
/// Builds the DateRange tag utilizing the configured HLS interstitial properties
236+
///
237+
/// - Returns: `PlaylistTag`
238+
public func buildTag() -> PlaylistTag {
239+
240+
var tagDictionary = PlaylistTagDictionary()
241+
242+
tagDictionary[PantosValue.id.rawValue] = PlaylistTagValueData(value: id, quoteEscaped: true)
243+
let startDateString = String.DateFormatter.iso8601MS.string(from: startDate)
244+
tagDictionary[PantosValue.startDate.rawValue] = PlaylistTagValueData(value: startDateString,
245+
quoteEscaped: true)
246+
tagDictionary[PantosValue.classAttribute.rawValue] = PlaylistTagValueData(value: classId,
247+
quoteEscaped: true)
248+
249+
if let assetUri {
250+
tagDictionary[PantosValue.assetUri.rawValue] = PlaylistTagValueData(value: assetUri, quoteEscaped: true)
251+
}
252+
253+
if let assetList {
254+
tagDictionary[PantosValue.assetList.rawValue] = PlaylistTagValueData(value: assetList, quoteEscaped: true)
255+
}
256+
257+
if let duration {
258+
tagDictionary[PantosValue.duration.rawValue] = PlaylistTagValueData(value: String(duration),
259+
quoteEscaped: false)
260+
}
261+
262+
if let plannedDuration {
263+
tagDictionary[PantosValue.plannedDuration.rawValue] = PlaylistTagValueData(value: String(plannedDuration),
264+
quoteEscaped: false)
265+
}
266+
267+
if let resumeOffset {
268+
tagDictionary[PantosValue.resumeOffset.rawValue] = PlaylistTagValueData(value: String(resumeOffset),
269+
quoteEscaped: false)
270+
}
271+
272+
if let playoutLimit {
273+
tagDictionary[PantosValue.playoutLimit.rawValue] = PlaylistTagValueData(value: String(playoutLimit),
274+
quoteEscaped: false)
275+
}
276+
277+
if let restrictions {
278+
let str = restrictions.restrictions.map({ $0.rawValue }).joined(separator: ",")
279+
tagDictionary[PantosValue.restrict.rawValue] = PlaylistTagValueData(value: str, quoteEscaped: true)
280+
}
281+
282+
if let alignment {
283+
let str = alignment.values.map({ $0.rawValue }).joined(separator: ",")
284+
tagDictionary[PantosValue.snap.rawValue] = PlaylistTagValueData(value: str, quoteEscaped: true)
285+
}
286+
287+
if let timelineStyle {
288+
tagDictionary[PantosValue.timelineStyle.rawValue] = PlaylistTagValueData(value: timelineStyle.rawValue,
289+
quoteEscaped: true)
290+
}
291+
292+
if let timelineOccupation {
293+
tagDictionary[PantosValue.timelineOccupies.rawValue] = PlaylistTagValueData(value: timelineOccupation.rawValue,
294+
quoteEscaped: true)
295+
}
296+
297+
if let contentMayVary {
298+
tagDictionary[PantosValue.contentMayVary.rawValue] = PlaylistTagValueData(value: contentMayVary == true ? "YES" : "NO",
299+
quoteEscaped: true)
300+
}
301+
302+
if let clientAttributes {
303+
for (k, v) in clientAttributes {
304+
tagDictionary[k] = PlaylistTagValueData(value: String(v), quoteEscaped: true)
305+
}
306+
}
307+
308+
return PlaylistTag(tagDescriptor: PantosTag.EXT_X_DATERANGE,
309+
stringTagData: nil,
310+
parsedValues: tagDictionary)
311+
}
312+
}

0 commit comments

Comments
 (0)