Files
jellytau/src/lib/services/playbackCapabilities.ts
dtourolle d54d8cc7c4 refactor(logging): route frontend console calls through the logger
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.
2026-08-20 19:29:59 +02:00

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;
}