diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl
index f943500..072f8e3 100644
--- a/.beads/interactions.jsonl
+++ b/.beads/interactions.jsonl
@@ -32,3 +32,4 @@
{"id":"int-fe1c7364","kind":"field_change","created_at":"2026-05-25T16:04:54.482803Z","actor":"dirtydishes","issue_id":"dreamio-656","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"fixed"}}
{"id":"int-f9deecdb","kind":"field_change","created_at":"2026-05-25T16:18:29.458162Z","actor":"dirtydishes","issue_id":"dreamio-urs","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed by rejecting OpenSubtitles manifest.json_N identifiers as playable subtitle URLs, promoting file_id values to API download URLs, and adding parser coverage for the live log shape."}}
{"id":"int-569ee372","kind":"field_change","created_at":"2026-05-25T16:22:50.024736Z","actor":"dirtydishes","issue_id":"dreamio-433","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Fixed by tightening OpenSubtitles subtitle URL filtering in the web bridge and Swift parser, plus adding regression coverage for logged artwork and addon endpoint false positives."}}
+{"id":"int-eca1f7f8","kind":"field_change","created_at":"2026-05-25T16:33:55.331041Z","actor":"dirtydishes","issue_id":"dreamio-9sp","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Accepted Stremio subtitle download URLs in the bridge, parser, resolver, and regression tests."}}
diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl
index 16c6888..179f5e5 100644
--- a/.beads/issues.jsonl
+++ b/.beads/issues.jsonl
@@ -1,4 +1,5 @@
{"_type":"issue","id":"dreamio-8cz","title":"fix stremio external subtitle loading regression","description":"After adding late subtitle forwarding for native playback, Stremio external subtitle loading is failing. Investigate the injected bridge and native subtitle forwarding path, then adjust behavior so Stremio can still load external subtitles while native playback receives late candidates.","status":"closed","priority":0,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T11:05:42Z","created_by":"dirtydishes","updated_at":"2026-05-25T11:07:35Z","started_at":"2026-05-25T11:05:55Z","closed_at":"2026-05-25T11:07:35Z","close_reason":"Hardened subtitle bridge network observers so non-text Stremio subtitle loads are not touched, and made parser traversal deterministic for metadata preservation.","dependency_count":0,"dependent_count":0,"comment_count":0}
+{"_type":"issue","id":"dreamio-9sp","title":"Accept Stremio subtitle download URLs","description":"Runtime logs show Stremio external subtitle tracks using subs5.strem.io /en/download URLs. The subtitle bridge and Swift parser currently reject those URLs because they do not have a subtitle file extension and are not on an OpenSubtitles host, so native playback receives zero external subtitle candidates.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T16:32:04Z","created_by":"dirtydishes","updated_at":"2026-05-25T16:33:55Z","started_at":"2026-05-25T16:32:10Z","closed_at":"2026-05-25T16:33:55Z","close_reason":"Accepted Stremio subtitle download URLs in the bridge, parser, resolver, and regression tests.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"dreamio-433","title":"Filter false OpenSubtitles subtitle candidates","description":"Dreamio is treating addon artwork and OpenSubtitles addon endpoints as external subtitle candidates, which causes the native player UI to show only embedded subtitles. Tighten subtitle URL detection in the web bridge and Swift parser, and add regression coverage for the logged false positives.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T16:20:47Z","created_by":"dirtydishes","updated_at":"2026-05-25T16:22:50Z","started_at":"2026-05-25T16:20:50Z","closed_at":"2026-05-25T16:22:50Z","close_reason":"Fixed by tightening OpenSubtitles subtitle URL filtering in the web bridge and Swift parser, plus adding regression coverage for logged artwork and addon endpoint false positives.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"dreamio-urs","title":"Fix OpenSubtitles manifest-style subtitle URLs","description":"OpenSubtitles subtitle candidates discovered from Stremio are being resolved as manifest.json_N URLs, producing 404s and leaving only embedded subtitles visible. Preserve and resolve real subtitle URLs so external subtitle tracks can attach in the native player.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T16:16:52Z","created_by":"dirtydishes","updated_at":"2026-05-25T16:18:29Z","started_at":"2026-05-25T16:16:57Z","closed_at":"2026-05-25T16:18:29Z","close_reason":"Fixed by rejecting OpenSubtitles manifest.json_N identifiers as playable subtitle URLs, promoting file_id values to API download URLs, and adding parser coverage for the live log shape.","dependency_count":0,"dependent_count":0,"comment_count":0}
{"_type":"issue","id":"dreamio-656","title":"Capture OpenSubtitles candidates from Stremio app-state messages","description":"OpenSubtitlesV3 appears loaded in Stremio before native playback launches, but Dreamio forwards zero external subtitle candidates. The likely failure is not native-player timing; it is that the injected WebKit bridge does not extract Stremio's loaded subtitle metadata/state into URL candidates before opening VLC.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T16:00:09Z","created_by":"dirtydishes","updated_at":"2026-05-25T16:04:54Z","started_at":"2026-05-25T16:00:18Z","closed_at":"2026-05-25T16:04:54Z","close_reason":"fixed","dependency_count":0,"dependent_count":0,"comment_count":0}
diff --git a/Dreamio/DreamioWebViewController.swift b/Dreamio/DreamioWebViewController.swift
index c45e1d5..7ba97c4 100644
--- a/Dreamio/DreamioWebViewController.swift
+++ b/Dreamio/DreamioWebViewController.swift
@@ -169,12 +169,25 @@ final class DreamioWebViewController: UIViewController {
}
};
+ const isStremioSubtitleDownloadURL = (url) => {
+ try {
+ const parsed = new URL(url, window.location.href);
+ const host = parsed.hostname.toLowerCase();
+ const path = parsed.pathname.toLowerCase();
+ return host === "strem.io" || host.endsWith(".strem.io")
+ ? /\/[a-z]{2,3}\/download(?:\/|$)/i.test(path) || /\/download(?:\/|$)/i.test(path)
+ : false;
+ } catch (_) {
+ return false;
+ }
+ };
+
const isSubtitleURL = (url) => {
if (!url || isOpenSubtitlesManifestID(url)) {
return false;
}
return !isProbablyNonSubtitleAssetURL(url)
- && (isDirectSubtitleFileURL(url) || isOpenSubtitlesDownloadURL(url));
+ && (isDirectSubtitleFileURL(url) || isOpenSubtitlesDownloadURL(url) || isStremioSubtitleDownloadURL(url));
};
const findResolverURL = () => {
diff --git a/Dreamio/StreamCandidate.swift b/Dreamio/StreamCandidate.swift
index e735f88..b2345a4 100644
--- a/Dreamio/StreamCandidate.swift
+++ b/Dreamio/StreamCandidate.swift
@@ -256,6 +256,7 @@ enum SubtitleCandidateParser {
}
guard isDirectSubtitleFile(url)
|| isOpenSubtitlesDownloadURL(url)
+ || isStremioSubtitleDownloadURL(url)
else {
return nil
}
@@ -282,6 +283,18 @@ enum SubtitleCandidateParser {
|| path.range(of: #"(^|/)subtitles?(/|$)"#, options: .regularExpression) != nil
}
+ private static func isStremioSubtitleDownloadURL(_ url: URL) -> Bool {
+ guard let host = url.host?.lowercased(),
+ host == "strem.io" || host.hasSuffix(".strem.io")
+ else {
+ return false
+ }
+
+ let path = url.path.lowercased()
+ return path.range(of: #"^/[a-z]{2,3}/download(/|$)"#, options: .regularExpression) != nil
+ || path.range(of: #"(^|/)download(/|$)"#, options: .regularExpression) != nil
+ }
+
private static func isOpenSubtitlesManifestIdentifier(_ url: URL) -> Bool {
guard url.host?.localizedCaseInsensitiveContains("opensubtitles") == true else {
return false
diff --git a/Dreamio/StreamResolver.swift b/Dreamio/StreamResolver.swift
index 6aa7359..ffba9fd 100644
--- a/Dreamio/StreamResolver.swift
+++ b/Dreamio/StreamResolver.swift
@@ -131,6 +131,7 @@ final class SubtitleResolver: SubtitleResolving {
let lowercased = url.absoluteString.lowercased()
return ["srt", "vtt", "ass", "ssa", "sub"].contains(url.pathExtension.lowercased())
|| [".srt?", ".vtt?", ".ass?", ".ssa?", ".sub?", ".srt&", ".vtt&", ".ass&", ".ssa&", ".sub&"].contains(where: lowercased.contains)
+ || isStremioSubtitleDownloadURL(url)
}
private static func shouldResolve(_ url: URL) -> Bool {
@@ -138,6 +139,19 @@ final class SubtitleResolver: SubtitleResolving {
return lowercased.contains("opensubtitles")
|| lowercased.contains("/subtitle")
|| lowercased.contains("subtitle")
+ || isStremioSubtitleDownloadURL(url)
+ }
+
+ private static func isStremioSubtitleDownloadURL(_ url: URL) -> Bool {
+ guard let host = url.host?.lowercased(),
+ host == "strem.io" || host.hasSuffix(".strem.io")
+ else {
+ return false
+ }
+
+ let path = url.path.lowercased()
+ return path.range(of: #"^/[a-z]{2,3}/download(/|$)"#, options: .regularExpression) != nil
+ || path.range(of: #"(^|/)download(/|$)"#, options: .regularExpression) != nil
}
private static func logRejected(_ candidate: SubtitleCandidate, responseURL: URL?, data: Data) -> SubtitleCandidate? {
diff --git a/Tests/StreamResolverTests.swift b/Tests/StreamResolverTests.swift
index 39854a8..8fc7c48 100644
--- a/Tests/StreamResolverTests.swift
+++ b/Tests/StreamResolverTests.swift
@@ -13,6 +13,7 @@ struct StreamResolverTests {
testOpenSubtitlesNestedAttributesFilesParsing()
testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles()
testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored()
+ testStremioSubtitleDownloadURLParsing()
testOpenSubtitlesV3DownloadResponseResolution()
testOpenSubtitlesNestedDownloadResponseResolution()
await testSubtitleResolverDownloadJSONReturningLink()
@@ -240,6 +241,30 @@ struct StreamResolverTests {
assertEqual(candidates[0].label, "English")
}
+ private static func testStremioSubtitleDownloadURLParsing() {
+ let payload: [String: Any] = [
+ "subtitles": [
+ [
+ "label": "English",
+ "lang": "eng",
+ "url": "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
+ ],
+ [
+ "label": "Not a subtitle",
+ "url": "https://www.strem.io/images/addons/opensubtitles-logo.png"
+ ]
+ ]
+ ]
+
+ let candidates = SubtitleCandidateParser.candidates(in: payload)
+
+ assertEqual(candidates.count, 1)
+ assertEqual(candidates[0].url.absoluteString, "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941")
+ assertEqual(candidates[0].label, "English")
+ assertEqual(candidates[0].language, "eng")
+ assert(SubtitleResolver.isDirectSubtitleFile(candidates[0].url), "Expected Stremio subtitle downloads to be attachable without another resolver hop")
+ }
+
private static func testOpenSubtitlesV3DownloadResponseResolution() {
let payload = """
{
diff --git a/docs/turns/2026-05-25-accept-stremio-subtitle-download-urls.html b/docs/turns/2026-05-25-accept-stremio-subtitle-download-urls.html
new file mode 100644
index 0000000..378cacd
--- /dev/null
+++ b/docs/turns/2026-05-25-accept-stremio-subtitle-download-urls.html
@@ -0,0 +1,472 @@
+
+
+
+
+
+ Accept Stremio Subtitle Download URLs
+
+
+
+
+
+ Dreamio turn document
+ Accept Stremio subtitle download URLs
+ External subtitles from Stremio can arrive as subs*.strem.io/en/download/... URLs with no subtitle file extension. Dreamio now recognizes that shape in the injected bridge, Swift parser, and native subtitle resolver so those tracks can reach VLC instead of disappearing before playback.
+
+ Date: 2026-05-25
+ Issue: dreamio-9sp
+ Scope: subtitles, native playback
+
+
+
+
+ Summary
+ The pasted logs showed Stremio reporting a failed external subtitle track at https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941, while Dreamio logged zero parsed subtitle candidates. The fix adds Stremio subtitle download URL recognition so those URLs are accepted as real external subtitle candidates.
+
+
+
+ Changes Made
+
+ Updated the injected WebKit subtitle bridge to treat strem.io and *.strem.io download paths as subtitle URLs.
+ Updated SubtitleCandidateParser to parse the same Stremio download URL shape from Stremio payloads.
+ Updated SubtitleResolver so Stremio subtitle download URLs are considered directly attachable instead of requiring a second resolver response.
+ Added a focused regression test for the exact subs5.strem.io/en/download/... form from the runtime log.
+
+
+
+
+ Context
+ Before this change, Dreamio only accepted direct subtitle file extensions or OpenSubtitles-looking download endpoints. Stremio’s web player can expose external subtitles through a host like subs5.strem.io, where the path identifies a subtitle download but the URL does not end in .srt, .vtt, or similar.
+ That mismatch explains the log pattern: bridge inspection saw likely payload objects, but Swift parsed zero usable candidates and the native player only saw embedded VLC subtitle tracks.
+
+
+
+ Important Implementation Details
+
+ The accepted Stremio pattern is intentionally narrow: hosts must be strem.io or end in .strem.io, and paths must include /download, with language-prefixed forms such as /en/download/... supported.
+ Image and addon endpoints such as www.strem.io/images/addons/opensubtitles-logo.png are still rejected by the non-subtitle extension filter.
+ Marking these URLs as direct subtitle files lets VLC receive the URL directly through addPlaybackSlave, which matches the way Stremio labels the track URL.
+
+
+
+
+ Relevant Diff Snippets
+ Rendered with @pierre/diffs/ssr using one patch per changed file.
+ Dreamio/DreamioWebViewController.swift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dreamio/DreamioWebViewController.swift
-1 +14
169
170
171
172
173
174
175
176
177
178
179
180
}
};
+
const isSubtitleURL = ( url ) => {
if (! url || isOpenSubtitlesManifestID ( url )) {
return false ;
}
return ! isProbablyNonSubtitleAssetURL ( url )
&& ( isDirectSubtitleFileURL ( url ) || isOpenSubtitlesDownloadURL ( url )) ;
} ;
+
const findResolverURL = () => {
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
}
};
+
const isStremioSubtitleDownloadURL = ( url ) => {
try {
const parsed = new URL ( url, window. location . href ) ;
const host = parsed. hostname . toLowerCase () ;
const path = parsed. pathname . toLowerCase () ;
return host === "strem.io" || host. endsWith ( ".strem.io" )
? / \/ [ a-z ] {2,3} \/ download (?: \/ | $ ) / i. test ( path ) || / \/ download (?: \/ | $ ) / i. test ( path )
: false ;
} catch ( _ ) {
return false ;
}
} ;
+
const isSubtitleURL = ( url ) => {
if (! url || isOpenSubtitlesManifestID ( url )) {
return false ;
}
return ! isProbablyNonSubtitleAssetURL ( url )
&& ( isDirectSubtitleFileURL ( url ) || isOpenSubtitlesDownloadURL ( url ) || isStremioSubtitleDownloadURL ( url )) ;
} ;
+
const findResolverURL = () => {
+
Dreamio/StreamCandidate.swift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dreamio/StreamCandidate.swift
+13
256
257
258
259
260
261
282
283
284
285
286
287
}
guard isDirectSubtitleFile ( url )
|| isOpenSubtitlesDownloadURL ( url )
else {
return nil
}
|| path.range ( of : #"(^|/)subtitles?(/|$)"# , options : . regularExpression ) != nil
}
+
private static func isOpenSubtitlesManifestIdentifier ( _ url : URL ) -> Bool {
guard url.host ? . localizedCaseInsensitiveContains ( "opensubtitles" ) == true else {
return false
256
257
258
259
260
261
262
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
}
guard isDirectSubtitleFile ( url )
|| isOpenSubtitlesDownloadURL ( url )
|| isStremioSubtitleDownloadURL ( url )
else {
return nil
}
|| path.range ( of : #"(^|/)subtitles?(/|$)"# , options : . regularExpression ) != nil
}
+
private static func isStremioSubtitleDownloadURL ( _ url : URL ) -> Bool {
guard let host = url.host ? . lowercased () ,
host == "strem.io" || host. hasSuffix ( ".strem.io" )
else {
return false
}
+
let path = url. path . lowercased ()
return path.range ( of : #"^/[a-z]{2,3}/download(/|$)"# , options : . regularExpression ) != nil
|| path.range ( of : #"(^|/)download(/|$)"# , options : . regularExpression ) != nil
}
+
private static func isOpenSubtitlesManifestIdentifier ( _ url : URL ) -> Bool {
guard url.host ? . localizedCaseInsensitiveContains ( "opensubtitles" ) == true else {
return false
+
Dreamio/StreamResolver.swift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dreamio/StreamResolver.swift
+14
131
132
133
134
135
136
138
139
140
141
142
143
let lowercased = url. absoluteString . lowercased ()
return [ "srt" , "vtt" , "ass" , "ssa" , "sub" ] . contains ( url. pathExtension .lowercased ())
|| [ ".srt?" , ".vtt?" , ".ass?" , ".ssa?" , ".sub?" , ".srt&" , ".vtt&" , ".ass&" , ".ssa&" , ".sub&" ]. contains ( where : lowercased. contains )
}
+
private static func shouldResolve ( _ url : URL ) -> Bool {
return lowercased. contains ( "opensubtitles" )
|| lowercased. contains ( "/subtitle" )
|| lowercased. contains ( "subtitle" )
}
+
private static func logRejected ( _ candidate : SubtitleCandidate, responseURL : URL ? , data : Data ) -> SubtitleCandidate ? {
131
132
133
134
135
136
137
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
let lowercased = url. absoluteString . lowercased ()
return [ "srt" , "vtt" , "ass" , "ssa" , "sub" ] . contains ( url. pathExtension .lowercased ())
|| [ ".srt?" , ".vtt?" , ".ass?" , ".ssa?" , ".sub?" , ".srt&" , ".vtt&" , ".ass&" , ".ssa&" , ".sub&" ]. contains ( where : lowercased. contains )
|| isStremioSubtitleDownloadURL ( url )
}
+
private static func shouldResolve ( _ url : URL ) -> Bool {
return lowercased. contains ( "opensubtitles" )
|| lowercased. contains ( "/subtitle" )
|| lowercased. contains ( "subtitle" )
|| isStremioSubtitleDownloadURL ( url )
}
+
private static func isStremioSubtitleDownloadURL ( _ url : URL ) -> Bool {
guard let host = url.host ? . lowercased () ,
host == "strem.io" || host. hasSuffix ( ".strem.io" )
else {
return false
}
+
let path = url. path . lowercased ()
return path.range ( of : #"^/[a-z]{2,3}/download(/|$)"# , options : . regularExpression ) != nil
|| path.range ( of : #"(^|/)download(/|$)"# , options : . regularExpression ) != nil
}
+
private static func logRejected ( _ candidate : SubtitleCandidate, responseURL : URL ? , data : Data ) -> SubtitleCandidate ? {
+
Tests/StreamResolverTests.swift
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tests/StreamResolverTests.swift
+25
13
14
15
16
17
18
240
241
242
243
244
245
testOpenSubtitlesNestedAttributesFilesParsing ()
testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles ()
testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored ()
testOpenSubtitlesV3DownloadResponseResolution ()
testOpenSubtitlesNestedDownloadResponseResolution ()
await testSubtitleResolverDownloadJSONReturningLink ()
assertEqual ( candidates [ 0 ] . label , "English" )
}
+
private static func testOpenSubtitlesV3DownloadResponseResolution () {
let payload = """
{
13
14
15
16
17
18
19
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
testOpenSubtitlesNestedAttributesFilesParsing ()
testOpenSubtitlesManifestIDsAreNotResolvedAsSubtitles ()
testOpenSubtitlesArtworkAndAddonEndpointsAreIgnored ()
testStremioSubtitleDownloadURLParsing ()
testOpenSubtitlesV3DownloadResponseResolution ()
testOpenSubtitlesNestedDownloadResponseResolution ()
await testSubtitleResolverDownloadJSONReturningLink ()
assertEqual ( candidates [ 0 ] . label , "English" )
}
+
private static func testStremioSubtitleDownloadURLParsing () {
let payload: [ String : Any ] = [
"subtitles" : [
[
"label" : "English" ,
"lang" : "eng" ,
"url" : "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941"
],
[
"label" : "Not a subtitle" ,
"url" : "https://www.strem.io/images/addons/opensubtitles-logo.png"
]
]
]
+
let candidates = SubtitleCandidateParser. candidates ( in : payload )
+
assertEqual ( candidates.count, 1 )
assertEqual ( candidates [ 0 ] .url. absoluteString , "https://subs5.strem.io/en/download/subencoding-stremio-utf8/src-api/file/1952341941" )
assertEqual ( candidates [ 0 ] . label , "English" )
assertEqual ( candidates [ 0 ] . language , "eng" )
assert ( SubtitleResolver.isDirectSubtitleFile ( candidates [ 0 ] .url ) , "Expected Stremio subtitle downloads to be attachable without another resolver hop" )
}
+
private static func testOpenSubtitlesV3DownloadResponseResolution () {
let payload = """
{
+
+
+
+
+ Expected Impact for End-Users
+ When Stremio exposes external subtitles through subs*.strem.io download URLs, Dreamio should now carry those subtitles into the native VLC player instead of showing only embedded subtitle tracks in the UI.
+
+
+
+ Validation
+
+ Passed: swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Tests/StreamResolverTests.swift -o /tmp/StreamResolverTests && /tmp/StreamResolverTests
+ Passed: DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build
+
+
+
+
+ Issues, Limitations, and Mitigations
+
+ This was validated against the URL shape visible in the logs, not by replaying the exact remote Stremio session inside the app.
+ If Stremio introduces a different subtitle CDN path that does not include /download, another narrow allow-list entry may be needed.
+ The existing debug logging should now show parsed candidates for this URL form, which makes the next runtime check straightforward.
+
+
+
+
+ Follow-up Work
+ No new follow-up issue was filed. The next useful check is runtime validation on the same episode: look for parsed=1, nonzero native subtitle candidates, and a [DreamioVLC] attach accepted subtitle=... line for the Stremio subtitle download URL.
+
+
+
+
\ No newline at end of file