11869be443
Lifts the `state.kind === 'connected'` guard from the soundboard hotkey useEffect so OS-level shortcuts are always registered. Inside a call the existing `playSoundboard` path routes audio into the LiveKit pipeline so peers hear; outside a call the new `playSoundboardLocal` helper fetches the blob via `getSoundBlob`, creates a short-lived object URL, and plays through a fresh HTMLAudioElement on the system default output. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
// Plays a soundboard entry to the local default audio output. Used when no
|
|
// call pipeline is active (the in-call path routes via the LiveKit
|
|
// publishing pipeline so peers hear; this path is local-only). Fetches the
|
|
// blob via getSoundBlob and creates a short-lived object URL for the audio
|
|
// element.
|
|
|
|
import { getSoundBlob, type SoundboardEntry } from './soundboardStorage';
|
|
|
|
const activeAudios = new Set<HTMLAudioElement>();
|
|
|
|
export async function playSoundboardLocal(entry: SoundboardEntry): Promise<void> {
|
|
const blob = await getSoundBlob(entry.id);
|
|
if (!blob) return;
|
|
const src = URL.createObjectURL(blob);
|
|
const el = new Audio(src);
|
|
// Use the per-entry gain as local volume. SoundboardEntry exposes `gain`
|
|
// (0..1) which mirrors the value used in the in-call pipeline.
|
|
el.volume = Math.max(0, Math.min(1, entry.gain));
|
|
activeAudios.add(el);
|
|
const cleanup = () => {
|
|
activeAudios.delete(el);
|
|
URL.revokeObjectURL(src);
|
|
};
|
|
el.addEventListener('ended', cleanup);
|
|
el.addEventListener('error', cleanup);
|
|
try {
|
|
await el.play();
|
|
} catch (err) {
|
|
cleanup();
|
|
console.warn('soundboardLocalPlay failed', err);
|
|
}
|
|
}
|
|
|
|
export function stopSoundboardLocal(): void {
|
|
for (const el of activeAudios) {
|
|
try {
|
|
el.pause();
|
|
el.currentTime = 0;
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
activeAudios.clear();
|
|
}
|