Files
Psychotoxical-psysonic/src/utils/playback/autodjInterruptPrep.ts
T

66 lines
2.0 KiB
TypeScript

import { invoke } from '@tauri-apps/api/core';
import type { Track } from '../../store/playerStoreTypes';
import {
INTERRUPT_BLEND_PREP_FADE_SEC,
isCrossfadeNextReady,
kickEagerCrossfadePreload,
waitForCrossfadeNextReady,
} from '../../store/crossfadePreload';
export { INTERRUPT_BLEND_PREP_FADE_SEC };
/** Play generation with a pending interrupt handoff (suppress spurious `audio:ended`). */
let pendingHandoffGen: number | null = null;
export function armInterruptHandoff(gen: number): void {
pendingHandoffGen = gen;
}
export function clearInterruptHandoff(): void {
pendingHandoffGen = null;
}
export function isInterruptHandoffPending(): boolean {
return pendingHandoffGen !== null;
}
/** Keep player-bar metadata on A until B is buffered and the engine handoff runs. */
export function shouldDeferInterruptHandoffUi(
wantInterruptBlend: boolean,
bReadyNow: boolean,
): boolean {
return wantInterruptBlend && !bReadyNow;
}
function sleepMs(ms: number): Promise<void> {
return new Promise(resolve => { window.setTimeout(resolve, ms); });
}
export interface InterruptBlendPrepResult {
/** B is playable for a stable crossfade handoff. */
ready: boolean;
}
/**
* Win preload time before the incoming track starts: fade the outgoing engine
* source for ~1 s while eagerly buffering B. Caller should pass
* `outgoingFadeSec: 0` on the blend plan when prep ran — A was volume-ducked only.
*/
export async function runInterruptBlendPrep(
track: Track,
profileId: string | null,
cacheKey: string | null,
isStale: () => boolean,
): Promise<InterruptBlendPrepResult> {
kickEagerCrossfadePreload(track, profileId, cacheKey);
void invoke('audio_begin_outgoing_fade', { fadeSecs: INTERRUPT_BLEND_PREP_FADE_SEC }).catch(() => {});
const prepMs = Math.round(INTERRUPT_BLEND_PREP_FADE_SEC * 1000);
await Promise.all([
sleepMs(prepMs),
waitForCrossfadeNextReady(track.id, profileId, cacheKey, prepMs, isStale),
]);
if (isStale()) return { ready: false };
return { ready: isCrossfadeNextReady(track.id, profileId, cacheKey) };
}