Stop previews when arrowing or selecting

This commit is contained in:
2026-03-12 17:16:17 +01:00
parent 26274c3f31
commit 31abff05f3
3 changed files with 59 additions and 1 deletions

View File

@@ -48,6 +48,9 @@ export class AudioEngine {
private readonly sampleLoaders = new Map<string, Promise<AudioBuffer>>();
private readonly activeSpatialSamples = new Set<ActiveSpatialSampleRuntime>();
private previewSourceNode: AudioBufferSourceNode | null = null;
private previewGainNode: GainNode | null = null;
private outboundSource: MediaStreamAudioSourceNode | null = null;
private outboundInputGain: GainNode | null = null;
private outboundInputGainValue = 1;
@@ -491,6 +494,47 @@ export class AudioEngine {
}
}
stopPreviewSample(): void {
if (this.previewSourceNode) {
try { this.previewSourceNode.stop(); } catch { /* already ended */ }
try { this.previewSourceNode.disconnect(); } catch { /* ignore */ }
this.previewSourceNode = null;
}
if (this.previewGainNode) {
try { this.previewGainNode.disconnect(); } catch { /* ignore */ }
this.previewGainNode = null;
}
}
async playPreviewSample(url: string, gain = 1): Promise<void> {
this.stopPreviewSample();
await this.ensureContext();
const { audioCtx, sfxGainNode } = this;
if (!audioCtx || !sfxGainNode) return;
if (gain <= 0) return;
try {
const buffer = await this.getSampleBuffer(url);
this.stopPreviewSample();
const source = audioCtx.createBufferSource();
source.buffer = buffer;
const gainNode = audioCtx.createGain();
gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
gainNode.gain.setTargetAtTime(gain, audioCtx.currentTime, ONE_SHOT_ATTACK_SECONDS);
source.connect(gainNode).connect(sfxGainNode);
this.previewSourceNode = source;
this.previewGainNode = gainNode;
source.onended = () => {
if (this.previewSourceNode === source) {
this.previewSourceNode = null;
this.previewGainNode = null;
}
};
source.start();
} catch {
// Ignore decode/load errors.
}
}
/** Starts a looping sample and returns a stop callback for explicit teardown. */
async startLoopingSample(url: string, gain = 1): Promise<(() => void) | null> {
await this.ensureContext();

View File

@@ -49,6 +49,7 @@ type EditorDeps = {
sfxUiCancel: () => void;
openSoundPropertyPicker?: (item: WorldItem, key: string) => void;
previewSound?: (soundPath: string) => void;
stopPreviewSound?: () => void;
};
/**
@@ -408,6 +409,7 @@ export function createItemPropertyEditor(deps: EditorDeps): {
}
if (control.type === 'select') {
deps.stopPreviewSound?.();
const selectedValue = deps.state.itemPropertyOptionValues[deps.state.itemPropertyOptionIndex];
deps.signalingSend({ type: 'item_update', itemId, params: { [propertyKey]: selectedValue } });
const item = deps.state.items.get(itemId);
@@ -422,6 +424,7 @@ export function createItemPropertyEditor(deps: EditorDeps): {
}
if (control.type === 'cancel') {
deps.stopPreviewSound?.();
deps.state.mode = 'itemProperties';
deps.state.editingPropertyKey = null;
deps.state.itemPropertyOptionValues = [];

View File

@@ -1027,17 +1027,27 @@ let previewDebounceTimer: ReturnType<typeof setTimeout> | null = null;
/** Plays a sound preview with debounce, for use while navigating sound picker. */
function previewSound(soundPath: string): void {
audio.stopPreviewSample();
if (previewDebounceTimer !== null) {
clearTimeout(previewDebounceTimer);
}
previewDebounceTimer = setTimeout(() => {
previewDebounceTimer = null;
if (soundPath) {
void audio.playSample(soundPath, 0.7);
void audio.playPreviewSample(soundPath, 0.7);
}
}, 200);
}
/** Stops any in-progress preview sound and clears the debounce timer. */
function stopPreviewSound(): void {
if (previewDebounceTimer !== null) {
clearTimeout(previewDebounceTimer);
previewDebounceTimer = null;
}
audio.stopPreviewSample();
}
/** Opens the sound picker for a sound-typed item property, falling back to text edit if no sounds are found. */
async function openSoundPropertyPicker(item: WorldItem, key: string): Promise<void> {
updateStatus('Loading sounds...');
@@ -2629,6 +2639,7 @@ const itemPropertyEditor = createItemPropertyEditor({
sfxUiCancel: () => audio.sfxUiCancel(),
openSoundPropertyPicker: (item, key) => { void openSoundPropertyPicker(item, key); },
previewSound,
stopPreviewSound,
});
/** Handles nickname edit mode submission/cancel and text editing keys. */