diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index 0b1068f..ba85e4d 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -20,3 +20,4 @@ {"id":"int-45781aa3","kind":"field_change","created_at":"2026-05-25T14:19:19.141163Z","actor":"dirtydishes","issue_id":"dreamio-c1m","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Added DEBUG-only logs for captions menu actions and VLC subtitle selection results."}} {"id":"int-6343b773","kind":"field_change","created_at":"2026-05-25T14:25:59.50764Z","actor":"dirtydishes","issue_id":"dreamio-bd9","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Stopped rebuilding the captions menu on every progress refresh and validated the build."}} {"id":"int-26b872a1","kind":"field_change","created_at":"2026-05-25T14:31:46.83464Z","actor":"dirtydishes","issue_id":"dreamio-ese","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Added subtitle-shaped fetch/XHR inspection diagnostics and validated the build."}} +{"id":"int-4e095d3f","kind":"field_change","created_at":"2026-05-25T14:38:21.968713Z","actor":"dirtydishes","issue_id":"dreamio-djc","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Auto-select the first discovered VLC subtitle track when playback is still disabled, while preserving manual caption choices."}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index c67315b..4b1a01e 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-djc","title":"Auto-select embedded VLC subtitle tracks","description":"VLC discovers embedded MKV subtitle tracks after playback starts, but Dreamio leaves subtitles disabled when no external candidates were provided. Add automatic selection for the first selectable VLC subtitle track while preserving manual caption choices.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T14:36:11Z","created_by":"dirtydishes","updated_at":"2026-05-25T14:38:22Z","started_at":"2026-05-25T14:36:17Z","closed_at":"2026-05-25T14:38:22Z","close_reason":"Auto-select the first discovered VLC subtitle track when playback is still disabled, while preserving manual caption choices.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-ese","title":"Discover Stremio external subtitle payloads","description":"Extend and instrument the injected web subtitle discovery path so Stremio/OpenSubtitles addon responses can be captured when native playback only sees embedded VLC subtitle tracks.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T14:29:57Z","created_by":"dirtydishes","updated_at":"2026-05-25T14:31:47Z","started_at":"2026-05-25T14:30:03Z","closed_at":"2026-05-25T14:31:47Z","close_reason":"Added subtitle-shaped fetch/XHR inspection diagnostics and validated the build.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-bd9","title":"Stabilize captions menu refresh","description":"Stop rebuilding the captions UIMenu on every playback progress refresh so embedded subtitle actions can remain stable long enough to fire, while keeping DEBUG logs for menu state and selection.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T14:24:45Z","created_by":"dirtydishes","updated_at":"2026-05-25T14:25:59Z","started_at":"2026-05-25T14:24:50Z","closed_at":"2026-05-25T14:25:59Z","close_reason":"Stopped rebuilding the captions menu on every progress refresh and validated the build.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"dreamio-h5q","title":"Resolve OpenSubtitles API subtitle URLs before VLC attachment","description":"OpenSubtitles V3 can surface API/download endpoints that are not subtitle files themselves. Dreamio should resolve those endpoints to playable subtitle file URLs before handing them to VLC so Stremio does not show failed subtitle loads after native playback opens.","status":"closed","priority":1,"issue_type":"bug","assignee":"dirtydishes","owner":"dishes@dpdrm.com","created_at":"2026-05-25T13:47:17Z","created_by":"dirtydishes","updated_at":"2026-05-25T13:50:43Z","started_at":"2026-05-25T13:47:21Z","closed_at":"2026-05-25T13:50:43Z","close_reason":"Resolved OpenSubtitles V3 API-style subtitle download URLs to direct subtitle files before VLC attachment; added parser/resolver coverage and simulator build validation.","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/Dreamio/VLCNativePlaybackBackend.swift b/Dreamio/VLCNativePlaybackBackend.swift index b1fa064..209128d 100644 --- a/Dreamio/VLCNativePlaybackBackend.swift +++ b/Dreamio/VLCNativePlaybackBackend.swift @@ -23,6 +23,8 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend { private let mediaPlayer = VLCMediaPlayer() #endif private var attachedSubtitleURLs = Set() + private var didAutoSelectSubtitleTrack = false + private var didUserSelectSubtitleTrack = false override init() { super.init() @@ -41,6 +43,8 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend { func play(request: NativePlaybackRequest) { #if canImport(MobileVLCKit) attachedSubtitleURLs.removeAll() + didAutoSelectSubtitleTrack = false + didUserSelectSubtitleTrack = false let media = VLCMedia(url: request.playbackURL) let headerValue = request.headers .map { "\($0.key): \($0.value)" } @@ -100,6 +104,7 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend { func selectSubtitleTrack(id: Int32) { #if canImport(MobileVLCKit) + didUserSelectSubtitleTrack = true #if DEBUG logSubtitleTracks(reason: "before-select-\(id)") #endif @@ -239,6 +244,7 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend { return attachedCount } DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + self?.selectInitialSubtitleTrackIfNeeded(reason: "delayed-refresh") #if DEBUG self?.logSubtitleTracks(reason: "delayed-refresh") #endif @@ -254,6 +260,21 @@ final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend { print("[DreamioVLC] subtitle tracks reason=\(reason) names=\(names) indexes=\(indexes.map { $0.int32Value }) selected=\(mediaPlayer.currentVideoSubTitleIndex)") } #endif + + private func selectInitialSubtitleTrackIfNeeded(reason: String) { + guard !didUserSelectSubtitleTrack, + !didAutoSelectSubtitleTrack, + mediaPlayer.currentVideoSubTitleIndex < 0, + let track = subtitleTracks.first(where: { $0.id >= 0 }) else { + return + } + + didAutoSelectSubtitleTrack = true +#if DEBUG + print("[DreamioVLC] auto-select subtitle id=\(track.id) name=\(track.name) reason=\(reason)") +#endif + mediaPlayer.currentVideoSubTitleIndex = track.id + } #endif } @@ -272,6 +293,7 @@ extension VLCNativePlaybackBackend: VLCMediaPlayerDelegate { case .paused, .stopped, .ended: onStateChange?() case .esAdded: + selectInitialSubtitleTrackIfNeeded(reason: "esAdded") #if DEBUG logSubtitleTracks(reason: "esAdded") #endif diff --git a/docs/turns/2026-05-25-auto-select-vlc-subtitle-tracks.html b/docs/turns/2026-05-25-auto-select-vlc-subtitle-tracks.html new file mode 100644 index 0000000..57bcb80 --- /dev/null +++ b/docs/turns/2026-05-25-auto-select-vlc-subtitle-tracks.html @@ -0,0 +1,233 @@ + + + + + + Auto-select VLC subtitle tracks + + + +
+
+

Auto-select VLC subtitle tracks

+

Turn document for dreamio-djc, created May 25, 2026.

+
+

Dreamio now asks MobileVLCKit to enable the first real subtitle track when VLC discovers embedded MKV subtitles and playback is still on “Disable.” This targets the log pattern where VLC sees English (SDH) but leaves selected=-1.

+
+
+ +
+

Summary

+

Fixed the remaining native playback subtitle issue for streams that provide no external subtitle candidates but do contain embedded subtitle tracks. VLC can discover those tracks after playback starts; Dreamio now auto-selects the first selectable one once it appears.

+
+ +
+

Changes Made

+
    +
  • Added per-playback state to track whether Dreamio has already auto-selected subtitles.
  • +
  • Added per-playback state to detect when the user manually chooses a caption option, including disabling captions.
  • +
  • Auto-selects the first subtitle track with a non-negative VLC track id when VLC reports .esAdded or after an external subtitle attach refresh.
  • +
  • Added a debug log line for automatic subtitle selection so future device logs should show the selected track id and reason.
  • +
+
+ +
+

Context

+

The reported logs showed subtitle candidates=0 from the native player, followed by VLC reporting subtitle tracks named Disable and English (SDH) - [English] with selected track -1. That means stream parsing and VLC track discovery were both working; the remaining gap was that Dreamio never changed VLC away from the disabled subtitle track.

+
+ +
+

Important Implementation Details

+
    +
  • The selection guard only runs when currentVideoSubTitleIndex is below zero, so it will not replace an already active subtitle track.
  • +
  • The auto-selection runs only once per playback item.
  • +
  • User interaction wins: once selectSubtitleTrack(id:) is called from the captions menu, Dreamio stops automatic caption selection for that playback item.
  • +
  • The state resets in play(request:), alongside the existing attached subtitle URL reset.
  • +
+
+ +
+

Relevant Diff Snippets

+

Rendered with @pierre/diffs/ssr.

+
Dreamio/VLCNativePlaybackBackend.swift
+22
22 unmodified lines
23
24
25
26
27
28
12 unmodified lines
41
42
43
44
45
46
53 unmodified lines
100
101
102
103
104
105
133 unmodified lines
239
240
241
242
243
244
9 unmodified lines
254
255
256
257
258
259
12 unmodified lines
272
273
274
275
276
277
22 unmodified lines
private let mediaPlayer = VLCMediaPlayer()
#endif
private var attachedSubtitleURLs = Set<URL>()
+
override init() {
super.init()
12 unmodified lines
func play(request: NativePlaybackRequest) {
#if canImport(MobileVLCKit)
attachedSubtitleURLs.removeAll()
let media = VLCMedia(url: request.playbackURL)
let headerValue = request.headers
.map { "\($0.key): \($0.value)" }
53 unmodified lines
+
func selectSubtitleTrack(id: Int32) {
#if canImport(MobileVLCKit)
#if DEBUG
logSubtitleTracks(reason: "before-select-\(id)")
#endif
133 unmodified lines
return attachedCount
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
#if DEBUG
self?.logSubtitleTracks(reason: "delayed-refresh")
#endif
9 unmodified lines
print("[DreamioVLC] subtitle tracks reason=\(reason) names=\(names) indexes=\(indexes.map { $0.int32Value }) selected=\(mediaPlayer.currentVideoSubTitleIndex)")
}
#endif
#endif
}
+
12 unmodified lines
case .paused, .stopped, .ended:
onStateChange?()
case .esAdded:
#if DEBUG
logSubtitleTracks(reason: "esAdded")
#endif
22 unmodified lines
23
24
25
26
27
28
29
30
12 unmodified lines
43
44
45
46
47
48
49
50
53 unmodified lines
104
105
106
107
108
109
110
133 unmodified lines
244
245
246
247
248
249
250
9 unmodified lines
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
12 unmodified lines
293
294
295
296
297
298
299
22 unmodified lines
private let mediaPlayer = VLCMediaPlayer()
#endif
private var attachedSubtitleURLs = Set<URL>()
private var didAutoSelectSubtitleTrack = false
private var didUserSelectSubtitleTrack = false
+
override init() {
super.init()
12 unmodified lines
func play(request: NativePlaybackRequest) {
#if canImport(MobileVLCKit)
attachedSubtitleURLs.removeAll()
didAutoSelectSubtitleTrack = false
didUserSelectSubtitleTrack = false
let media = VLCMedia(url: request.playbackURL)
let headerValue = request.headers
.map { "\($0.key): \($0.value)" }
53 unmodified lines
+
func selectSubtitleTrack(id: Int32) {
#if canImport(MobileVLCKit)
didUserSelectSubtitleTrack = true
#if DEBUG
logSubtitleTracks(reason: "before-select-\(id)")
#endif
133 unmodified lines
return attachedCount
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
self?.selectInitialSubtitleTrackIfNeeded(reason: "delayed-refresh")
#if DEBUG
self?.logSubtitleTracks(reason: "delayed-refresh")
#endif
9 unmodified lines
print("[DreamioVLC] subtitle tracks reason=\(reason) names=\(names) indexes=\(indexes.map { $0.int32Value }) selected=\(mediaPlayer.currentVideoSubTitleIndex)")
}
#endif
+
private func selectInitialSubtitleTrackIfNeeded(reason: String) {
guard !didUserSelectSubtitleTrack,
!didAutoSelectSubtitleTrack,
mediaPlayer.currentVideoSubTitleIndex < 0,
let track = subtitleTracks.first(where: { $0.id >= 0 }) else {
return
}
+
didAutoSelectSubtitleTrack = true
#if DEBUG
print("[DreamioVLC] auto-select subtitle id=\(track.id) name=\(track.name) reason=\(reason)")
#endif
mediaPlayer.currentVideoSubTitleIndex = track.id
}
#endif
}
+
12 unmodified lines
case .paused, .stopped, .ended:
onStateChange?()
case .esAdded:
selectInitialSubtitleTrackIfNeeded(reason: "esAdded")
#if DEBUG
logSubtitleTracks(reason: "esAdded")
#endif
+
+ +
+

Expected Impact for End-Users

+

MKV streams with embedded subtitles should show captions automatically instead of requiring the user to open the captions menu and pick the embedded track manually. Users can still disable captions or switch tracks afterward.

+
+ +
+

Validation

+
    +
  • Ran xcodebuild -scheme Dreamio -project Dreamio.xcodeproj -destination 'generic/platform=iOS' build.
  • +
  • The build succeeded against MobileVLCKit.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • This was validated by build, not by replaying the exact Real-Debrid stream on device in this turn.
  • +
  • If a file has multiple embedded subtitle tracks, Dreamio chooses the first selectable track VLC exposes. The captions menu remains available for manual switching.
  • +
  • If a stream intentionally starts with subtitles disabled and the user never touches the captions menu, Dreamio will now enable the first discovered track by default.
  • +
+
+ +
+

Follow-up Work

+
    +
  • Test the same South Park MKV on device and confirm the logs include [DreamioVLC] auto-select subtitle followed by a non-negative selected subtitle id.
  • +
  • Consider a future setting for “auto-enable embedded subtitles” if users want control over the default behavior.
  • +
+
+
+ + \ No newline at end of file