-
-
Notifications
You must be signed in to change notification settings - Fork 228
Open
Description
When notification sounds are enabled, playing a sound causes the app to appear in the macOS Now Playing widget (Control Center) as if it were media playback, complete with play/pause controls:
This can also cause AirPods to switch focus from one device to another which can be annoying if trying to listen to music on one device and work on another.
It happens because src/utils/notificationSounds.ts uses new Audio(url) / audio.play(), which macOS treats as media content regardless of duration. A way to fix this is to switch from HTMLAudioElement to the Web Audio API - it shouldn't register with the OS media session.
I've tested a fix for this locally that appears to work as expected:
src/utils/notificationSounds.ts
import type { DebugEntry } from "../types";
type DebugLogger = (entry: DebugEntry) => void;
type SoundLabel = "success" | "error" | "test";
let audioContext: AudioContext | null = null;
function getAudioContext(): AudioContext {
if (!audioContext || audioContext.state === "closed") {
audioContext = new AudioContext();
}
return audioContext;
}
export function playNotificationSound(
url: string,
label: SoundLabel,
onDebug?: DebugLogger,
) {
try {
const ctx = getAudioContext();
if (ctx.state === "suspended") {
void ctx.resume();
}
fetch(url)
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => ctx.decodeAudioData(arrayBuffer))
.then((audioBuffer) => {
const source = ctx.createBufferSource();
const gainNode = ctx.createGain();
gainNode.gain.value = 0.05;
source.buffer = audioBuffer;
source.connect(gainNode);
gainNode.connect(ctx.destination);
source.start();
})
.catch((error) => {
onDebug?.({
id: `${Date.now()}-audio-${label}-play-error`,
timestamp: Date.now(),
source: "error",
label: `audio/${label} play error`,
payload: error instanceof Error ? error.message : String(error),
});
});
} catch (error) {
onDebug?.({
id: `${Date.now()}-audio-${label}-init-error`,
timestamp: Date.now(),
source: "error",
label: `audio/${label} init error`,
payload: error instanceof Error ? error.message : String(error),
});
}
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels