feat: add mic mute button with visual state
This commit is contained in:
+21
@@ -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", {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user