feat(autodj): smooth skip and interrupt blend transitions (#1128)

This commit is contained in:
cucadmuh
2026-06-19 04:52:15 +03:00
committed by GitHub
parent d50c9c444d
commit 4225146a16
50 changed files with 1118 additions and 208 deletions
+65
View File
@@ -0,0 +1,65 @@
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) };
}