TRACES: | DR-204 484 ungated `console.*` calls across 63 non-test frontend files shipped to end users with no way to turn them off. Mechanical substitution, no control flow, error handling or message semantics changed: console.log / console.debug -> log.debug console.info -> log.info console.warn -> log.warn console.error -> log.error Hand-written `"[Scope] …"` prefixes are dropped where the logger's scope now carries them; scope names that already existed are preserved verbatim (`[Auth]`, `[VideoPlayer]`, `[PiP]`, …) and inferred from the filename where a file had none. `src/routes/player/[id]/+page.svelte` keeps its `NextEpisode` and `AutoPlay` sub-scopes as separate loggers rather than flattening them into the page scope. `grep -rn 'console\.' src/` now matches nothing outside the tests and the facade itself.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
/**
|
|
* Platform playback capabilities, read from Rust.
|
|
*
|
|
* TRACES: UR-003, UR-005 | DR-004, DR-152
|
|
*
|
|
* "Which backend does this platform have" is a *backend* fact, so Rust owns it
|
|
* (`player_get_capabilities`, gated on the same `cfg!` the backends are built
|
|
* under). This module is a thin cache over that command.
|
|
*
|
|
* It exists because the frontend used to re-derive the answer by sniffing
|
|
* `navigator.userAgent` for "android"/"linux" — a second, silently drifting copy
|
|
* of a decision Rust already makes. Consume the value; never re-derive it.
|
|
*/
|
|
|
|
import { commands } from "$lib/api/bindings";
|
|
import { createLogger } from "$lib/utils/logger";
|
|
|
|
const log = createLogger("capabilities");
|
|
|
|
export interface PlaybackCapabilities {
|
|
/** Audio renders through a webview `<audio>` element, not a native backend. */
|
|
usesWebviewAudio: boolean;
|
|
/** Video can render on a native surface behind a transparent webview. */
|
|
supportsNativeVideo: boolean;
|
|
}
|
|
|
|
/**
|
|
* Conservative defaults for when the backend cannot be reached (very early
|
|
* startup, or a command failure). Both false = "assume no special platform
|
|
* facilities": no stray `<audio>` element is mounted, and video stays on the
|
|
* HTML5 path, which is the safe behaviour everywhere.
|
|
*/
|
|
const FALLBACK: PlaybackCapabilities = {
|
|
usesWebviewAudio: false,
|
|
supportsNativeVideo: false,
|
|
};
|
|
|
|
let cached: PlaybackCapabilities | null = null;
|
|
let inflight: Promise<PlaybackCapabilities> | null = null;
|
|
|
|
/**
|
|
* Fetch (and memoize) this platform's capabilities. Cached because the answer is
|
|
* compile-time constant in Rust — it cannot change during a session.
|
|
*/
|
|
export async function getPlaybackCapabilities(): Promise<PlaybackCapabilities> {
|
|
if (cached) return cached;
|
|
if (inflight) return inflight;
|
|
|
|
inflight = (async () => {
|
|
try {
|
|
const caps = (await commands.playerGetCapabilities()) as PlaybackCapabilities;
|
|
cached = {
|
|
usesWebviewAudio: !!caps?.usesWebviewAudio,
|
|
supportsNativeVideo: !!caps?.supportsNativeVideo,
|
|
};
|
|
return cached;
|
|
} catch (err) {
|
|
log.warn("player_get_capabilities failed:", err);
|
|
// Do NOT cache the fallback — a later call should get the real answer.
|
|
return FALLBACK;
|
|
} finally {
|
|
inflight = null;
|
|
}
|
|
})();
|
|
|
|
return inflight;
|
|
}
|
|
|
|
/** Reset the cache. Test-only. */
|
|
export function __resetPlaybackCapabilitiesCache(): void {
|
|
cached = null;
|
|
inflight = null;
|
|
}
|