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
- Extended
NativePlaybackBackendwith player state, transport controls, seeking, subtitle track selection, and subtitle delay APIs. - Added a native overlay in
NativePlayerViewControllerwith close, play/pause, 15-second jumps, scrubber, elapsed and remaining labels, captions, tap-to-reveal, and auto-hide while playing. - Implemented MobileVLCKit-backed state reads and controls in
VLCNativePlaybackBackend, including subtitle track mapping and remote subtitle attachment. - Added subtitle candidate parsing for Stremio/OpenSubtitles-like payloads and pure helper tests for time labels and caption option mapping.
- Observed JavaScript
fetchandXMLHttpRequestresponses in Stremio Web to collect subtitle-like URLs before native playback opens. - Added a native dismiss cleanup script that pauses/removes in-page media, clicks visible close/back controls, and falls back to web history when the player state appears stuck.
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
- Subtitle discovery is best effort. The injected bridge watches web responses for URLs that look like subtitle assets or OpenSubtitles links, then includes up to the latest 20 candidates in the native stream candidate message.
- VLC receives remote subtitles through
addPlaybackSlave(_:type:.subtitle,enforce:). Embedded tracks are exposed fromvideoSubTitlesNamesandvideoSubTitlesIndexes. - The captions sheet always includes an explicit
Offoption, plus simple delay controls in half-second increments. - Seek and jump controls disable visually and functionally when VLC reports a non-seekable stream.
- The close flow avoids a full reload during normal cleanup so the user can return to the selection context whenever Stremio's UI cooperates.
Relevant Diff Snippets
Rendered with @pierre/diffs/ssr using preloadPatchDiff, following the repository turn-document requirement to use diffs.com rendering for diff snippets.
3 unmodified lines45678910113 unmodified linesvar 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 lines45678910111213141516171819202122233 unmodified linesvar 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()}
112 unmodified lines111112113114115116112 unmodified linespageUrl: window.location.href,tagName: element && element.tagName ? element.tagName : "",currentSrc: element && element.currentSrc ? element.currentSrc : ""});} catch (_) {}};112 unmodified lines113114115116117118119120121122123124125126127128129130112 unmodified linespageUrl: 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;};}
269 unmodified lines269 unmodified lines269 unmodified lines270271272273274275276277269 unmodified linesSubtitleOptionMapper.options(from: backend.subtitleTracks).forEach { track inlet prefix = track.id == backend.selectedSubtitleTrackID ? "Selected: " : ""alert.addAction(UIAlertAction(title: "\(prefix)\(track.name)", style: .default) { [weak self] _ inself?.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
- Passed pure Swift tests with
swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Tests/StreamResolverTests.swift -o /tmp/dreamio-stream-tests && /tmp/dreamio-stream-tests. - Passed simulator build with
xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -sdk iphonesimulator -configuration Debug build. - The Xcode build still reports the existing warning that the MobileVLCKit prepare script has no declared outputs.
- Ran
bd dolt push; Beads reported that no Dolt remote is configured, so issue data remains stored locally and in the committed Beads export. - Manual real-device playback and subtitle validation was not performed in this terminal session.
Issues, Limitations, and Mitigations
- External subtitle support depends on the hosted Stremio Web app exposing subtitle URLs in fetch/XHR responses before native playback starts. If not, VLC will still show embedded tracks.
- The close flow uses visible button heuristics because Stremio Web does not provide a native close API to Dreamio. It falls back to web history and only reloads if JavaScript cleanup errors.
- The captions sheet is intentionally basic for this pass. It exposes track selection and simple delay adjustments but not full subtitle styling.
Follow-up Work
- Validate embedded and external subtitles on a real device with representative Stremio and OpenSubtitles addons.
- Consider a richer caption settings panel if users need style controls or exact delay entry.
- Add a UI test harness or injectable mock backend for exercising native player overlay behavior without MobileVLCKit.