import { create } from 'zustand' /** * Per-session player state - things that should reset on a new playback * session and shouldn't pollute the persisted preferences store. * * Examples: * - A-B loop markers (per item) * - Theater / zen mode toggle (transient) * - Per-session subtitle / audio delay overrides on top of the persisted * defaults from `usePreferencesStore` * - Secondary subtitle index * - Picture filters during this session (overrides persisted defaults) * * Anything you'd want to persist across episodes / app restarts belongs * in `usePreferencesStore`, not here. */ export interface PlayerRuntimeState { // A-B loop loopA: number | null loopB: number | null // Subtitle + audio offsets in ms. These add to the persisted default // from settings; combined effective offset = settings + runtime. subtitleOffsetMs: number audioOffsetMs: number // Secondary subtitle stream index (Jellyfin stream Index, not array index) secondarySubtitleIndex: number | null // Theater / zen mode theaterMode: boolean // Picture filters - if a value is null we fall back to the persisted pref. // Overrides for *this* session only. brightnessOverride: number | null contrastOverride: number | null saturationOverride: number | null // Auto-recap dismissal flag - true once the user has dismissed (or // skipped) the recap card for the current item, so it doesn't reappear // if playback restarts. recapDismissed: boolean setLoopA(t: number | null): void setLoopB(t: number | null): void clearLoop(): void setSubtitleOffsetMs(ms: number): void bumpSubtitleOffsetMs(deltaMs: number): void setAudioOffsetMs(ms: number): void bumpAudioOffsetMs(deltaMs: number): void setSecondarySubtitleIndex(i: number | null): void setTheaterMode(on: boolean): void setBrightnessOverride(v: number | null): void setContrastOverride(v: number | null): void setSaturationOverride(v: number | null): void setRecapDismissed(v: boolean): void /** Reset everything except theaterMode (which is window-scoped, not item-scoped). */ resetForNewItem(): void } export const usePlayerRuntimeStore = create(set => ({ loopA: null, loopB: null, subtitleOffsetMs: 0, audioOffsetMs: 0, secondarySubtitleIndex: null, theaterMode: false, brightnessOverride: null, contrastOverride: null, saturationOverride: null, recapDismissed: false, setLoopA: t => set({ loopA: t }), setLoopB: t => set({ loopB: t }), clearLoop: () => set({ loopA: null, loopB: null }), setSubtitleOffsetMs: ms => set({ subtitleOffsetMs: ms }), bumpSubtitleOffsetMs: deltaMs => set(s => ({ subtitleOffsetMs: s.subtitleOffsetMs + deltaMs })), setAudioOffsetMs: ms => set({ audioOffsetMs: ms }), bumpAudioOffsetMs: deltaMs => set(s => ({ audioOffsetMs: s.audioOffsetMs + deltaMs })), setSecondarySubtitleIndex: i => set({ secondarySubtitleIndex: i }), setTheaterMode: on => set({ theaterMode: on }), setBrightnessOverride: v => set({ brightnessOverride: v }), setContrastOverride: v => set({ contrastOverride: v }), setSaturationOverride: v => set({ saturationOverride: v }), setRecapDismissed: v => set({ recapDismissed: v }), resetForNewItem: () => set({ loopA: null, loopB: null, subtitleOffsetMs: 0, audioOffsetMs: 0, secondarySubtitleIndex: null, brightnessOverride: null, contrastOverride: null, saturationOverride: null, recapDismissed: false, }), }))