Skip to content

Commit

Permalink
Added InterstitialTagBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
rmigneco committed Nov 1, 2024
1 parent acc69dc commit f7d1918
Show file tree
Hide file tree
Showing 2 changed files with 301 additions and 1 deletion.
8 changes: 8 additions & 0 deletions mamba.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
E65FB2462CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
E65FB2472CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
E65FB2482CD5241D00BF6F56 /* InterstitialValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */; };
E65FB24A2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
E65FB24B2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
E65FB24C2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */; };
EC03B63D1E5CC55800BF1F97 /* RapidParserMasterParseArray.c in Sources */ = {isa = PBXBuildFile; fileRef = EC03B63B1E5CC55800BF1F97 /* RapidParserMasterParseArray.c */; };
EC03B63E1E5CC55800BF1F97 /* RapidParserMasterParseArray.c in Sources */ = {isa = PBXBuildFile; fileRef = EC03B63B1E5CC55800BF1F97 /* RapidParserMasterParseArray.c */; };
EC03B63F1E5CC55800BF1F97 /* RapidParserMasterParseArray.h in Headers */ = {isa = PBXBuildFile; fileRef = EC03B63C1E5CC55800BF1F97 /* RapidParserMasterParseArray.h */; };
Expand Down Expand Up @@ -678,6 +681,7 @@
D4BB018C1E2EABD500CA006E /* PlaylistTagArray+RenditionGroups.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "PlaylistTagArray+RenditionGroups.swift"; sourceTree = "<group>"; };
E65FB2412CD51E4200BF6F56 /* InterstitialValueTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialValueTypes.swift; sourceTree = "<group>"; };
E65FB2452CD5241D00BF6F56 /* InterstitialValueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialValueTests.swift; sourceTree = "<group>"; };
E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterstitialTagBuilder.swift; sourceTree = "<group>"; };
EC03B62D1E5CC54900BF1F97 /* PrototypeRapidParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = PrototypeRapidParseArray.include; sourceTree = "<group>"; };
EC03B62E1E5CC54900BF1F97 /* RapidParser_LookingForEForEXTINFState_ParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = RapidParser_LookingForEForEXTINFState_ParseArray.include; sourceTree = "<group>"; };
EC03B62F1E5CC54900BF1F97 /* RapidParser_LookingForEForEXTState_ParseArray.include */ = {isa = PBXFileReference; lastKnownFileType = text; path = RapidParser_LookingForEForEXTState_ParseArray.include; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1118,6 +1122,7 @@
EC7ECA011D30177A000EEB7D /* Utils */,
EC7491B11DD29D5C00AF4E20 /* ValueTypes.swift */,
E65FB2412CD51E4200BF6F56 /* InterstitialValueTypes.swift */,
E65FB2492CD524BF00BF6F56 /* InterstitialTagBuilder.swift */,
);
path = mambaSharedFramework;
sourceTree = "<group>";
Expand Down Expand Up @@ -1816,6 +1821,7 @@
EC349AD62236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
ECDE18442238114E008566BB /* VariantPlaylist.swift in Sources */,
EC7491721DD29B5D00AF4E20 /* OrderedDictionary.swift in Sources */,
E65FB24A2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
EC3B01BF1DD4D49A00B512E3 /* PlaylistTagCardinalityValidator.swift in Sources */,
EC7491FA1DD29DD300AF4E20 /* GenericSingleTagValidator.swift in Sources */,
EC9547851E5CC83C00962535 /* NoOpTagParser.swift in Sources */,
Expand Down Expand Up @@ -1995,6 +2001,7 @@
EC349AD72236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
ECDE18452238114E008566BB /* VariantPlaylist.swift in Sources */,
EC7491CA1DD29D5C00AF4E20 /* PlaylistWriter.swift in Sources */,
E65FB24C2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
EC3B01A61DD4D47900B512E3 /* EXT_X_KEYValidator.swift in Sources */,
EC7491731DD29B5D00AF4E20 /* OrderedDictionary.swift in Sources */,
EC3B01C01DD4D49A00B512E3 /* PlaylistTagCardinalityValidator.swift in Sources */,
Expand Down Expand Up @@ -2174,6 +2181,7 @@
EC1CCD60209A2CF9006B59FF /* PlaylistValidationIssue.swift in Sources */,
EC349AD82236F55F0077432B /* MasterPlaylistStructure.swift in Sources */,
ECDE18462238114E008566BB /* VariantPlaylist.swift in Sources */,
E65FB24B2CD524BF00BF6F56 /* InterstitialTagBuilder.swift in Sources */,
EC1CCD40209A2CF9006B59FF /* EXT_X_MEDIARenditionGroupTYPEValidator.swift in Sources */,
EC1CCD21209A2CF9006B59FF /* RapidParserStateHandlers.c in Sources */,
EC1CCD23209A2CF9006B59FF /* CollectionType+FindExtensions.swift in Sources */,
Expand Down
294 changes: 293 additions & 1 deletion mambaSharedFramework/InterstitialTagBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// InterstitialTagBuilder.swift
// mamba
//
// Created by Migneco, Ray on 11/1/24.
// Created by Migneco, Ray on 10/22/24.
// Copyright © 2024 Comcast Corporation.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -18,3 +18,295 @@
//

import Foundation

/// A utility class for configuring and constructing an interstitial tag
/// The properties in this class are in accordance with the HLS spec
/// outlined in `draft-pantos-hls-rfc8216bis-15` Appendix D
/// https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#appendix-D
public final class InterstitialTagBuilder {

/// An Interstitial EXT-X-DATERANGE tag MUST have a CLASS attribute whose
/// value is "com.apple.hls.interstitial".
static let appleHLSInterstitialClassIdentifier = "com.apple.hls.interstitial"

/// A quoted-string that uniquely identifies a Date Range in the
/// Playlist. This attribute is REQUIRED
let id: String

/// required to be "com.apple.hls.interstitial"
let classId: String

/// date/time at which the Date Range begins. This attribute is REQUIRED.
let startDate: Date

/// The value of the X-ASSET-URI is a quoted-string absolute URI for a
/// single interstitial asset. An Interstitial EXT-X-DATERANGE tag
/// MUST have either the X-ASSET-URI attribute or the X-ASSET-LIST
/// attribute. It MUST NOT have both.
let assetUri: String?

/// The value of the X-ASSET-LIST is a quoted-string URI to a JSON
/// object.
let assetList: String?

/// the duration of the interstitial content in seconds
var duration: Double?

/// the expected duration of the interstitial content in seconds which can indicate a value when the actual duration is not yet known
var plannedDuration: Double?

/// The value of X-RESUME-OFFSET is a decimal-floating-point of seconds that specifies where primary playback is to resume
/// following the playback of the interstitial.
var resumeOffset: Double?

/// 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.
var playoutLimit: Double?

/// The value of the X-SNAP attribute is an enumerated-string-list of Snap Identifiers.
/// The defined Snap Identifiers are: OUT and IN. This attribute is OPTIONAL.
var alignment: HLSInterstitialAlignment?

/// The value of the X-RESTRICT attribute is an enumerated-string-list of Navigation Restriction Identifiers. The defined Navigation
/// Restriction Identifiers are: SKIP and JUMP. These restrictions are enforced at the player UI level.
var restrictions: HLSInterstitialSeekRestrictions?

/// This attribute indicates whether the interstitial is intended to be presented as distinct from the content ("HIGHLIGHT") or not differentiated ("PRIMARY").
var timelineStyle: HLSInterstitialTimelineStyle?

/// The attribute indicates whether the interstitial should be presented as a single point on the timeline or as a range.
var timelineOccupation: HLSInterstitialTimelineOccupation?

/// Provides a hint to the client to know how coordinated playback of the same asset will behave across multiple players
var contentMayVary: Bool?

/// The "X-" prefix defines a namespace reserved for client-defined attributes. The client-attribute MUST be a legal AttributeName.
/// Clients SHOULD use a reverse-DNS syntax when defining their own attribute names to avoid collisions. The attribute value MUST be
/// a quoted-string, a hexadecimal-sequence, or a decimal-floating- point. An example of a client-defined attribute is X-COM-EXAMPLE-
/// AD-ID="XYZ123". These attributes are OPTIONAL.
var clientAttributes: [String: LosslessStringConvertible]?

/// Creates a Tag Builder using an asset Uri
///
/// - Parameters:
/// - id: the identifier for the interstitial
/// - startDate: `Date` at which the interstitial begins
/// - assetUri: the URI locating the interstitial
public init(id: String, startDate: Date, assetUri: String) {
self.id = id
self.startDate = startDate
self.assetUri = assetUri
self.assetList = nil
self.classId = Self.appleHLSInterstitialClassIdentifier
}

/// Creates a Tag Builder using an Asset List Uri
///
/// - Parameters:
/// - id: the identifier for the interstitial
/// - startDate: `Date` indicating when the interstitial begins
/// - assetList: the URI to a JSON object containing the assets
public init(id: String, startDate: Date, assetList: String) {
self.id = id
self.startDate = startDate
self.assetList = assetList
self.assetUri = nil
self.classId = Self.appleHLSInterstitialClassIdentifier
}

/// Specifies the duration of the interstitial
///
/// - Parameter duration: `Double` indicating duration
///
/// - Returns: an instance of the builder
@discardableResult
public func withDuration(_ duration: Double) -> Self {
self.duration = duration

return self
}

/// Specifies the planned duration of the interstitial
///
/// - Parameter duration: `Double` indicating duration
///
/// - Returns: an instance of the builder
@discardableResult
public func withPlannedDuration(_ plannedDuration: Double) -> Self {
self.plannedDuration = plannedDuration

return self
}

/// Configures the interstitial with a resume offset
///
/// - Parameter offset: `Double` indicating the resume offset
///
/// - Returns: an instance of the builder
@discardableResult
public func withResumeOffset(_ offset: Double) -> Self {
self.resumeOffset = offset

return self
}

/// Configures the interstitial with a playout limit
///
/// - Parameter limit: `Double` indicating playout limit
///
/// - Returns: an instance of the builder
@discardableResult
public func withPlayoutLimit(_ limit: Double) -> Self {
self.playoutLimit = limit

return self
}

/// Specifies the alignment of the interstitial with respect to content
///
/// - Parameter alignment: `HLSInterstitialAlignment` specifying alignment guides
///
/// - Returns: an instance of the builder
@discardableResult
public func withAlignment(_ alignment: HLSInterstitialAlignment) -> Self {
self.alignment = alignment

return self
}

/// Specifies seek restrictions applied to the interstitial
///
/// - Parameter restrictions: instance of `HLSInterstitialSeekRestrictions`
///
/// - Returns: an instance of the builder
public func withRestrictions(_ restrictions: HLSInterstitialSeekRestrictions) -> Self {
self.restrictions = restrictions

return self
}

/// Specifies how the interstitial is styled on the timeline
///
/// - Parameter style: `HLSInterstitialTimelineStyle` type
///
/// - Returns: an instance of the builder
@discardableResult
public func withTimelineStyle(_ style: HLSInterstitialTimelineStyle) -> Self {
self.timelineStyle = style

return self
}

/// Describes how the interstitial occupies the content timeline
///
/// - Parameter occupation: `HLSInterstitialTimelineOccupation` type
///
/// - Returns: an instance of the builder
@discardableResult
public func withTimelineOccupation(_ occupation: HLSInterstitialTimelineOccupation) -> Self {
self.timelineOccupation = occupation

return self
}

/// Indicates if the interstitial content varies or stays the same during a shared watching activity
///
/// - Parameter variation: `Bool` indicating if there's variation
///
/// - Returns: an instance of the builder
@discardableResult
public func withContentVariation(_ variation: Bool) -> Self {
self.contentMayVary = variation

return self
}

/// Specifies client attributes describing the interstitial
///
/// - Parameter attributes: a map of `[String: LosslessStringConvertible]` describing the attributes
///
/// - Returns: an instance of the builder
@discardableResult
public func withClientAttributes(_ attributes: [String: LosslessStringConvertible]) -> Self {
self.clientAttributes = attributes

return self
}

/// Builds the DateRange tag utilizing the configured HLS interstitial properties
///
/// - Returns: `PlaylistTag`
public func buildTag() -> PlaylistTag {

var tagDictionary = PlaylistTagDictionary()

tagDictionary[PantosValue.id.rawValue] = PlaylistTagValueData(value: id, quoteEscaped: true)
let startDateString = String.DateFormatter.iso8601MS.string(from: startDate)
tagDictionary[PantosValue.startDate.rawValue] = PlaylistTagValueData(value: startDateString,
quoteEscaped: true)
tagDictionary[PantosValue.classAttribute.rawValue] = PlaylistTagValueData(value: classId,
quoteEscaped: true)

if let assetUri {
tagDictionary[PantosValue.assetUri.rawValue] = PlaylistTagValueData(value: assetUri, quoteEscaped: true)
}

if let assetList {
tagDictionary[PantosValue.assetList.rawValue] = PlaylistTagValueData(value: assetList, quoteEscaped: true)
}

if let duration {
tagDictionary[PantosValue.duration.rawValue] = PlaylistTagValueData(value: String(duration),
quoteEscaped: false)
}

if let plannedDuration {
tagDictionary[PantosValue.plannedDuration.rawValue] = PlaylistTagValueData(value: String(plannedDuration),
quoteEscaped: false)
}

if let resumeOffset {
tagDictionary[PantosValue.resumeOffset.rawValue] = PlaylistTagValueData(value: String(resumeOffset),
quoteEscaped: false)
}

if let playoutLimit {
tagDictionary[PantosValue.playoutLimit.rawValue] = PlaylistTagValueData(value: String(playoutLimit),
quoteEscaped: false)
}

if let restrictions {
let str = restrictions.restrictions.map({ $0.rawValue }).joined(separator: ",")
tagDictionary[PantosValue.restrict.rawValue] = PlaylistTagValueData(value: str, quoteEscaped: true)
}

if let alignment {
let str = alignment.values.map({ $0.rawValue }).joined(separator: ",")
tagDictionary[PantosValue.snap.rawValue] = PlaylistTagValueData(value: str, quoteEscaped: true)
}

if let timelineStyle {
tagDictionary[PantosValue.timelineStyle.rawValue] = PlaylistTagValueData(value: timelineStyle.rawValue,
quoteEscaped: true)
}

if let timelineOccupation {
tagDictionary[PantosValue.timelineOccupies.rawValue] = PlaylistTagValueData(value: timelineOccupation.rawValue,
quoteEscaped: true)
}

if let contentMayVary {
tagDictionary[PantosValue.contentMayVary.rawValue] = PlaylistTagValueData(value: contentMayVary == true ? "YES" : "NO",
quoteEscaped: true)
}

if let clientAttributes {
for (k, v) in clientAttributes {
tagDictionary[k] = PlaylistTagValueData(value: String(v), quoteEscaped: true)
}
}

return PlaylistTag(tagDescriptor: PantosTag.EXT_X_DATERANGE,
stringTagData: nil,
parsedValues: tagDictionary)
}
}

0 comments on commit f7d1918

Please sign in to comment.