Turn document created May 25, 2026 at 01:02 EDT

Native Player Controls, Captions, and Close Flow

Dreamio now presents a fuller VLC-backed native playback surface: transport controls, scrubbing, caption selection and delay controls, best-effort external subtitle discovery, and cleanup that returns Stremio Web toward episode or stream selection after native playback closes.

Summary

Implemented native player controls on top of MobileVLCKit and expanded the web bridge so subtitle metadata discovered in Stremio Web can be carried into VLC. Closing the native player now stops the underlying web media and attempts to escape Stremio Web's stuck preparing or buffering player without forcing a full reload unless cleanup fails.

Changes Made

Context

The previous native player surface was intentionally minimal: it opened VLC, showed a spinner, and exposed only a close button. That made unsupported containers playable, but it left users without ordinary playback affordances and could leave Stremio Web behind the modal in a preparing or buffering state.

This pass keeps the architecture pragmatic: Stremio Web remains the source of stream selection and metadata, while VLC handles native playback for containers WebKit cannot reliably play.

Important Implementation Details

Relevant Diff Snippets

Rendered with @pierre/diffs/ssr using preloadPatchDiff, following the repository turn-document requirement to use diffs.com rendering for diff snippets.

Dreamio/NativePlaybackBackend.swift
+12
3 unmodified lines
4
5
6
7
8
9
10
11
3 unmodified lines
var view: UIView { get }
var onReady: (() -> Void)? { get set }
var onFailure: ((Error) -> Void)? { get set }
func prepare(in viewController: UIViewController)
func play(request: NativePlaybackRequest)
func stop()
}
3 unmodified lines
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3 unmodified lines
var view: UIView { get }
var onReady: (() -> Void)? { get set }
var onFailure: ((Error) -> Void)? { get set }
var onStateChange: (() -> Void)? { get set }
var onSubtitleTracksChange: (() -> Void)? { get set }
var isPlaying: Bool { get }
var isSeekable: Bool { get }
var duration: TimeInterval { get }
var currentTime: TimeInterval { get }
var subtitleTracks: [SubtitleTrack] { get }
var selectedSubtitleTrackID: Int32 { get }
func prepare(in viewController: UIViewController)
func play(request: NativePlaybackRequest)
func togglePlayPause()
func seek(to position: Float)
func selectSubtitleTrack(id: Int32)
func adjustSubtitleDelay(by seconds: TimeInterval)
func stop()
}
Dreamio/DreamioWebViewController.swift
-1+13
112 unmodified lines
111
112
113
114
115
116
112 unmodified lines
pageUrl: window.location.href,
tagName: element && element.tagName ? element.tagName : "",
currentSrc: element && element.currentSrc ? element.currentSrc : ""
});
} catch (_) {}
};
112 unmodified lines
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
112 unmodified lines
pageUrl: window.location.href,
tagName: element && element.tagName ? element.tagName : "",
currentSrc: element && element.currentSrc ? element.currentSrc : "",
subtitles: subtitleCandidates.slice(-20)
});
} catch (_) {}
};
const originalFetch = window.fetch;
if (originalFetch) {
window.fetch = async (...args) => {
const response = await originalFetch(...args);
try {
response.clone().text().then(inspectSubtitlePayload).catch(() => {});
} catch (_) {}
return response;
};
}
Dreamio/NativePlayerViewController.swift
+8
269 unmodified lines
269 unmodified lines
269 unmodified lines
270
271
272
273
274
275
276
277
269 unmodified lines
SubtitleOptionMapper.options(from: backend.subtitleTracks).forEach { track in
let prefix = track.id == backend.selectedSubtitleTrackID ? "Selected: " : ""
alert.addAction(UIAlertAction(title: "\(prefix)\(track.name)", style: .default) { [weak self] _ in
self?.backend.selectSubtitleTrack(id: track.id)
})
}
alert.addAction(UIAlertAction(title: "Delay -0.5s", style: .default))
alert.addAction(UIAlertAction(title: "Delay +0.5s", style: .default))

Expected Impact for End-Users

Users should be able to control native VLC playback without leaving the app: pause, resume, jump, scrub when possible, switch captions, turn captions off, and make small caption timing corrections. After closing native playback, Stremio Web should more reliably return to episode or stream selection rather than remaining on a stale preparing or buffering player.

Validation

Issues, Limitations, and Mitigations

Follow-up Work