# Guard Native Playback Availability

Dreamio now checks whether the MobileVLCKit-backed native player is actually linked before presenting the full-screen native player. Raw project builds stay buildable, but they now show a setup alert instead of opening a black player that can only fail.

Beads: dreamio-2k5 Native playback CocoaPods setup 2026-05-25

## Summary

Fixed the unavailable native playback build path by exposing a build-time availability check on `VLCNativePlaybackBackend` and using it before Dreamio presents native playback. CocoaPods was installed through Homebrew, `pod install` was run, and the generated workspace now links MobileVLCKit.

## Changes Made

- Added `VLCNativePlaybackBackend.isAvailable`, backed by the same `canImport(MobileVLCKit)` compile condition as the real VLC implementation.
- Updated `DreamioWebViewController` to check native backend availability before resolving and presenting the native player.
- Added an actionable setup alert for builds that do not link `MobileVLCKit`.
- Updated the README to explain that the exact unavailable-build message means the binary was built without the CocoaPods workspace.
- Installed CocoaPods 1.16.2 with Homebrew and ran `pod install`, generating `Dreamio.xcworkspace` and `Podfile.lock` with MobileVLCKit 3.7.3.
- Disabled Xcode user script sandboxing for the project so CocoaPods can embed MobileVLCKit during the framework copy phase.

## Context

The repository has a `Podfile` declaring `MobileVLCKit`, but this checkout did not have a generated `Pods/` directory or `Dreamio.xcworkspace`. In that state, Swift takes the fallback compile path where `canImport(MobileVLCKit)` is false. Before this change, Dreamio could still present the native player, which then displayed the generic fallback error.

## Important Implementation Details

- The fallback backend remains intact so opening `Dreamio.xcodeproj` directly still compiles.
- The guard runs before stream resolution, avoiding unnecessary resolver network work when native playback cannot succeed in the current build.
- The duplicate playback key is cleared when the guard blocks playback, so the user can retry after rebuilding the app correctly.
- The generated workspace references `Dreamio.xcodeproj` and `Pods/Pods.xcodeproj`. The `Pods/` directory remains ignored, while `Podfile.lock` and workspace metadata are tracked.
- `ENABLE_USER_SCRIPT_SANDBOXING` is set to `NO` because the CocoaPods embed frameworks script uses `rsync` to copy the MobileVLCKit framework into the app bundle.
- No public app-facing API changed.

## Relevant Diff Snippets

`@pierre/diffs` is installed as a library dependency, but its package does not expose a runnable CLI in this checkout, and `npx @pierre/diffs --help` failed with "could not determine executable to run." The plain diff below is the fallback snippet for the core behavior change.

    diff --git a/Dreamio/DreamioWebViewController.swift b/Dreamio/DreamioWebViewController.swift
    @@ -368,6 +368,12 @@ final class DreamioWebViewController: UIViewController {
         @MainActor
         private func resolveAndPresentNativePlayback(_ request: NativePlaybackRequest) async {
    +        guard VLCNativePlaybackBackend.isAvailable else {
    +            lastNativePlaybackURL = nil
    +            showNativePlaybackUnavailableAlert()
    +            return
    +        }
    +
             do {
                 let resolved = try await streamResolver.resolve(request: request)

    diff --git a/Dreamio/VLCNativePlaybackBackend.swift b/Dreamio/VLCNativePlaybackBackend.swift
    @@ -5,6 +5,14 @@ import MobileVLCKit
     #endif

     final class VLCNativePlaybackBackend: NSObject, NativePlaybackBackend {
    +    static var isAvailable: Bool {
    +#if canImport(MobileVLCKit)
    +        true
    +#else
    +        false
    +#endif
    +    }
    +
         let view = UIView()
         var onReady: (() -> Void)?
         var onFailure: ((Error) -> Void)?

## Expected Impact for End-Users

Users who accidentally run a raw `.xcodeproj` build will see a clear CocoaPods setup message instead of a black native player with an unavailable-build failure. Users who build from `Dreamio.xcworkspace` with `MobileVLCKit` linked should continue into VLC-backed direct-file playback.

## Validation

- Ran `swiftc Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift Tests/StreamResolverTests.swift -o /tmp/dreamio-stream-tests && /tmp/dreamio-stream-tests`: passed.
- Ran `swiftc -typecheck Dreamio/StreamCandidate.swift Dreamio/StreamResolver.swift`: passed.
- Ran `git diff --check`: passed.
- Ran `HOMEBREW_NO_AUTO_UPDATE=1 brew install cocoapods`: passed, installing CocoaPods 1.16.2.
- Ran `pod --version && pod install`: passed, installing MobileVLCKit 3.7.3 and generating the workspace.
- Ran `DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -configuration Debug -sdk iphonesimulator build`: passed.

## Issues, Limitations, and Mitigations

The simulator workspace build passes. Real-device playback validation is still required for the actual VLC-backed stream behavior.

- The global `xcode-select` value still points at Command Line Tools because changing it requires sudo. Command-line builds can use `DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer`.
- The `Pods/` directory is intentionally ignored by git, so another checkout should run `pod install` after pulling.
- The native player still depends on MobileVLCKit behavior once the workspace build is available.

## Follow-up Work

- On device, select a direct MKV, AVI, or WebM stream and confirm the VLC-backed player starts.