feat: add mic mute button with visual state

This commit is contained in:
Shane
2026-08-22 15:51:59 -04:00
parent 510a577761
commit c2f4c62aad
3 changed files with 30 additions and 0 deletions
+21
View File
@@ -39,6 +39,8 @@ let wakeLock = null;
// ── DOM ───────────────────────────────────────────────────────────────────── // ── DOM ─────────────────────────────────────────────────────────────────────
const startBtn = document.getElementById("startBtn"); const startBtn = document.getElementById("startBtn");
const stopBtn = document.getElementById("stopBtn"); const stopBtn = document.getElementById("stopBtn");
const muteBtn = document.getElementById("muteBtn");
let micMuted = false;
const statusEl = document.getElementById("status"); const statusEl = document.getElementById("status");
const messagesEl = document.getElementById("messages"); const messagesEl = document.getElementById("messages");
const orbEl = document.getElementById("orb"); const orbEl = document.getElementById("orb");
@@ -600,6 +602,8 @@ startBtn.addEventListener("click", async () => {
statusEl.textContent = "Connected — speak now"; statusEl.textContent = "Connected — speak now";
startBtn.disabled = true; startBtn.disabled = true;
stopBtn.disabled = false; stopBtn.disabled = false;
muteBtn.disabled = false;
micMuted = false;
setupMicAnalyser(); setupMicAnalyser();
startVisualLoop(); startVisualLoop();
@@ -627,8 +631,25 @@ stopBtn.addEventListener("click", () => {
setState("idle"); setState("idle");
startBtn.disabled = false; startBtn.disabled = false;
stopBtn.disabled = true; stopBtn.disabled = true;
muteBtn.disabled = true;
micMuted = false;
updateMuteIcon();
}); });
// ── Mic mute toggle ─────────────────────────────────────────────────────────
muteBtn.addEventListener("click", async () => {
if (!room || !room.localParticipant) return;
micMuted = !micMuted;
await room.localParticipant.setMicrophoneEnabled(!micMuted);
updateMuteIcon();
});
function updateMuteIcon() {
const muted = muteBtn.classList.toggle("muted", micMuted);
muteBtn.setAttribute("aria-label", muted ? "Unmute microphone" : "Mute microphone");
muteBtn.title = muted ? "Unmute microphone" : "Mute microphone";
}
// ── Helper: fetch a signed access token from the token endpoint ──────────── // ── Helper: fetch a signed access token from the token endpoint ────────────
async function fetchToken(roomName) { async function fetchToken(roomName) {
const resp = await fetch("/token", { const resp = await fetch("/token", {
+3
View File
@@ -31,6 +31,9 @@
<div class="controls"> <div class="controls">
<button id="startBtn" class="btn btn-primary">Start Conversation</button> <button id="startBtn" class="btn btn-primary">Start Conversation</button>
<button id="stopBtn" class="btn btn-danger" disabled>Stop</button> <button id="stopBtn" class="btn btn-danger" disabled>Stop</button>
<button id="muteBtn" class="btn btn-icon" aria-label="Mute microphone" title="Mute microphone" disabled>
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="8" y1="22" x2="16" y2="22"></line></svg>
</button>
<button id="settingsBtn" class="btn btn-icon" aria-label="Voice settings" title="Voice settings"> <button id="settingsBtn" class="btn btn-icon" aria-label="Voice settings" title="Voice settings">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg> <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 1 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 1 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
</button> </button>
+6
View File
@@ -243,6 +243,12 @@ h1 {
background: var(--panel-strong); background: var(--panel-strong);
} }
.btn-icon.muted {
color: #ef4444;
border-color: #ef4444;
background: rgba(239, 68, 68, 0.1);
}
.btn-ghost { .btn-ghost {
background: transparent; background: transparent;
color: var(--text-faint); color: var(--text-faint);