Dreamio turn document · 2026-05-24 23:18 EDT · Beads issue dreamio-l68

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.

WKWebView bridge Stream classification MobileVLCKit backend Sanitized diagnostics

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

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

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

Issues, Limitations, and Mitigations

Follow-up Work

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.