feature/IO-3377-Add-Notification-Tone-For-Messaging - Finalize

This commit is contained in:
Dave
2025-09-24 14:32:45 -04:00
parent e11260e8fc
commit 5ae2e33596
6 changed files with 221 additions and 43 deletions

View File

@@ -1,3 +1,9 @@
// 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;
@@ -5,8 +11,8 @@ let installingUnlockHandlers = false;
/**
* Initialize the new-message sound.
* @param url
* @param volume
* @param {string} url
* @param {number} volume
*/
export function initNewMessageSound(url, volume = 0.7) {
baseAudio = new Audio(url);
@@ -14,27 +20,35 @@ export function initNewMessageSound(url, volume = 0.7) {
baseAudio.volume = volume;
}
/** Has this tab unlocked audio? (optional helper) */
export function isAudioUnlocked() {
return unlocked;
}
/**
* Unlocks audio if not already unlocked.
* @returns {Promise<void>}
* On success, this tab immediately becomes the sound LEADER for the given bodyshop.
*/
export async function unlockAudio() {
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 {
@@ -42,31 +56,26 @@ export async function unlockAudio() {
}
}
/**
* Installs listeners to unlock audio on first gesture.
*/
function addUnlockListeners() {
/** Installs listeners to unlock audio on first gesture. */
function addUnlockListeners(bodyshopId) {
if (installingUnlockHandlers) return;
installingUnlockHandlers = true;
const handler = () => unlockAudio();
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.
*/
/** Removes listeners to unlock audio on first gesture. */
function removeUnlockListeners() {
// No need to remove explicitly with {once:true}, but keep this if you change it later
// 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.
* @returns {Promise<void>}
*/
export async function playNewMessageSound() {
export async function playNewMessageSound(bodyshopId) {
if (!baseAudio) return;
try {
const a = baseAudio.cloneNode(true);
@@ -75,14 +84,14 @@ export async function playNewMessageSound() {
// Most common: NotAllowedError due to missing prior gesture
if (err?.name === "NotAllowedError") {
queuedPlays = Math.min(queuedPlays + 1, 1); // cap at 1
addUnlockListeners();
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);
}
}