Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
9f03dc7
Adding multivariant playlist tags defined in draft 15 and still missi…
theRealRobG Sep 1, 2024
84b3a0b
Add attributes missing from EXT-X-MEDIA as of draft 15
theRealRobG Sep 2, 2024
63aaabc
Updated test comment with up-to-date docs for EXT-X-MEDIA
theRealRobG Sep 2, 2024
28d401a
Add attributes missing from EXT-X-STREAM-INF as of draft 15
theRealRobG Sep 2, 2024
064b48d
Add attributes missing from EXT-X-I-FRAME-STREAM-INF as of draft 15
theRealRobG Sep 2, 2024
9390aed
Updated encryption method enum with new type
theRealRobG Sep 2, 2024
a46b9e4
Added parsing for CHANNELS attribute
theRealRobG Sep 2, 2024
2441804
Added parsing for HDCP-LEVEL
theRealRobG Sep 2, 2024
492bc60
Added parsing for VIDEO-RANGE
theRealRobG Sep 3, 2024
4fd3f9d
Added parsing for REQ-VIDEO-LAYOUT
theRealRobG Sep 3, 2024
32d0ec8
Added parsing for FORMAT in EXT-X-SESSION-DATA
theRealRobG Sep 3, 2024
cdd2694
Corrected location for EXT-X-SESSION-DATA playlist validation
theRealRobG Sep 3, 2024
2f1eb23
Merge branch 'develop_1.x' into missing-multivariant-playlist-tags-fr…
theRealRobG Sep 4, 2024
c871de1
Favor structs over classes
theRealRobG Sep 7, 2024
f23275d
Favor not double declaring the issue variable
theRealRobG Sep 7, 2024
440593f
Clarified enum naming and favored more terse function syntax
theRealRobG Sep 7, 2024
34de4a9
Favor enums rather than structs holding enums
theRealRobG Sep 7, 2024
02d2cb9
Introduce unrecognized cases to keep parsing forward compatible
theRealRobG Sep 7, 2024
2d21125
Explicitly indicate class is final
theRealRobG Sep 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion mamba.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@

/* Begin PBXBuildFile section */
01CD2E7A1DE4D46F002510E7 /* EXT_X_MAPTagParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CD2E791DE4D46F002510E7 /* EXT_X_MAPTagParserTests.swift */; };
1447582D2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1447582C2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift */; };
1447582E2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1447582C2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift */; };
1447582F2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1447582C2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift */; };
144758312C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758302C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift */; };
144758322C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758302C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift */; };
144758332C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758302C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift */; };
144758352C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758342C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift */; };
144758362C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758342C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift */; };
144758372C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 144758342C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift */; };
1D28F3451EAA9E500010320B /* hls_ad_master_playlist.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 1D28F3401EAA9E500010320B /* hls_ad_master_playlist.m3u8 */; };
1D28F3461EAA9E500010320B /* hls_ad_variant_playlist.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 1D28F3411EAA9E500010320B /* hls_ad_variant_playlist.m3u8 */; };
1D28F3471EAA9E500010320B /* hls_master_playlist_sap.m3u8 in Resources */ = {isa = PBXBuildFile; fileRef = 1D28F3421EAA9E500010320B /* hls_master_playlist_sap.m3u8 */; };
Expand Down Expand Up @@ -614,6 +623,9 @@

/* Begin PBXFileReference section */
01CD2E791DE4D46F002510E7 /* EXT_X_MAPTagParserTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EXT_X_MAPTagParserTests.swift; sourceTree = "<group>"; };
1447582C2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EXT_X_SESSION_KEYValidator.swift; sourceTree = "<group>"; };
144758302C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EXT_X_SESSION_DATATagValidator.swift; sourceTree = "<group>"; };
144758342C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EXT_X_SESSION_DATAPlaylistValidator.swift; sourceTree = "<group>"; };
1D28F3401EAA9E500010320B /* hls_ad_master_playlist.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = hls_ad_master_playlist.m3u8; sourceTree = "<group>"; };
1D28F3411EAA9E500010320B /* hls_ad_variant_playlist.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = hls_ad_variant_playlist.m3u8; sourceTree = "<group>"; };
1D28F3421EAA9E500010320B /* hls_master_playlist_sap.m3u8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = hls_master_playlist_sap.m3u8; sourceTree = "<group>"; };
Expand Down Expand Up @@ -900,7 +912,6 @@
0173AB0D1D5BB371005DE51B /* Pantos-Generic Tag Validators */ = {
isa = PBXGroup;
children = (
EC95478A1E5CC86300962535 /* EXTINFValidator.swift */,
6DD0A1B0242FADC600FF7AAE /* EXT_X_DATERANGEPlaylistValidator.swift */,
6DD0A1AC242F85C800FF7AAE /* EXT_X_DATERANGETagValidator.swift */,
EC3B019E1DD4D47900B512E3 /* EXT_X_KEYValidator.swift */,
Expand All @@ -909,9 +920,13 @@
EC3B01A11DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupNAMEValidator.swift */,
EC3B01A21DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupTYPEValidator.swift */,
43DE4EFC1E564DBE00EEE800 /* EXT_X_MEDIARenditionINSTREAMIDValidator.swift */,
144758342C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift */,
144758302C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift */,
1447582C2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift */,
43DE4EFA1E564DA300EEE800 /* EXT_X_STARTTimeOffsetValidator.swift */,
EC3B01A31DD4D47900B512E3 /* EXT_X_STREAM_INFRenditionGroupValidator.swift */,
EC3B01A41DD4D47900B512E3 /* EXT_X_TARGETDURATIONLengthValidator.swift */,
EC95478A1E5CC86300962535 /* EXTINFValidator.swift */,
EC7491F51DD29DD300AF4E20 /* GenericDictionaryTagValidator.swift */,
EC7491F61DD29DD300AF4E20 /* GenericSingleTagValidator.swift */,
EC7491F71DD29DD300AF4E20 /* HLSDictionaryTagValueIdentifier.swift */,
Expand Down Expand Up @@ -1761,6 +1776,7 @@
ECFAA6581E6DD93C00398D66 /* HLSPlaylist.swift in Sources */,
ECC410601EA02F4800B4E3C8 /* StructureState.swift in Sources */,
EC7491811DD29C3500AF4E20 /* String+Trim.swift in Sources */,
144758352C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */,
EC7491C31DD29D5C00AF4E20 /* HLSValidationIssue.swift in Sources */,
EC74916E1DD29B5D00AF4E20 /* CollectionType+FindExtensions.swift in Sources */,
EC7491DA1DD29D9600AF4E20 /* GenericNoDataTagParser.swift in Sources */,
Expand Down Expand Up @@ -1830,7 +1846,9 @@
EC3B01AB1DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupNAMEValidator.swift in Sources */,
EC7491651DD29B0F00AF4E20 /* FailableStringLiteralConvertible.swift in Sources */,
EC7491461DD299B400AF4E20 /* HLSPlaylistTypes.swift in Sources */,
144758312C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */,
EC3B01A71DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupAUTOSELECTValidator.swift in Sources */,
1447582D2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */,
F700CD391E78A2BE001C9487 /* HLSStringRef_ConcreteNSString.m in Sources */,
43DE4EFB1E564DA300EEE800 /* EXT_X_STARTTimeOffsetValidator.swift in Sources */,
EC74918A1DD29CCB00AF4E20 /* StringDictionaryParser.swift in Sources */,
Expand Down Expand Up @@ -1926,6 +1944,7 @@
ECFAA6591E6DD93C00398D66 /* HLSPlaylist.swift in Sources */,
ECC410611EA02F4800B4E3C8 /* StructureState.swift in Sources */,
EC3B01AA1DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupDEFAULTValidator.swift in Sources */,
144758362C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */,
EC3B01C41DD4D49A00B512E3 /* HLSPlaylistOneToManyValidator.swift in Sources */,
EC7491821DD29C3500AF4E20 /* String+Trim.swift in Sources */,
EC7491C41DD29D5C00AF4E20 /* HLSValidationIssue.swift in Sources */,
Expand Down Expand Up @@ -1995,7 +2014,9 @@
EC74917E1DD29C3500AF4E20 /* String+DateParsing.swift in Sources */,
EC3B01AC1DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupNAMEValidator.swift in Sources */,
EC7491661DD29B0F00AF4E20 /* FailableStringLiteralConvertible.swift in Sources */,
144758322C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */,
EC7491471DD299B400AF4E20 /* HLSPlaylistTypes.swift in Sources */,
1447582E2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */,
F700CD3A1E78A2BE001C9487 /* HLSStringRef_ConcreteNSString.m in Sources */,
EC3B01A81DD4D47900B512E3 /* EXT_X_MEDIARenditionGroupAUTOSELECTValidator.swift in Sources */,
EC74918B1DD29CCB00AF4E20 /* StringDictionaryParser.swift in Sources */,
Expand Down Expand Up @@ -2091,6 +2112,7 @@
EC1CCD36209A2CF9006B59FF /* URL+hlsplaylist.swift in Sources */,
EC1CCD32209A2CF9006B59FF /* String+Trim.swift in Sources */,
EC1CCD46209A2CF9006B59FF /* GenericSingleTagValidator.swift in Sources */,
144758372C83D23100D12CCD /* EXT_X_SESSION_DATAPlaylistValidator.swift in Sources */,
EC1CCD59209A2CF9006B59FF /* HLSParser.swift in Sources */,
EC1CCD30209A2CF9006B59FF /* String+DateParsing.swift in Sources */,
EC1CCD53209A2CF9006B59FF /* GenericDictionaryTagWriter.swift in Sources */,
Expand Down Expand Up @@ -2160,7 +2182,9 @@
EC1CCD4F209A2CF9006B59FF /* HLSPlaylistTagCardinalityValidation.swift in Sources */,
EC1CCD2B209A2CF9006B59FF /* IndeterminateBool.swift in Sources */,
EC1CCCF9209A2CF9006B59FF /* HLSTagCriterion.swift in Sources */,
144758332C83C72B00D12CCD /* EXT_X_SESSION_DATATagValidator.swift in Sources */,
EC1CCD43209A2CF9006B59FF /* EXT_X_STREAM_INFRenditionGroupValidator.swift in Sources */,
1447582F2C83C20800D12CCD /* EXT_X_SESSION_KEYValidator.swift in Sources */,
EC1CCD35209A2CF9006B59FF /* StringDictionaryParser.swift in Sources */,
EC1CCD02209A2CF9006B59FF /* HLSStringRef_ConcreteNSString.m in Sources */,
EC1CCD38209A2CF9006B59FF /* GenericNoDataTagParser.swift in Sources */,
Expand Down
3 changes: 3 additions & 0 deletions mambaSharedFramework/HLSValidationIssue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public enum IssueDescription: String {

case HLSPlaylistRenditionGroupMatchingNAMELANGUAGEValidator = "A Playlist MAY contain multiple groups of the same TYPE in order to provide multiple encodings of each rendition. If it does so, each group of the same TYPE SHOULD contain corresponding members with the same NAME attribute, LANGUAGE attribute, and rendition."
case EXT_X_KEYValidator = "EXT-X-KEY If the encryption method is NONE, the URI, IV, KEYFORMAT and KEYFORMATVERSIONS attributes MUST NOT be present. If the encryption method is AES-128 or SAMPLE-AES, the URI attribute MUST be present."
case EXT_X_SESSION_KEYValidator = "All attributes defined for the EXT-X-KEY tag are also defined for the EXT-X-SESSION-KEY, except that the value of the METHOD attribute MUST NOT be NONE."
case EXT_X_SESSION_DATATagValidator = "Each EXT-X-SESSION-DATA tag MUST contain either a VALUE or URI attribute, but not both."
case EXT_X_SESSION_DATAPlaylistValidator = "A Playlist MAY contain multiple EXT-X-SESSION-DATA tags with the same DATA-ID attribute. A Playlist MUST NOT contain more than one EXT-X-SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute."
case HLSPlaylistRenditionGroupMatchingPROGRAM_IDValidator = "Variant Playlists MUST contain an EXT-X-STREAM-INF tag or EXT-X-I-FRAME-STREAM-INF tag for each variant stream. Each tag identifying an encoding of the same presentation MUST have the same PROGRAM-ID attribute value."
case EXT_X_STREAM_INFRenditionGroupAUDIOValidator = "EXT-X-STREAM-INF - AUDIO The value is a quoted-string. It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the Playlist whose TYPE attribute is AUDIO."
case EXT_X_STREAM_INFRenditionGroupVIDEOValidator = "EXT-X-STREAM-INF - VIDEO The value is a quoted-string. It MUST match the value of the GROUP-ID attribute of an EXT-X-MEDIA tag elsewhere in the Playlist whose TYPE attribute is VIDEO."
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// EXT_X_SESSION_DATAPlaylistValidator.swift
// mamba
//
// Created by Robert Galluccio on 8/31/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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. All rights reserved.
//

import Foundation

class EXT_X_SESSION_DATAPlaylistValidator: HLSPlaylistValidator {
static func validate(hlsPlaylist: any HLSPlaylistInterface) -> [HLSValidationIssue]? {
var issues = [HLSValidationIssue]()

let issue = duplicateIssue(tags: hlsPlaylist.tags.filter { $0.tagDescriptor == PantosTag.EXT_X_SESSION_DATA })
if let issue {
issues.append(issue)
}

return issues.isEmpty ? nil : issues
}

// A Playlist MAY contain multiple EXT-X-SESSION-DATA tags with the same DATA-ID attribute. A Playlist MUST NOT
// contain more than one EXT-X-SESSION-DATA tag with the same DATA-ID attribute and the same LANGUAGE attribute.
private static func duplicateIssue(tags: [HLSTag]) -> HLSValidationIssue? {
var dataIdToLanguagesMap = [String: [String?]]()
for tag in tags {
guard let dataId = tag.value(forValueIdentifier: PantosValue.dataId) else { continue }
var existingLanguages = dataIdToLanguagesMap[dataId] ?? []
existingLanguages.append(tag.value(forValueIdentifier: PantosValue.language))
dataIdToLanguagesMap[dataId] = existingLanguages
}
for languages in dataIdToLanguagesMap.values {
if languages.count != Set(languages).count {
return HLSValidationIssue(description: .EXT_X_SESSION_DATAPlaylistValidator, severity: .error)
}
}
return nil
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// EXT_X_SESSION_DATATagValidator.swift
// mamba
//
// Created by Robert Galluccio on 8/31/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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. All rights reserved.
//

import Foundation

class EXT_X_SESSION_DATATagValidator: HLSTagValidator {
private var genericValidator: GenericDictionaryTagValidator

init() {
genericValidator = GenericDictionaryTagValidator(
tag: PantosTag.EXT_X_SESSION_DATA,
dictionaryValueIdentifiers: [
HLSDictionaryTagValueIdentifierImpl(
valueId: PantosValue.dataId,
optional: false,
expectedType: String.self
),
HLSDictionaryTagValueIdentifierImpl(
valueId: PantosValue.value,
optional: true,
expectedType: String.self
),
HLSDictionaryTagValueIdentifierImpl(
valueId: PantosValue.uri,
optional: true,
expectedType: String.self
),
HLSDictionaryTagValueIdentifierImpl(
valueId: PantosValue.format,
optional: true,
expectedType: String.self
),
HLSDictionaryTagValueIdentifierImpl(
valueId: PantosValue.language,
optional: true,
expectedType: String.self
),
]
)
}

func validate(tag: HLSTag) -> [HLSValidationIssue]? {
var issueList = genericValidator.validate(tag: tag) ?? []

// Each EXT-X-SESSION-DATA tag MUST contain either a VALUE or URI attribute, but not both.
switch (tag.value(forValueIdentifier: PantosValue.value), tag.value(forValueIdentifier: PantosValue.uri)) {
case (.none, .some), (.some, .none):
break
case (.some, .some), (.none, .none):
issueList.append(HLSValidationIssue(description: .EXT_X_SESSION_DATATagValidator, severity: .error))
}

return issueList.isEmpty ? nil : issueList
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// EXT_X_SESSION_KEYValidator.swift
// mamba
//
// Created by Robert Galluccio on 8/31/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.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License. All rights reserved.
//

import Foundation

// All attributes defined for the EXT-X-KEY tag (Section 4.4.4.4) are also defined for the
// EXT-X-SESSION-KEY, except that the value of the METHOD attribute MUST NOT be NONE.
class EXT_X_SESSION_KEYValidator: EXT_X_KEYValidator {

override public func validate(tag: HLSTag) -> [HLSValidationIssue]? {
var issueList = super.validate(tag: tag) ?? []

if let method = tag.value(forValueIdentifier: PantosValue.method) {
if method == HLSEncryptionMethodType.EncryptionMethod.None.rawValue {
issueList.append(
HLSValidationIssue(
description: IssueDescription.EXT_X_SESSION_KEYValidator,
severity: IssueSeverity.error
)
)
}
}

return issueList.isEmpty ? nil : issueList
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ public class HLSVariantPlaylistValidator: HLSExtensibleValidator {
HLSPlaylistRenditionGroupMatchingPROGRAM_IDValidator.self,
HLSPlaylistRenditionGroupMatchingNAMELANGUAGEValidator.self,
EXT_X_STARTTimeOffsetValidator.self,
EXT_X_DATERANGEPlaylistValidator.self]
EXT_X_DATERANGEPlaylistValidator.self,
EXT_X_SESSION_DATAPlaylistValidator.self]
}

/// A general purpose validator that will validate either a variant or a master playlist
Expand Down
Loading