Native Direct-Stream Playback for Debrid Files
Added a production WebKit-to-native playback path for direct-file debrid streams, with MKV, AVI, and WebM candidates routed into a new MobileVLCKit-backed player while ordinary HLS and MP4 web playback stay in Stremio Web.
Summary
This change keeps Stremio Web as Dreamio's main browsing and account UI, but intercepts direct-file stream URLs that iOS WebKit is likely to reject. Matching streams now open in a native fullscreen-style player with a close button and failure state.
Changes Made
- Added a production JavaScript bridge in
DreamioWebViewControllerthat observes video/source URLs, directsrcassignment,setAttribute("src"), mutations, andload(). - Added stream classification for Debridio, Torrentio, Real-Debrid, MKV, AVI, WebM, HLS, and MP4 candidates.
- Added redacted URL diagnostics that strip query strings, fragments, and long token-like path segments before DEBUG logging.
- Added
NativePlayerViewController,NativePlaybackBackend, and the first backend implementation,VLCNativePlaybackBackend. - Added a CocoaPods
PodfileforMobileVLCKitand ignored generatedPods/content. - Updated README workflow instructions to use
pod installandDreamio.xcworkspace.
Context
Dreamio started as a thin UIKit wrapper around hosted Stremio Web. That remains the product shape: login, browsing, addon setup, stream selection, popups, and compatible web media playback still belong to the web app. This work adds a native escape hatch only for direct-file streams that are likely to fail in iOS WebKit.
Important Implementation Details
- The bridge allows ordinary HLS and MP4 playback to continue in WebKit unless the URL also matches a known direct-file debrid rule.
- Native playback prefers the resolver URL when one is available, which avoids unnecessarily reusing short-lived observed CDN links.
- The native playback request carries the current user agent when available and sets
Referer: https://web.stremio.com/. - The native player clears duplicate suppression on dismissal, so selecting the same stream again can reopen playback.
- MobileVLCKit is behind a small protocol so the player controller is not permanently coupled to VLC.
Relevant Diff Snippets
Repository instructions prefer @pierre/diffs output. The package is installed as a library, but npx @pierre/diffs --help failed because it exposes no executable in this repo. This section uses a clearly labeled plain diff fallback.
diff --git a/Dreamio/DreamioWebViewController.swift b/Dreamio/DreamioWebViewController.swift
+ static let streamCandidateMessageHandler = "dreamioStreamCandidate"
+ configuration.userContentController.add(
+ WeakScriptMessageHandler(delegate: self),
+ name: Constants.streamCandidateMessageHandler
+ )
+ configuration.userContentController.addUserScript(Self.streamCandidateScript)
+
+ const nativePatterns = [
+ /\/\/addon\.debridio\.com\/play\//i,
+ /\/\/torrentio\.strem\.fun\/resolve\//i,
+ /\/\/download\.real-debrid\.com\//i,
+ /\.(mkv|avi|webm)(?:[?#]|$)/i
+ ];
+
+ private func handleStreamCandidate(_ candidate: StreamCandidate) {
+ guard let request = StreamClassifier.playbackRequest(from: candidate, userAgent: userAgent) else {
+ return
+ }
+ let player = NativePlayerViewController(request: request)
+ present(player, animated: true)
+ }
diff --git a/Dreamio/StreamCandidate.swift b/Dreamio/StreamCandidate.swift
+ enum StreamClassifier {
+ static func playbackRequest(
+ from candidate: StreamCandidate,
+ userAgent: String?
+ ) -> NativePlaybackRequest? {
+ let classification = classify(candidate: candidate)
+ guard classification.shouldIntercept else { return nil }
+ return NativePlaybackRequest(
+ playbackURL: candidate.resolverURL ?? candidate.observedURL,
+ observedURL: candidate.observedURL,
+ resolverURL: candidate.resolverURL,
+ pageURL: candidate.pageURL,
+ userAgent: userAgent,
+ referer: "https://web.stremio.com/",
+ classification: classification
+ )
+ }
+ }
diff --git a/Podfile b/Podfile
+ platform :ios, '16.0'
+ target 'Dreamio' do
+ use_frameworks!
+ pod 'MobileVLCKit'
+ end
Expected Impact for End-Users
Users should keep using Dreamio through the Stremio Web interface, but direct debrid MKV, AVI, and WebM streams should now open in native playback instead of falling through to WebKit's unsupported media failure path. Closing the native player returns to the same web session.
Validation
- Passed: JavaScript bridge syntax was checked with
node --check. - Passed: Swift Foundation-only classifier file type-checked with
xcrun swiftc -typecheck Dreamio/StreamCandidate.swift. - Passed: Whitespace validation passed with
git diff --check. - Blocked:
pod installcould not run because CocoaPods is not installed on this machine. - Blocked: iOS build validation could not run because active developer tools are Command Line Tools and the iPhoneOS SDK is unavailable.
Issues, Limitations, and Mitigations
- Native playback improves container support, but it cannot guarantee every codec, audio format, subtitle format, HDR variant, or expired debrid URL will play.
- The workspace and lockfile are not generated here because CocoaPods is unavailable. The README now makes the required local workflow explicit.
- Manual real-device validation is still required for actual Debridio, Torrentio, and Real-Debrid streams.
- DEBUG logs are intentionally sanitized and should not include full debrid URLs, query strings, tokens, signed paths, or long secret-like path segments.
Follow-up Work
- Install CocoaPods locally, run
pod install, commit the resultingPodfile.lockand workspace metadata if appropriate. - Open
Dreamio.xcworkspacein full Xcode and build on a real iOS device. - Validate sample Debridio, Torrentio, and Real-Debrid URLs, including HTTP 206 direct download responses.
- Consider adding a tiny XCTest target for classifier behavior once the project has a test bundle.
New Changes as of 2026-05-24 23:22 EDT
Summary of changes: Fixed the Swift build errors reported after the first handoff by converting the injected stream bridge into a raw multiline Swift string and guarding the MobileVLCKit import with canImport(MobileVLCKit).
Why this change was made: Swift was interpreting JavaScript regex backslashes as Swift string escapes, and the app could not compile from Dreamio.xcodeproj before CocoaPods had installed and linked MobileVLCKit. The fallback keeps the project buildable enough to show a native-player unavailable error until the workspace is set up with pods.
Code diffs: Plain diff fallback is used for the same reason noted above: @pierre/diffs is present as a library but has no runnable CLI exposed in this repo.
diff --git a/Dreamio/DreamioWebViewController.swift b/Dreamio/DreamioWebViewController.swift
- source: """
+ source: #"""
...
- """,
+ """#,
diff --git a/Dreamio/VLCNativePlaybackBackend.swift b/Dreamio/VLCNativePlaybackBackend.swift
+#if canImport(MobileVLCKit)
import MobileVLCKit
+#endif
...
+#if canImport(MobileVLCKit)
private let mediaPlayer = VLCMediaPlayer()
+#endif
...
+#else
+ onFailure?(NativePlaybackError.backendUnavailable)
+#endif
Related issues or PRs: Follow-up to Beads issue dreamio-l68.