Skip to content

Commit 64021be

Browse files
committed
修复: 保留未配置选择器的 HLS 播放资源
1 parent 9c04a08 commit 64021be

3 files changed

Lines changed: 74 additions & 17 deletions

File tree

Sources/HTTPMediaCache/HLS/HLSPlaylistRewriter.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public struct HLSPlaylistRewriter: Sendable {
5050
}
5151

5252
let parsed = HLSPlaylistParser().parse(playlist: playlist, sourceURL: baseURL)
53-
let selectedURLs = await selectedPlaybackURLs(in: parsed, originalURL: baseURL, currentURL: baseURL)
53+
let selection = await playbackSelection(in: parsed, originalURL: baseURL, currentURL: baseURL)
5454
let lines = playlist.split(separator: "\n", omittingEmptySubsequences: false).map(String.init)
5555

5656
var rewrittenLines: [String] = []
@@ -63,7 +63,7 @@ public struct HLSPlaylistRewriter: Sendable {
6363
if let uriIndex = nextURIIndex(after: index, in: lines),
6464
let url = URL(string: lines[uriIndex], relativeTo: baseURL)?.absoluteURL
6565
{
66-
if selectedURLs.isEmpty || selectedURLs.contains(url) {
66+
if !selection.filtersVariants || selection.variantURLs.contains(url) {
6767
rewrittenLines.append(rewriteURIAttribute(in: text, baseURL: baseURL, kind: nil))
6868
var cursor = index + 1
6969
while cursor < uriIndex {
@@ -82,7 +82,7 @@ public struct HLSPlaylistRewriter: Sendable {
8282
}
8383

8484
if text.hasPrefix("#EXT-X-MEDIA:"),
85-
let rewritten = rewriteRenditionLine(text, baseURL: baseURL, selectedURLs: selectedURLs)
85+
let rewritten = rewriteRenditionLine(text, baseURL: baseURL, selection: selection)
8686
{
8787
rewrittenLines.append(rewritten)
8888
} else if !text.hasPrefix("#EXT-X-MEDIA:") {
@@ -107,7 +107,7 @@ public struct HLSPlaylistRewriter: Sendable {
107107
return rewrittenLines.joined(separator: "\n")
108108
}
109109

110-
private func rewriteRenditionLine(_ line: String, baseURL: URL, selectedURLs: Set<URL>) -> String? {
110+
private func rewriteRenditionLine(_ line: String, baseURL: URL, selection: HLSPlaybackSelection) -> String? {
111111
guard let uriRange = line.range(of: "URI=") else {
112112
return line
113113
}
@@ -131,7 +131,7 @@ public struct HLSPlaylistRewriter: Sendable {
131131
guard let url = URL(string: uriText, relativeTo: baseURL)?.absoluteURL else {
132132
return line
133133
}
134-
guard selectedURLs.isEmpty || selectedURLs.contains(url) else {
134+
guard !selection.filtersRenditions || selection.renditionURLs.contains(url) else {
135135
return nil
136136
}
137137
return rewriteURIAttribute(in: line, baseURL: baseURL, kind: renditionKind(from: line))
@@ -285,12 +285,12 @@ public struct HLSPlaylistRewriter: Sendable {
285285
return attributes
286286
}
287287

288-
private func selectedPlaybackURLs(
288+
private func playbackSelection(
289289
in playlist: HLSPlaylist,
290290
originalURL: URL,
291291
currentURL: URL
292-
) async -> Set<URL> {
293-
await HLSSelectionResolver().selectedPlaybackURLs(in: playlist, originalURL: originalURL, currentURL: currentURL)
292+
) async -> HLSPlaybackSelection {
293+
await HLSSelectionResolver().playbackSelection(in: playlist, originalURL: originalURL, currentURL: currentURL)
294294
}
295295

296296
private func nextURIIndex(after index: Int, in lines: [String]) -> Int? {

Sources/HTTPMediaCache/HLS/HLSSelectionResolver.swift

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ struct HLSSelectedResources: Equatable {
2222
}
2323
}
2424

25+
struct HLSPlaybackSelection: Equatable {
26+
var variantURLs: Set<URL>
27+
var renditionURLs: Set<URL>
28+
var filtersVariants: Bool
29+
var filtersRenditions: Bool
30+
31+
static let unrestricted = HLSPlaybackSelection(
32+
variantURLs: [],
33+
renditionURLs: [],
34+
filtersVariants: false,
35+
filtersRenditions: false
36+
)
37+
}
38+
2539
struct HLSSelectionResolver {
2640
func selectedResources(
2741
in playlist: HLSPlaylist,
@@ -45,21 +59,33 @@ struct HLSSelectionResolver {
4559
return HLSSelectedResources(variant: selectedVariant, renditions: renditions)
4660
}
4761

48-
func selectedPlaybackURLs(
62+
func playbackSelection(
4963
in playlist: HLSPlaylist,
5064
originalURL: URL,
5165
currentURL: URL
52-
) async -> Set<URL> {
66+
) async -> HLSPlaybackSelection {
5367
let hasSelectionHandler = await CacheRuntime.shared.hasHLSSelectionHandler()
54-
guard hasSelectionHandler || !playlist.renditions.isEmpty || playlist.hasMixedVideoAndAudioOnlyVariants else {
55-
return []
56-
}
5768

58-
if !hasSelectionHandler, playlist.renditions.isEmpty, playlist.hasMixedVideoAndAudioOnlyVariants {
59-
return Set(playlist.variantStreams.filter(\.hasVideoTrackSignal).map(\.url))
69+
guard hasSelectionHandler else {
70+
if playlist.hasMixedVideoAndAudioOnlyVariants {
71+
return HLSPlaybackSelection(
72+
variantURLs: Set(playlist.variantStreams.filter(\.hasVideoTrackSignal).map(\.url)),
73+
renditionURLs: [],
74+
filtersVariants: true,
75+
filtersRenditions: false
76+
)
77+
}
78+
return .unrestricted
6079
}
6180

62-
return await selectedResources(in: playlist, originalURL: originalURL, currentURL: currentURL).urls
81+
let selected = await selectedResources(in: playlist, originalURL: originalURL, currentURL: currentURL)
82+
83+
return HLSPlaybackSelection(
84+
variantURLs: Set([selected.variant?.url].compactMap { $0 }),
85+
renditionURLs: Set(selected.renditions.compactMap(\.url)),
86+
filtersVariants: true,
87+
filtersRenditions: true
88+
)
6389
}
6490

6591
private func selectedRendition(

Tests/HTTPMediaCacheTests/HLSPlaylistRewriterTests.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,31 @@ final class HLSPlaylistRewriterTests: XCTestCase {
121121
XCTAssertFalse(output.contains("iframe"))
122122
}
123123

124+
func testProxyPlaybackRewritePreservesVariantsWhenRenditionsExistWithoutSelectionHandlers() async throws {
125+
let codec = ProxyURLCodec(port: 8123)
126+
let rewriter = HLSPlaylistRewriter(codec: codec)
127+
let playlist = """
128+
#EXTM3U
129+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aac",NAME="AAC",DEFAULT=YES,URI="audio/aac/index.m3u8"
130+
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="ec3",NAME="Atmos",DEFAULT=YES,URI="audio/ec3/index.m3u8"
131+
#EXT-X-STREAM-INF:BANDWIDTH=1000000,VIDEO-RANGE=SDR,CODECS="avc1.64001f,mp4a.40.2",AUDIO="aac"
132+
sdr/index.m3u8
133+
#EXT-X-STREAM-INF:BANDWIDTH=30000000,VIDEO-RANGE=PQ,CODECS="dvh1.05.06,ec-3",AUDIO="ec3"
134+
dolby/index.m3u8
135+
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=300000,URI="iframe/index.m3u8"
136+
"""
137+
let base = try XCTUnwrap(URL(string: "https://example.com/master.m3u8"))
138+
139+
let output = try await rewriter.rewriteForProxyPlayback(playlist: playlist, baseURL: base)
140+
141+
XCTAssertEqual(output.components(separatedBy: "#EXT-X-STREAM-INF").count - 1, 2)
142+
XCTAssertTrue(output.contains("sdr%2Findex%2Em3u8"))
143+
XCTAssertTrue(output.contains("dolby%2Findex%2Em3u8"))
144+
XCTAssertTrue(output.contains("audio%2Faac%2Findex%2Em3u8"))
145+
XCTAssertTrue(output.contains("audio%2Fec3%2Findex%2Em3u8"))
146+
XCTAssertFalse(output.contains("iframe"))
147+
}
148+
124149
func testProxyPlaybackRewriteSelectsVideoVariantWhenMasterAlsoContainsAudioOnlyVariants() async throws {
125150
let codec = ProxyURLCodec(port: 8123)
126151
let rewriter = HLSPlaylistRewriter(codec: codec)
@@ -166,7 +191,13 @@ final class HLSPlaylistRewriterTests: XCTestCase {
166191
XCTAssertFalse(output.contains("iframe"))
167192
}
168193

169-
func testProxyPlaybackRewriteSelectsDefaultVariantWhenRenditionsExist() async throws {
194+
func testProxyPlaybackRewriteSelectsDefaultVariantWhenSelectionHandlersExist() async throws {
195+
await HTTPMediaCache.setHLSVariantStreamSelectionHandler { streams, _, _ in
196+
streams.first { $0.url.absoluteString.contains("/sdr/") }
197+
}
198+
await HTTPMediaCache.setHLSRenditionSelectionHandler { _, renditions, _, _ in
199+
renditions.first { $0.url?.absoluteString.contains("/aac/") == true }
200+
}
170201
let codec = ProxyURLCodec(port: 8123)
171202
let rewriter = HLSPlaylistRewriter(codec: codec)
172203
let playlist = """

0 commit comments

Comments
 (0)