98 lines
2.8 KiB
JavaScript
98 lines
2.8 KiB
JavaScript
// src/utils/soundManager.js
|
|
// Handles audio init, autoplay unlock, and queued plays.
|
|
// When a tab successfully unlocks audio, it CLAIMS LEADERSHIP immediately for that bodyshop.
|
|
|
|
import { claimLeadershipNow } from "./singleTabAudioLeader";
|
|
|
|
let baseAudio = null;
|
|
let unlocked = false;
|
|
let queuedPlays = 0;
|
|
let installingUnlockHandlers = false;
|
|
|
|
/**
|
|
* Initialize the new-message sound.
|
|
* @param {string} url
|
|
* @param {number} volume
|
|
*/
|
|
export function initNewMessageSound(url, volume = 0.7) {
|
|
baseAudio = new Audio(url);
|
|
baseAudio.preload = "auto";
|
|
baseAudio.volume = volume;
|
|
}
|
|
|
|
/** Has this tab unlocked audio? (optional helper) */
|
|
export function isAudioUnlocked() {
|
|
return unlocked;
|
|
}
|
|
|
|
/**
|
|
* Unlocks audio if not already unlocked.
|
|
* On success, this tab immediately becomes the sound LEADER for the given bodyshop.
|
|
*/
|
|
export async function unlockAudio(bodyshopId) {
|
|
if (unlocked) return;
|
|
try {
|
|
// Chrome/Safari: playing any media (even muted) after a gesture unlocks audio.
|
|
const a = new Audio();
|
|
a.muted = true;
|
|
await a.play().catch(() => {
|
|
// ignore
|
|
});
|
|
unlocked = true;
|
|
|
|
// Immediately become the leader because THIS tab can actually play sound.
|
|
claimLeadershipNow(bodyshopId);
|
|
|
|
// Flush exactly one queued ding (avoid spamming if many queued while locked)
|
|
if (queuedPlays > 0 && baseAudio) {
|
|
queuedPlays = 0;
|
|
const b = baseAudio.cloneNode(true);
|
|
b.play().catch(() => {
|
|
// ignore
|
|
});
|
|
}
|
|
} finally {
|
|
removeUnlockListeners();
|
|
}
|
|
}
|
|
|
|
/** Installs listeners to unlock audio on first gesture. */
|
|
function addUnlockListeners(bodyshopId) {
|
|
if (installingUnlockHandlers) return;
|
|
installingUnlockHandlers = true;
|
|
const handler = () => unlockAudio(bodyshopId);
|
|
window.addEventListener("click", handler, { once: true, passive: true });
|
|
window.addEventListener("touchstart", handler, { once: true, passive: true });
|
|
window.addEventListener("keydown", handler, { once: true });
|
|
}
|
|
|
|
/** Removes listeners to unlock audio on first gesture. */
|
|
function removeUnlockListeners() {
|
|
// With {once:true} they self-remove; we only reset the flag.
|
|
installingUnlockHandlers = false;
|
|
}
|
|
|
|
/**
|
|
* Plays the new-message ding. If blocked, queue one and wait for first gesture.
|
|
*/
|
|
export async function playNewMessageSound(bodyshopId) {
|
|
if (!baseAudio) return;
|
|
try {
|
|
const a = baseAudio.cloneNode(true);
|
|
await a.play();
|
|
} catch (err) {
|
|
// Most common: NotAllowedError due to missing prior gesture
|
|
if (err?.name === "NotAllowedError") {
|
|
queuedPlays = Math.min(queuedPlays + 1, 1); // cap at 1
|
|
addUnlockListeners(bodyshopId);
|
|
|
|
// Let the app know we need user interaction (optional UI prompt)
|
|
window.dispatchEvent(new CustomEvent("sound-needs-unlock"));
|
|
return;
|
|
}
|
|
// Other errors can be logged
|
|
|
|
console.error("Audio play error:", err);
|
|
}
|
|
}
|