feat(windows): mpv draws Windows video into the app window

DR-237's video half. mpv now renders Windows video the way
tauri-plugin-libmpv does on Windows: MpvBackend is handed the main
window's HWND and sets it as `wid` before mpv initialises, so mpv draws
as a child of the app window beneath the transparent WebView2, with
vo=gpu-next,gpu. osc, default bindings, VO keyboard and cursor handling
are off, so the Svelte controls drawn over the picture are the only ones.

- video_output() decides per platform (UT-274): Window(hwnd) on Windows,
  RenderApi on Linux, Off without native video. Windows with no handle
  draws nothing rather than letting mpv open a top-level window.
- native_video::enabled() is true on Windows as well, so the frontend
  takes the native path there: NativePlayerAdapter, and the page clears
  its background while video is on screen.
- enableNativeVideoCompositing() no longer logs a missing Android bridge
  as an error on the desktop, where there is no bridge to have.

Verified: every option, wid included, is accepted by the real libmpv on
Linux and by the shipped Windows DLL under wine; the Windows unit suite
passes under wine (949). Not yet seen on real Windows hardware.
This commit is contained in:
2026-09-24 22:42:42 -04:00
parent 4daf172834
commit bb3ab1edd7
7 changed files with 240 additions and 31 deletions
+17
View File
@@ -24,9 +24,18 @@
import { nativeVideoActive } from "$lib/stores/nativeVideo";
import { createLogger } from "$lib/utils/logger";
import { platform } from "@tauri-apps/plugin-os";
const log = createLogger("videoSurface");
function isAndroid(): boolean {
try {
return platform() === "android";
} catch {
return false;
}
}
interface AndroidVideoSurfaceBridge {
setTransparent(transparent: boolean): void;
isSupported(): boolean;
@@ -69,6 +78,14 @@ export function enableNativeVideoCompositing(): void {
// would see through the app to the home screen.
nativeVideoActive.set(true);
const androidVideoSurface = bridge();
// Only Android has a webview widget to make transparent. On the desktop the
// window is created transparent and mpv draws beneath it, so the page layer
// above is all there is — an absent bridge there is correct, not a fault.
// TRACES: UR-080 | DR-237
if (!androidVideoSurface && !isAndroid()) {
nativeVideoActive.set(true);
return;
}
if (!androidVideoSurface) {
// Say so loudly. Every bridge call in this file is optional-chained, so a
// missing bridge is silent — and a silently-skipped setTransparent(true) is