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.
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
/**
|
|
* Background-audio support, Android only.
|
|
*
|
|
* TRACES: UR-040 | IR-025, DR-051
|
|
*
|
|
* Keeps a video's *audio* playing when the app is backgrounded or the screen is
|
|
* locked, while video decode stops. This is a HANDOFF: the WebView `<video>`
|
|
* element (which decodes video) is torn down and the same item is played back
|
|
* audio-only through the native ExoPlayer foreground service. It is NOT the
|
|
* WebView staying alive — an Android WebView `<video>` does not keep audio
|
|
* playing once the app is backgrounded.
|
|
*
|
|
* The `AndroidBackgroundAudio` @JavascriptInterface (installed by MainActivity)
|
|
* carries the toggle state to native; native signals background/foreground back
|
|
* to the frontend as DOM CustomEvents (`jellytau-background` /
|
|
* `jellytau-foreground`) — see subscribeAppBackgrounded/Foregrounded below.
|
|
*
|
|
* Unsupported (no-op) on every non-Android platform.
|
|
*/
|
|
|
|
import { createLogger } from "$lib/utils/logger";
|
|
|
|
const log = createLogger("BgAudio");
|
|
|
|
interface AndroidBackgroundAudioBridge {
|
|
setEnabled(enabled: boolean): void;
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
AndroidBackgroundAudio?: AndroidBackgroundAudioBridge;
|
|
}
|
|
}
|
|
|
|
function bridge(): AndroidBackgroundAudioBridge | undefined {
|
|
if (typeof window === "undefined") return undefined;
|
|
return window.AndroidBackgroundAudio;
|
|
}
|
|
|
|
/**
|
|
* Arm/disarm background-audio mode for the current video. When armed, the native
|
|
* side runs the audio handoff on background instead of entering PiP.
|
|
*/
|
|
export function setBackgroundAudioEnabled(enabled: boolean): boolean {
|
|
const b = bridge();
|
|
if (!b) {
|
|
// The button is gated on platform(), not on this bridge, so it can render
|
|
// before/without the bridge existing. Silently no-oping here leaves the UI
|
|
// showing "armed" while native never learns — and the handoff then never
|
|
// fires on lock. Report it so callers can retry.
|
|
log.warn("setEnabled: bridge missing, native NOT armed");
|
|
return false;
|
|
}
|
|
try {
|
|
b.setEnabled(enabled);
|
|
log.debug("setEnabled ->", enabled);
|
|
return true;
|
|
} catch (err) {
|
|
log.warn("Failed to set enabled:", err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subscribe to the native "app backgrounded" signal (Home/app-switch/lock).
|
|
* Returns an unsubscribe function. No-op where unsupported (the event never
|
|
* fires on non-Android platforms).
|
|
*/
|
|
export function subscribeAppBackgrounded(handler: () => void): () => void {
|
|
if (typeof window === "undefined") return () => {};
|
|
window.addEventListener("jellytau-background", handler);
|
|
return () => window.removeEventListener("jellytau-background", handler);
|
|
}
|
|
|
|
/** Subscribe to the native "app foregrounded" signal. Returns an unsubscribe fn. */
|
|
export function subscribeAppForegrounded(handler: () => void): () => void {
|
|
if (typeof window === "undefined") return () => {};
|
|
window.addEventListener("jellytau-foreground", handler);
|
|
return () => window.removeEventListener("jellytau-foreground", handler);
|
|
}
|