/** * Picture-in-picture support, Android only. * * TRACES: UR-041 | IR-026 | DR-053 * * Video on Android renders into a native ExoPlayer SurfaceView behind the * WebView, so PiP is driven by the Activity (which shrinks into a floating * window) rather than the HTML5 `requestPictureInPicture()` API. The bridge is * the `AndroidPictureInPicture` @JavascriptInterface installed by MainActivity. * * On every other platform this module reports unsupported. Notably WebKitGTK * (the Linux webview) does not implement the Picture-in-Picture Web API at all, * so there is no HTML5 fallback to reach for. */ import { createLogger } from "$lib/utils/logger"; const log = createLogger("PiP"); interface AndroidPictureInPictureBridge { enterPip(): void; isSupported(): boolean; canEnterPip(): boolean; setAutoEnterEnabled(enabled: boolean): void; setHtml5VideoState(active: boolean, width: number, height: number, playing: boolean): void; } declare global { interface Window { AndroidPictureInPicture?: AndroidPictureInPictureBridge; } } function bridge(): AndroidPictureInPictureBridge | undefined { if (typeof window === "undefined") return undefined; return window.AndroidPictureInPicture; } /** * Whether the device can do PiP at all - used to decide if the button should * be rendered. False on desktop, and on Android devices where the user has * disabled the feature. */ export function isPipSupported(): boolean { try { return bridge()?.isSupported() ?? false; } catch (err) { log.warn("isSupported check failed:", err); return false; } } /** * Whether entering PiP would succeed right now: a native video must be playing * locally. False during audio playback and while casting to a remote session. */ export function canEnterPip(): boolean { try { return bridge()?.canEnterPip() ?? false; } catch (err) { log.warn("canEnterPip check failed:", err); return false; } } /** Enter picture-in-picture. No-op where unsupported. */ export function enterPip(): void { try { bridge()?.enterPip(); } catch (err) { log.error("Failed to enter picture-in-picture:", err); } } /** * Enable/disable auto-entering PiP when the user backgrounds the app. * * This is a coarse frontend override; the authoritative gate is the native * `canEnterPip` guard, which already refuses PiP unless a local video surface * is actively rendering (so audio playback, menu/library browsing, and * remote/cast sessions never enter PiP regardless of this flag). The only * caller today is the background-audio toggle, which disarms auto-PiP so the * two background behaviours stay mutually exclusive. */ export function setAutoEnterEnabled(enabled: boolean): void { try { bridge()?.setAutoEnterEnabled(enabled); } catch (err) { log.warn("Failed to set auto-enter:", err); } } /** * Tell native that a WebView `