95 lines
3.4 KiB
TypeScript
95 lines
3.4 KiB
TypeScript
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<PlayerRuntimeState>(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,
|
|
}),
|
|
}))
|