/** * An {@link AdapterHost} implementation that forwards a player adapter's outward * lifecycle events into the Rust `PlayerController` via the `player_report_*` * commands. The controller re-emits the same `PlayerStatusEvent`s the native * backends emit, so the frontend `player` store is fed from ONE pipeline * (playerEvents.ts) in both native and HTML5 modes — keeping Rust the single * source of truth. * * This is the sole place that talks to the report commands; adapters depend only * on the {@link AdapterHost} interface, never on `commands` directly, which keeps * them unit-testable with a mock host. * * TRACES: UR-003, UR-005 | DR-001, DR-028 */ import { commands } from "$lib/api/bindings"; import type { AdapterHost } from "./types"; import { createLogger } from "$lib/utils/logger"; const log = createLogger("rustReportHost"); const POSITION_REPORT_INTERVAL_MS = 250; /** Report options that let a caller bypass throttling for discrete events. */ export interface ReportPositionOptions { force?: boolean; } /** * Low-level report helpers, exported so the legacy `$lib/player/html5Adapter` * shim can keep its function-style API while there are still direct callers. * Prefer {@link createRustReportHost} for new adapter code. */ let lastPositionReport = 0; export async function reportState( state: "playing" | "paused" | "loading" | "stopped" | "idle", mediaId: string | null, ): Promise { try { await commands.playerReportState(state, mediaId); } catch (err) { log.warn("Failed to report state:", err); } } export async function reportPosition( position: number, duration: number, { force = false }: ReportPositionOptions = {}, ): Promise { const now = Date.now(); if (!force && now - lastPositionReport < POSITION_REPORT_INTERVAL_MS) { return; } lastPositionReport = now; try { await commands.playerReportPosition(position, Number.isFinite(duration) ? duration : 0); } catch (err) { log.warn("Failed to report position:", err); } } export async function reportMediaLoaded(duration: number): Promise { try { await commands.playerReportMediaLoaded(Number.isFinite(duration) ? duration : 0); } catch (err) { log.warn("Failed to report media loaded:", err); } } export function resetReporting(): void { lastPositionReport = 0; } /** * Build an {@link AdapterHost} bound to a specific media id that forwards adapter * events to Rust. `onStreamUrlChanged`, `onBuffering`, and `onReady` are wired by * the owning view (they affect the `