feat: select Miruro playback and caption providers

This commit is contained in:
2026-10-22 18:45:14 +02:00
parent 4576080955
commit 80d9e705e5
10 changed files with 561 additions and 76 deletions
+94 -10
View File
@@ -40,7 +40,17 @@ struct StreamResolverChecks {
throw Failure(message: "Accepted \(message)")
}
static func project(_ json: String) throws -> ResolvedSource {
try MiruroResolver.decode(Data(json.utf8), mediaID: mediaID, episode: 1)
try MiruroResolver.decode(Data(json.utf8), mediaID: mediaID, episode: 1).select().source
}
static func catalog(_ document: [String: Any]) throws -> MiruroSources {
try MiruroResolver.decode(JSONSerialization.data(withJSONObject: document), mediaID: mediaID, episode: 1)
}
static func provider(_ name: String, captions: Bool = true) -> [String: Any] {
["provider": name,
"servers": [["headers": ["Referer": "https://\(name).example/"],
"streams": [["url": "https://\(name).example/master.m3u8", "format": "hls"]]]],
"subtitles": captions ? [["file": "https://\(name).example/english.vtt", "format": "vtt",
"language": "en", "label": "English", "default": true]] : []]
}
static func checks() async throws {
@@ -84,6 +94,79 @@ struct StreamResolverChecks {
try require(source.subtitles.count == 2 && source.subtitles[0].isDefault, "only external VTT retained with default")
try require(source.subtitles[0].url.hasSuffix(".jpeg") && source.subtitles[1].label == "Spanish (Latin America)", "subtitle format and labels independent of file extension")
try require(source.subtitles.allSatisfy { $0.headers == source.headers }, "verified Sun subtitle headers retained")
let defaults = try MiruroResolver.decode(Data(fixture.utf8), mediaID: mediaID, episode: 1)
let defaultSelection = try defaults.select()
try require(defaults.videoProviders == ["sun"] && defaults.captionProviders == ["sun"], "menus contain usable native providers")
try require(defaultSelection.videoProvider == "sun" && defaultSelection.captionProvider == "sun", "existing Sun defaults")
let native: [String: Any] = ["player": "native", "proxy": false]
var document: [String: Any] = [
"episodeNumber": 1, "providerOrder": ["cloud", "river", "sun", "cloud", "missing"],
"config": ["sun": native, "river": native, "cloud": native],
"tracks": [["track": "ssub", "providers": [provider("river"), provider("sun"), provider("cloud")]]]
]
let choices = try catalog(document)
try require(choices.videoProviders == ["cloud", "river", "sun"] &&
choices.captionProviders == ["cloud", "river", "sun"], "runtime names follow deduplicated provider order")
let automatic = try choices.select()
try require(automatic.videoProvider == "sun" && automatic.captionProvider == "sun", "Auto prefers usable Sun")
let independent = try choices.select(videoProvider: "river", captionProvider: "cloud")
try require(independent.videoProvider == "river" && independent.captionProvider == "cloud", "explicit independent choices")
try require(independent.source.url == "https://river.example/master.m3u8" &&
independent.source.headers == "Referer: https://river.example/\r\n", "selected host retains its URL and headers")
try require(independent.source.subtitles.first?.url == "https://cloud.example/english.vtt" &&
independent.source.subtitles.first?.headers == "Referer: https://cloud.example/\r\n", "captions retain their own provider headers")
let sameHost = try choices.select(videoProvider: "river")
try require(sameHost.captionProvider == "river", "Auto captions prefer chosen video provider")
let absent = try choices.select(videoProvider: "absent", captionProvider: "unsupported")
let empty = try choices.select(videoProvider: "", captionProvider: "")
try require(absent.videoProvider == "sun" && absent.captionProvider == "sun" &&
empty.videoProvider == "sun" && empty.captionProvider == "sun", "absent and empty preferences fall back to Auto")
document["tracks"] = [["track": "ssub", "providers": [provider("river"), provider("cloud")]]]
let withoutSun = try catalog(document).select()
try require(withoutSun.videoProvider == "cloud" && withoutSun.captionProvider == "cloud", "provider order applies without Sun")
document["providerOrder"] = []
let returnedOrder = try catalog(document).select()
try require(returnedOrder.videoProvider == "river", "returned order is deterministic when no provider order applies")
document["tracks"] = [["track": "ssub", "providers": [provider("river", captions: false), provider("cloud")]]]
let otherCaptions = try catalog(document).select()
try require(otherCaptions.videoProvider == "river" && otherCaptions.captionProvider == "cloud", "Auto captions fall back independently")
document["tracks"] = [["track": "sub", "providers": [provider("river", captions: false)]]]
let noCaptions = try catalog(document)
let bare = try noCaptions.select(captionProvider: "cloud")
try require(noCaptions.captionProviders.isEmpty && bare.captionProvider == nil && bare.source.subtitles.isEmpty, "Japanese sub without captions remains playable")
var soft = provider("river")
soft["servers"] = [["streams": [["url": "https://soft.example/master.m3u8", "format": "m3u8"]]]]
document["tracks"] = [["track": "sub", "providers": [provider("river")]],
["track": "ssub", "providers": [soft]],
["track": "dub", "providers": [provider("cloud")]]]
let preferredVariant = try catalog(document)
let preferredSelection = try preferredVariant.select()
try require(preferredVariant.videoProviders == ["river"] &&
preferredSelection.source.url == "https://soft.example/master.m3u8", "ssub wins per provider without dub fallback")
var mixedConfigs: [String: Any] = ["river": native, "proxy": ["player": "native", "proxy": ["segments": true]],
"iframe": ["player": "iframe", "proxy": false], "broken": ["player": 42], "bad name": native]
mixedConfigs["rewrite"] = ["player": "native", "proxy": false, "hls": ["query": ["enabled": true]]]
document["config"] = mixedConfigs
var resilient = provider("river")
resilient["servers"] = [
["streams": [["url": "https://embed.example/master.m3u8", "format": "hls"]], "embed": ["url": "https://embed.example/"]],
["streams": [42, ["url": "file:///secret", "format": "hls"],
["url": "https://river.example/master.m3u8", "format": "application/vnd.apple.mpegurl"]] as [Any],
"headers": ["Referer": "https://river.example/"]]] as [[String: Any]]
document["tracks"] = [["track": "ssub", "providers": [
42, ["provider": "broken", "servers": "invalid"], provider("proxy"), provider("iframe"),
provider("rewrite"), provider("bad name"), resilient] as [Any]]]
let resilientSources = try catalog(document)
let resilientChoice = try resilientSources.select(videoProvider: "proxy", captionProvider: "iframe")
try require(resilientSources.videoProviders == ["river"] && resilientSources.captionProviders == ["river"] &&
resilientChoice.source.url == "https://river.example/master.m3u8", "malformed and unsupported entries do not poison usable alternatives")
for (old, replacement) in [
("https://vault.example/english.jpeg", "https://user:secret@vault.example/english.jpeg"),
("\"default\": false", "\"default\": true")
] {
let safeVideo = try project(fixture.replacingOccurrences(of: old, with: replacement))
try require(safeVideo.url == source.url && safeVideo.subtitles.isEmpty, "unsafe caption sets are excluded without losing safe video")
}
let renewed = try project(fixture.replacingOccurrences(of: "token=secret", with: "token=renewed"))
try require(renewed.identity == source.identity && renewed.url != source.url, "token rotation does not change stable identity")
for (old, replacement) in [
@@ -92,14 +175,13 @@ struct StreamResolverChecks {
("\"enabled\": false", "\"enabled\": true"),
("\"player\": \"native\"", "\"player\": \"iframe\""),
("\"track\": \"ssub\"", "\"track\": \"dub\""),
("\"provider\": \"sun\"", "\"provider\": \"bee\""),
("\"proxy\": false", "\"proxy\": {\"segments\": true}"),
("\"format\": \"hls\"", "\"format\": \"mp4\""),
("\"embed\": null", "\"embed\": {\"url\": \"https://embed.example/secret\"}"),
("https://vault.example/master.m3u8?token=secret", "file:///secret"),
("https://vault.example/english.jpeg", "https://user:secret@vault.example/english.jpeg"),
("\"embed\": null", "\"embed\": false"),
("https://ultracloud.cc/\"", "https://ultracloud.cc/\\r\\nX-Injected: secret\""),
("\"Origin\":", "\"origin\": \"secret\", \"Origin\":"),
("\"default\": false", "\"default\": true")
("\"Origin\":", "\"origin\": \"secret\", \"Origin\":")
] {
try reject("unsafe/unsupported source projection") {
_ = try project(fixture.replacingOccurrences(of: old, with: replacement))
@@ -122,7 +204,7 @@ struct StreamResolverChecks {
_ = try await cancelled.value
throw Failure(message: "Cancelled resolution was accepted")
} catch is CancellationError {}
print("PASS: streaming URL security, Sun projection, shared validation, bounded responses and cancellation")
print("PASS: streaming URL security, runtime native providers, independent captions, bounded responses and cancellation")
}
static func main() async {
@@ -132,12 +214,14 @@ struct StreamResolverChecks {
guard case .miruro(let id, let episode) = try StreamURL.parse(args[1]) else {
throw Failure(message: "--live requires a Miruro episode URL")
}
let source = try await MiruroResolver.resolve(mediaID: id, episode: episode)
print("PASS: live \(source.identity); \(source.subtitles.count) VTT tracks")
let sources = try await MiruroResolver.resolve(mediaID: id, episode: episode)
let selection = try sources.select()
print("PASS: live video=\(selection.videoProvider); captions=\(selection.captionProvider ?? "none"); \(sources.videoProviders.count) hosts; \(sources.captionProviders.count) caption providers; \(selection.source.subtitles.count) VTT tracks")
} else if args.count == 2, args[0] == "--fixture" {
let data = try Data(contentsOf: URL(fileURLWithPath: args[1]))
let source = try MiruroResolver.decode(data, mediaID: mediaID, episode: 1)
print("PASS: fixture \(source.identity); \(source.subtitles.count) VTT tracks")
let sources = try MiruroResolver.decode(data, mediaID: mediaID, episode: 1)
let selection = try sources.select()
print("PASS: fixture video=\(selection.videoProvider); captions=\(selection.captionProvider ?? "none"); \(sources.videoProviders.count) hosts; \(sources.captionProviders.count) caption providers; \(selection.source.subtitles.count) VTT tracks")
} else if args.isEmpty {
try await checks()
} else {