🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m1s
Traceability Validation / Check Requirement Traces (push) Successful in 23s
Build & Release / Run Tests (push) Successful in 4m7s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 19m5s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 19m12s
Build & Release / Create Release (push) Successful in 8s
85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
/**
|
|
* Sleep Timer Store (Backend-First Architecture)
|
|
*
|
|
* This store reflects sleep timer state from the backend.
|
|
* All logic is in the Rust backend (PlayerController).
|
|
*
|
|
* The backend emits SleepTimerChanged events to update this store.
|
|
* Components call store methods which invoke the backend API.
|
|
*
|
|
* TRACES: UR-026 | DR-029
|
|
*/
|
|
|
|
import { writable, derived } from "svelte/store";
|
|
import { commands } from "$lib/api/bindings";
|
|
|
|
export type SleepTimerMode =
|
|
| { kind: "off" }
|
|
| { kind: "time"; endTime: number }
|
|
| { kind: "endOfTrack" }
|
|
| { kind: "episodes"; remaining: number };
|
|
|
|
interface SleepTimerState {
|
|
mode: SleepTimerMode;
|
|
remainingSeconds: number;
|
|
}
|
|
|
|
function createSleepTimerStore() {
|
|
const initialState: SleepTimerState = {
|
|
mode: { kind: "off" },
|
|
remainingSeconds: 0,
|
|
};
|
|
|
|
const { subscribe, set } = writable<SleepTimerState>(initialState);
|
|
|
|
return {
|
|
subscribe,
|
|
set, // Updated by playerEvents.ts when backend emits SleepTimerChanged event
|
|
|
|
// Methods that invoke the backend API
|
|
async setTimeTimer(minutes: number): Promise<void> {
|
|
const endTime = Date.now() + minutes * 60 * 1000;
|
|
await commands.playerSetSleepTimer({ kind: "time", endTime });
|
|
},
|
|
|
|
async setEndOfTrackTimer(): Promise<void> {
|
|
await commands.playerSetSleepTimer({ kind: "endOfTrack" });
|
|
},
|
|
|
|
async setEpisodesTimer(count: number): Promise<void> {
|
|
await commands.playerSetSleepTimer({ kind: "episodes", remaining: count });
|
|
},
|
|
|
|
async cancel(): Promise<void> {
|
|
await commands.playerCancelSleepTimer();
|
|
},
|
|
};
|
|
}
|
|
|
|
export const sleepTimer = createSleepTimerStore();
|
|
|
|
/**
|
|
* Incremented each time the backend reports the time-based sleep timer
|
|
* expired. The backend stops its own (MPV/ExoPlayer) playback itself, but
|
|
* HTML5 video on Linux plays in the webview outside the backend's control —
|
|
* VideoPlayer watches this signal and pauses the <video> element.
|
|
*/
|
|
export const sleepTimerExpiredSignal = writable(0);
|
|
|
|
// Derived stores for convenient access
|
|
export const sleepTimerMode = derived(sleepTimer, ($s) => $s.mode);
|
|
|
|
export const sleepTimerActive = derived(
|
|
sleepTimer,
|
|
($s) => $s.mode.kind !== "off"
|
|
);
|
|
|
|
export const sleepTimerRemainingSeconds = derived(
|
|
sleepTimer,
|
|
($s) => $s.remainingSeconds
|
|
);
|
|
|
|
export const sleepTimerRemainingEpisodes = derived(sleepTimer, ($s) =>
|
|
$s.mode.kind === "episodes" ? $s.mode.remaining : 0
|
|
);
|