Audio: "Tap to enable audio" overlay when context is suspended

Instead of silently dropping notes or hoping gesture listeners fire,
show a clear overlay on first packet if AudioContext is suspended.
One tap resumes context and removes overlay. Standard pattern used
by every browser game/music site.
This commit is contained in:
you committed 2026-03-22 17:31:23 +00:00
1 parent 37b89315a4
commit 7514111f5c
3 files changed
+52 -16

No files matched your search

+22 -14
View File
@@ -88,11 +88,13 @@
function sonifyPacket(pkt) {
if (!audioEnabled || !currentVoice) return;
// Lazy-init AudioContext on first actual packet (may not have user gesture,
// but browser will auto-resume when it gets one)
if (!audioCtx) initAudio();
if (!audioCtx) return;
if (audioCtx.state === 'suspended') audioCtx.resume();
if (audioCtx.state === 'suspended') {
// Show unlock overlay if not already showing
_showUnlockOverlay();
return; // don't schedule notes on suspended context
}
if (activeVoices >= MAX_VOICES) return;
const parsed = parsePacketBytes(pkt);
@@ -175,22 +177,28 @@
const savedVoice = localStorage.getItem('live-audio-voice');
if (savedVoice) setVoice(savedVoice);
// If audio was enabled, create context now and try to resume.
// Browsers with prior engagement will auto-allow; gesture listener is fallback.
// If audio was enabled, create context eagerly. If browser suspends it,
// the unlock overlay will appear when the first packet arrives.
if (audioEnabled) {
initAudio();
const unlockAudio = () => {
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
document.removeEventListener('click', unlockAudio, true);
document.removeEventListener('touchstart', unlockAudio, true);
document.removeEventListener('keydown', unlockAudio, true);
};
document.addEventListener('click', unlockAudio, true);
document.addEventListener('touchstart', unlockAudio, true);
document.addEventListener('keydown', unlockAudio, true);
}
}
let _overlayShown = false;
function _showUnlockOverlay() {
if (_overlayShown) return;
_overlayShown = true;
const overlay = document.createElement('div');
overlay.className = 'audio-unlock-overlay';
overlay.innerHTML = '<div class="audio-unlock-prompt">🔊 Tap to enable audio</div>';
overlay.addEventListener('click', () => {
if (audioCtx) audioCtx.resume();
overlay.remove();
}, { once: true });
document.body.appendChild(overlay);
}
// Export engine + helpers for voice modules
window.MeshAudio = {
sonifyPacket,
+2 -2
View File
@@ -22,7 +22,7 @@
<meta name="twitter:title" content="MeshCore Analyzer">
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/meshcore-analyzer/master/public/og-image.png">
<link rel="stylesheet" href="style.css?v=1774199178">
<link rel="stylesheet" href="style.css?v=1774200683">
<link rel="stylesheet" href="home.css">
<link rel="stylesheet" href="live.css?v=1774058575">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
@@ -90,7 +90,7 @@
<script src="nodes.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
<script src="traces.js?v=1774135052" onerror="console.error('Failed to load:', this.src)"></script>
<script src="analytics.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio.js?v=1774200474" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio.js?v=1774200683" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio-v1-constellation.js?v=1774199178" onerror="console.error('Failed to load:', this.src)"></script>
<script src="live.js?v=1774199178" onerror="console.error('Failed to load:', this.src)"></script>
<script src="observers.js?v=1774290000" onerror="console.error('Failed to load:', this.src)"></script>
+28
View File
@@ -1640,3 +1640,31 @@ tr[data-hops]:hover { background: rgba(59,130,246,0.1); }
color: #00ff41 !important;
border-color: #00ff4130 !important;
}
/* Audio unlock overlay */
.audio-unlock-overlay {
position: fixed;
inset: 0;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.6);
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.audio-unlock-prompt {
background: #1f2937;
color: #e5e7eb;
padding: 24px 40px;
border-radius: 12px;
font-size: 20px;
font-weight: 600;
box-shadow: 0 4px 24px rgba(0,0,0,0.5);
user-select: none;
}
.matrix-theme .audio-unlock-prompt {
background: #001a00;
color: #00ff41;
box-shadow: 0 0 30px rgba(0,255,65,0.2);
}