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

- Added a production JavaScript bridge in `DreamioWebViewController` that observes video/source URLs, direct `src` assignment, `setAttribute("src")`, mutations, and `load()`.
- 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 `Podfile` for `MobileVLCKit` and ignored generated `Pods/` content.
- Updated README workflow instructions to use `pod install` and `Dreamio.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 install` could 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 resulting `Podfile.lock` and workspace metadata if appropriate.
- Open `Dreamio.xcworkspace` in 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`.