Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,16 @@ final class HLSPlaylistStructure: HLSPlaylistStructureInterface {
do {
let result = try HLSPlaylistStructureConstructor.generateMediaGroups(fromTags: _tags)

let mediaSpans = try HLSPlaylistStructureConstructor.generateMediaSpans(fromTags: _tags,
header: result.header,
mediaSegmentGroups: result.mediaSegmentGroups)
let mediaSpans: [TagSpan]
do {
mediaSpans = try HLSPlaylistStructureConstructor.generateMediaSpans(
fromTags: _tags,
header: result.header,
mediaSegmentGroups: result.mediaSegmentGroups
)
} catch {
mediaSpans = []
}

self._header = result.header
self._mediaSegmentGroups = result.mediaSegmentGroups
Expand Down Expand Up @@ -508,6 +515,11 @@ fileprivate struct HLSPlaylistStructureConstructor {
header: TagGroup?,
mediaSegmentGroups: [MediaSegmentTagGroup]) throws -> [TagSpan] {

// If the playlist contains no segments then there are no spans
if mediaSegmentGroups.isEmpty {
return []
}

var mediaSpans = [TagSpan]()

// handle our only known spannable tag, `EXT-X-KEY`
Expand Down Expand Up @@ -546,7 +558,9 @@ fileprivate struct HLSPlaylistStructureConstructor {

if let startKeyIndex = startKeyIndex, let startKeyTag = startKeyTag {
// we are closing out our last key
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...currentIndex - 1))
if startKeyIndex <= currentIndex - 1 {
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...(currentIndex - 1)))
}
}

startKeyIndex = currentIndex
Expand All @@ -559,10 +573,15 @@ fileprivate struct HLSPlaylistStructureConstructor {

// close out our last tag
if let startKeyIndex = startKeyIndex, let startKeyTag = startKeyTag {
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...(currentIndex - 1)))
if startKeyIndex <= currentIndex - 1 {
mediaSpans.append(TagSpan(parentTag: startKeyTag, tagMediaSpan: startKeyIndex...(currentIndex - 1)))
}
}

// instead of assert, warn softly if key counts mismatch (footer keys or malformed playlists)
if keyCount != keyTags.count {
print("Warning: generateMediaSpans counted \(keyCount) EXT-X-KEY tags, but found \(keyTags.count). Possibly due to footer-only key tags.")
}

assert(keyCount == keyTags.count, "we missed a key tag")

return mediaSpans
}
Expand Down
48 changes: 48 additions & 0 deletions mambaTests/HLSMediaSpanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,52 @@ class HLSMediaSpanTests: XCTestCase {
let hlsString = hlsArray.joined()
runTest(hlsString: hlsString, expectedSpans: [0...1, 2...4, 5...8])
}

// This validates the early return logic in generateMediaSpans() for empty mediaSegmentGroups.
func testNoMediaSegmentsScenario() {
print("Starting test: testNoMediaSegmentsScenario")
let hlsArray = [
"#EXTM3U\n",
"#EXT-X-TARGETDURATION:6\n",
"#EXT-X-VERSION:3\n",
"#EXT-X-MEDIA-SEQUENCE:0\n",
"#EXT-X-PLAYLIST-TYPE:VOD\n",
"#EXT-X-KEY:METHOD=NONE\n",
"#EXT-X-MAP:URI=\"test.mp4\",BYTERANGE=\"610@0\"\n"
]
let hlsString = hlsArray.joined()
runTest(hlsString: hlsString, expectedSpans: [])
print("Finished test: testNoMediaSegmentsScenario")
}

// Covers an edge case crash where an EXT-X-KEY appears in the header, but the playlist has no media segments.
func testKeyInHeaderWithNoMediaSegmentsDoesNotCrash() {
print("Starting test: testKeyInHeaderWithNoMediaSegmentsDoesNotCrash")
let hlsArray = [
"#EXTM3U\n",
"#EXT-X-VERSION:3\n",
"#EXT-X-KEY:METHOD=AES-128,URI=\"enc.key\"\n",
"#EXT-X-ENDLIST\n"
]
let hlsString = hlsArray.joined()
runTest(hlsString: hlsString, expectedSpans: [])
print("Finished test: testKeyInHeaderWithNoMediaSegmentsDoesNotCrash")
}

// Validates that an EXT-X-KEY tag appearing after the last media segment (in the footer) does not crash generateMediaSpans() or create invalid spans. This seems to occur with DAI
func testFooterOnlyKeyDoesNotCrashOrAppend() {
print("Starting test: testFooterOnlyKeyDoesNotCrashOrAppend")
let hlsArray = [
"#EXTM3U\n",
"#EXT-X-VERSION:3\n",
"#EXT-X-TARGETDURATION:6\n",
"#EXTINF:6.0,\n",
"segment1.ts\n",
"#EXT-X-KEY:METHOD=AES-128,URI=\"footer.key\"\n",
"#EXT-X-ENDLIST\n"
]
let hlsString = hlsArray.joined()
runTest(hlsString: hlsString, expectedSpans: [])
print("Finished test: testFooterOnlyKeyDoesNotCrashOrAppend")
}
}