mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
3a99be4daa
Three coordinating mutables move into `src/store/gaplessPreloadState.ts`
behind a thin API:
- `gaplessPreloadingId` / `bytePreloadingId` — guards so the runtime
doesn't fire a chain-/byte-preload twice for the same id
- `lastGaplessSwitchTime` — timestamp used by the 500–600 ms
ghost-IPC suppression guards in `handleAudioEnded` and `playTrack`
Public API: `get…` / `set…` accessors plus `clearPreloadingIds`
(atomic clear of both guards — collapses three `= null; = null;`
duplications) and `markGaplessSwitch` (`Date.now()` stamp). The
mutables are no longer reachable from outside the module.
13 focused tests pin the get/set round-trips, independence between
the two guards, the atomic clear, and the timestamp progression.
playerStore 3208 → 3207 LOC (small delta because the `= null; = null;`
inlines compress to single `clearPreloadingIds()` calls but the
module itself adds the accessor functions to the import list).
92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
/**
|
|
* Three mutables that coordinate the gapless preloader. Most of the surface
|
|
* is straight get/set — the interesting bit is `clearPreloadingIds` (atomic
|
|
* clear of both) and `markGaplessSwitch` (timestamp side effect).
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
_resetGaplessPreloadStateForTest,
|
|
clearPreloadingIds,
|
|
getBytePreloadingId,
|
|
getGaplessPreloadingId,
|
|
getLastGaplessSwitchTime,
|
|
markGaplessSwitch,
|
|
setBytePreloadingId,
|
|
setGaplessPreloadingId,
|
|
} from './gaplessPreloadState';
|
|
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date('2026-05-12T12:00:00Z'));
|
|
});
|
|
|
|
afterEach(() => {
|
|
_resetGaplessPreloadStateForTest();
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('initial state', () => {
|
|
it('is null / 0 for unread accessors', () => {
|
|
expect(getGaplessPreloadingId()).toBeNull();
|
|
expect(getBytePreloadingId()).toBeNull();
|
|
expect(getLastGaplessSwitchTime()).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('preloading-id accessors', () => {
|
|
it('round-trips through the gapless guard', () => {
|
|
setGaplessPreloadingId('t1');
|
|
expect(getGaplessPreloadingId()).toBe('t1');
|
|
});
|
|
|
|
it('round-trips through the byte guard', () => {
|
|
setBytePreloadingId('t2');
|
|
expect(getBytePreloadingId()).toBe('t2');
|
|
});
|
|
|
|
it('keeps the two guards independent', () => {
|
|
setGaplessPreloadingId('a');
|
|
setBytePreloadingId('b');
|
|
expect(getGaplessPreloadingId()).toBe('a');
|
|
expect(getBytePreloadingId()).toBe('b');
|
|
});
|
|
|
|
it('accepts null to clear a guard', () => {
|
|
setGaplessPreloadingId('a');
|
|
setGaplessPreloadingId(null);
|
|
expect(getGaplessPreloadingId()).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('clearPreloadingIds', () => {
|
|
it('clears both guards atomically', () => {
|
|
setGaplessPreloadingId('a');
|
|
setBytePreloadingId('b');
|
|
clearPreloadingIds();
|
|
expect(getGaplessPreloadingId()).toBeNull();
|
|
expect(getBytePreloadingId()).toBeNull();
|
|
});
|
|
|
|
it('does not touch the gapless-switch timestamp', () => {
|
|
markGaplessSwitch();
|
|
const before = getLastGaplessSwitchTime();
|
|
clearPreloadingIds();
|
|
expect(getLastGaplessSwitchTime()).toBe(before);
|
|
});
|
|
});
|
|
|
|
describe('markGaplessSwitch', () => {
|
|
it('stamps Date.now()', () => {
|
|
markGaplessSwitch();
|
|
expect(getLastGaplessSwitchTime()).toBe(Date.now());
|
|
});
|
|
|
|
it('overwrites on a later call', () => {
|
|
markGaplessSwitch();
|
|
const first = getLastGaplessSwitchTime();
|
|
vi.advanceTimersByTime(700);
|
|
markGaplessSwitch();
|
|
expect(getLastGaplessSwitchTime()).toBeGreaterThan(first);
|
|
});
|
|
});
|