/** * Native video surface compositing, Android only. * * TRACES: UR-003, UR-004 | DR-150, DR-151 * * On Android, ExoPlayer renders video into a SurfaceView that sits *behind* the * Tauri WebView (`setZOrderMediaOverlay(false)`, added at index 0 of the content * view by VideoOverlayManager). For that video to be visible, two independent * opaque layers have to be cleared: * * 1. The **WebView widget's own background** — reachable only from Kotlin, via * the `AndroidVideoSurface` @JavascriptInterface installed by MainActivity. * 2. The **web page's backgrounds** — the `html`/`body` colour in app.css and * the app shell's `bg-[var(--color-background)]`. Handled by the * `data-native-video` attribute, which $lib/stores/nativeVideo.ts sets and * app.css keys its transparency rules off. * * Clearing only one leaves a black screen with audio, which is exactly the * failure mode the old INTERIM override in VideoPlayer.svelte was working * around. Both must be toggled together, so this module owns both halves. * * Everything here is a no-op off Android — the bridge is simply absent. */ import { nativeVideoActive } from "$lib/stores/nativeVideo"; interface AndroidVideoSurfaceBridge { setTransparent(transparent: boolean): void; isSupported(): boolean; } declare global { interface Window { AndroidVideoSurface?: AndroidVideoSurfaceBridge; } } function bridge(): AndroidVideoSurfaceBridge | undefined { if (typeof window === "undefined") return undefined; return window.AndroidVideoSurface; } /** * Whether the native-surface bridge exists on this platform. This reports only * that the *plumbing* is present; whether native video should actually be used * is Rust's decision (`player_get_capabilities`) gated by the user's * `experimentalNativeVideo` flag. */ export function isNativeSurfaceBridgeAvailable(): boolean { try { return bridge()?.isSupported() ?? false; } catch (err) { console.warn("[videoSurface] isSupported check failed:", err); return false; } } /** * Make the webview transparent so the video surface behind it shows through. * * MUST be paired with {@link disableNativeVideoCompositing} on teardown — a * transparent window left behind shows the launcher through the whole app. */ export function enableNativeVideoCompositing(): void { // Page layer first: if the Kotlin call succeeded but this threw, the user // would see through the app to the home screen. nativeVideoActive.set(true); try { bridge()?.setTransparent(true); } catch (err) { console.warn("[videoSurface] setTransparent(true) failed:", err); nativeVideoActive.set(false); } } /** Restore the opaque webview background. Safe to call unconditionally. */ export function disableNativeVideoCompositing(): void { try { bridge()?.setTransparent(false); } catch (err) { console.warn("[videoSurface] setTransparent(false) failed:", err); } // Always clear the page layer, even if the bridge call failed, so the app is // never left rendering over a transparent window. nativeVideoActive.set(false); }