[object Object]

← back to Butlr

live page: chime on every new call (two-tone Web Audio beep, fires on new call ids)

4ef812b656844f06a0993fbc92c635cea1ccc86c · 2026-05-26 10:00:56 -0700 · SteveStudio2

Files touched

Diff

commit 4ef812b656844f06a0993fbc92c635cea1ccc86c
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Tue May 26 10:00:56 2026 -0700

    live page: chime on every new call (two-tone Web Audio beep, fires on new call ids)
---
 routes/live.js | 42 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 41 insertions(+), 1 deletion(-)

diff --git a/routes/live.js b/routes/live.js
index 38a0de5..ef43e30 100644
--- a/routes/live.js
+++ b/routes/live.js
@@ -112,7 +112,7 @@ function renderLive(calls) {
     <span class="count"><span id="liveCount">${live.length}</span> active</span>
     <span class="sub" style="margin-left:auto">auto-refresh 3s · click 🔊 Listen to hear live (no page reload)</span>
   </header>
-  <div id="audio-unlock-banner" onclick="unlockAudio()" style="background:#22c55e;color:#022;text-align:center;padding:10px;font-weight:700;cursor:pointer;font-size:14px;">🔊 CLICK HERE ONCE TO ENABLE LIVE AUDIO — then calls play automatically as they connect</div>
+  <div id="audio-unlock-banner" onclick="unlockAudio()" style="background:#22c55e;color:#022;text-align:center;padding:10px;font-weight:700;cursor:pointer;font-size:14px;">🔊 CLICK HERE ONCE TO ENABLE SOUND — chime on every new call + live audio plays automatically as calls connect</div>
   <div style="display:none">
   </div>
   <main id="grid">${cardsHtml}</main>
@@ -128,6 +128,14 @@ function renderLive(calls) {
   // Map of callId → { ws, audioCtx, btn, stateEl }
   const sessions = new Map();
 
+  // Call IDs we've already announced with a chime. Seeded on load from the
+  // server-rendered cards so we DON'T beep for calls already on screen —
+  // only for genuinely new calls that arrive after the page is open.
+  const seenCallIds = new Set();
+  document.querySelectorAll('#grid .card[data-id]').forEach(function(el){
+    seenCallIds.add(el.dataset.id);
+  });
+
   // Shared AudioContext — browsers require a user gesture to start audio.
   // We unlock ONE context on the first click anywhere, then reuse it for every
   // call so auto-play-on-connect actually produces sound.
@@ -156,6 +164,30 @@ function renderLive(calls) {
   document.addEventListener('click', unlockAudio, { once: false });
   document.addEventListener('keydown', unlockAudio, { once: false });
 
+  // Two-tone ascending chime (no audio asset needed) — fired whenever a NEW
+  // call appears in the grid so Steve hears "a call is being made" even before
+  // the called party picks up (the live stream is silent until they speak).
+  // Needs the shared AudioContext unlocked first (the green banner does that).
+  function playChime() {
+    if (!audioUnlocked) return;
+    try {
+      const ctx = ensureCtx();
+      const t0 = ctx.currentTime + 0.01;
+      [[880, t0, 0.12], [1318.5, t0 + 0.13, 0.18]].forEach(function(spec){
+        const freq = spec[0], start = spec[1], dur = spec[2];
+        const osc = ctx.createOscillator();
+        const gain = ctx.createGain();
+        osc.type = 'sine';
+        osc.frequency.value = freq;
+        gain.gain.setValueAtTime(0.0001, start);
+        gain.gain.exponentialRampToValueAtTime(0.25, start + 0.02);
+        gain.gain.exponentialRampToValueAtTime(0.0001, start + dur);
+        osc.connect(gain); gain.connect(ctx.destination);
+        osc.start(start); osc.stop(start + dur + 0.02);
+      });
+    } catch (e) {}
+  }
+
   function getScheme() { return location.protocol === 'https:' ? 'wss:' : 'ws:'; }
 
   function startListen(callId, sampleRate, btn, stateEl) {
@@ -251,6 +283,14 @@ function renderLive(calls) {
       const liveIds = new Set(calls.map(c => c.id));
       document.getElementById('liveCount').textContent = calls.length;
 
+      // Chime for any call we haven't announced yet — a call was just made.
+      calls.forEach((c) => {
+        if (!seenCallIds.has(c.id)) {
+          seenCallIds.add(c.id);
+          playChime();
+        }
+      });
+
       // Remove cards for calls that ended — unless we're listening to them (keep so user
       // can finish hearing the wind-down before WS closes from the server side).
       document.querySelectorAll('.card').forEach((el) => {

← 56de574 live page: completed-calls history + reconcile prod drift  ·  back to Butlr  ·  butlr: live-listen now broadcasts BOTH call tracks (inbound 5ed99e3 →