fix: replace new Audio() with howler.js for reliable sound playback + error logging
Some checks failed
Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Deploy / build-and-deploy (push) Has been cancelled
This commit is contained in:
@@ -8,6 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
|||||||
import { SOUND_CATEGORIES, EMOJIS } from "@/lib/bingo-data";
|
import { SOUND_CATEGORIES, EMOJIS } from "@/lib/bingo-data";
|
||||||
import { Badge } from "./ui/badge";
|
import { Badge } from "./ui/badge";
|
||||||
import { ResourceBrowser, DragData } from "./ResourceBrowser";
|
import { ResourceBrowser, DragData } from "./ResourceBrowser";
|
||||||
|
import { playSoundOnce } from "@/lib/sounds";
|
||||||
|
|
||||||
type Item = {
|
type Item = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -200,10 +201,6 @@ export function ItemEditor({ campaign }: { campaign: Campaign }) {
|
|||||||
setUploading(false);
|
setUploading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const playSoundOnce = (url: string) => {
|
|
||||||
try { const a = new Audio(url); a.preload = "auto"; a.volume = 0.4; a.play().catch(() => {}); } catch {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ─── Drag & Drop handlers ───────────────────────────────────────
|
// ─── Drag & Drop handlers ───────────────────────────────────────
|
||||||
|
|
||||||
const handleDragOver = useCallback((e: React.DragEvent, idx: number) => {
|
const handleDragOver = useCallback((e: React.DragEvent, idx: number) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from "react";
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import { Howl } from "howler";
|
import { playSoundOnce } from "@/lib/sounds";
|
||||||
|
|
||||||
// ─── Types ──────────────────────────────────────────────────────────
|
// ─── Types ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -33,19 +33,6 @@ export type DragData =
|
|||||||
| { type: "image"; url: string; originalName: string }
|
| { type: "image"; url: string; originalName: string }
|
||||||
| { type: "emoji"; emoji: string };
|
| { type: "emoji"; emoji: string };
|
||||||
|
|
||||||
// ─── Howl helper ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const howlCache = new Map<string, Howl>();
|
|
||||||
|
|
||||||
function playPreview(url: string) {
|
|
||||||
let h = howlCache.get(url);
|
|
||||||
if (!h) {
|
|
||||||
h = new Howl({ src: [url], format: ["mp3", "ogg"], volume: 0.4, preload: true });
|
|
||||||
howlCache.set(url, h);
|
|
||||||
}
|
|
||||||
h.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── Emoji Picker (inline, searchable) ──────────────────────────────
|
// ─── Emoji Picker (inline, searchable) ──────────────────────────────
|
||||||
|
|
||||||
const EMOJI_LIST = [
|
const EMOJI_LIST = [
|
||||||
@@ -211,7 +198,7 @@ function SoundsTab() {
|
|||||||
label={s.originalName}
|
label={s.originalName}
|
||||||
duration={s.duration}
|
duration={s.duration}
|
||||||
url={s.url}
|
url={s.url}
|
||||||
onPlay={() => playPreview(s.url)}
|
onPlay={() => playSoundOnce(s.url)}
|
||||||
onDelete={() => deleteSound(filename)}
|
onDelete={() => deleteSound(filename)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -244,7 +231,7 @@ function SoundsTab() {
|
|||||||
key={r.id}
|
key={r.id}
|
||||||
name={r.name}
|
name={r.name}
|
||||||
duration={r.duration}
|
duration={r.duration}
|
||||||
onPreview={() => r.previewUrl && playPreview(r.previewUrl)}
|
onPreview={() => r.previewUrl && playSoundOnce(r.previewUrl)}
|
||||||
onImport={() => importSound(r)}
|
onImport={() => importSound(r)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { Howl } from "howler";
|
||||||
|
|
||||||
let audioCtx: AudioContext | null = null;
|
let audioCtx: AudioContext | null = null;
|
||||||
|
|
||||||
function getCtx(): AudioContext {
|
function getCtx(): AudioContext {
|
||||||
@@ -107,13 +109,50 @@ export function playChaosRiser() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Howl-based URL playback ──────────────────────────────────────
|
||||||
|
|
||||||
|
const howlCache = new Map<string, Howl>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a sound file URL (MP3/OGG) using howler.js.
|
||||||
|
* Howler handles autoplay policy, cross-browser codec support,
|
||||||
|
* and AudioContext resume better than raw new Audio().
|
||||||
|
*/
|
||||||
export function playSoundUrl(url: string) {
|
export function playSoundUrl(url: string) {
|
||||||
try {
|
let h = howlCache.get(url);
|
||||||
const audio = new Audio(url);
|
if (!h) {
|
||||||
audio.preload = "auto";
|
h = new Howl({
|
||||||
audio.volume = 0.4;
|
src: [url],
|
||||||
audio.play().catch(() => {});
|
format: url.endsWith(".mp3") ? ["mp3"] : url.endsWith(".ogg") ? ["ogg"] : ["mp3", "ogg"],
|
||||||
} catch {}
|
volume: 0.4,
|
||||||
|
onloaderror: (_id: number, err: unknown) => {
|
||||||
|
console.error("[BaraBingo] Howl load error:", url, err);
|
||||||
|
},
|
||||||
|
onplayerror: (_id: number, err: unknown) => {
|
||||||
|
console.error("[BaraBingo] Howl play error:", url, err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
howlCache.set(url, h);
|
||||||
|
}
|
||||||
|
h.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One-shot play of a sound URL. Same as playSoundUrl but always creates a fresh instance.
|
||||||
|
* Useful for preview/editor buttons where you want it to play every click.
|
||||||
|
*/
|
||||||
|
export function playSoundOnce(url: string) {
|
||||||
|
new Howl({
|
||||||
|
src: [url],
|
||||||
|
format: url.endsWith(".mp3") ? ["mp3"] : url.endsWith(".ogg") ? ["ogg"] : ["mp3", "ogg"],
|
||||||
|
volume: 0.4,
|
||||||
|
onloaderror: (_id: number, err: unknown) => {
|
||||||
|
console.error("[BaraBingo] Howl load error:", url, err);
|
||||||
|
},
|
||||||
|
onplayerror: (_id: number, err: unknown) => {
|
||||||
|
console.error("[BaraBingo] Howl play error:", url, err);
|
||||||
|
},
|
||||||
|
}).play();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function playSound(category: string, soundUrl?: string | null) {
|
export function playSound(category: string, soundUrl?: string | null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user