Add microphone mute (#158)

* Add code

* add code
This commit is contained in:
Freddy Boulton
2025-03-10 14:53:08 -04:00
committed by GitHub
parent ed59834378
commit 51f1fafa3a
14 changed files with 1579 additions and 1520 deletions

11
frontend/.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"plugins": ["prettier-plugin-svelte"],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

View File

@@ -46,10 +46,7 @@
msg?.type === "warning" || msg?.type === "warning" ||
msg?.type === "error" msg?.type === "error"
) { ) {
gradio.dispatch( gradio.dispatch(msg?.type === "error" ? "error" : "warning", msg.message);
msg?.type === "error" ? "error" : "warning",
msg.message,
);
} else if (msg?.type === "fetch_output") { } else if (msg?.type === "fetch_output") {
gradio.dispatch("state_change"); gradio.dispatch("state_change");
} else if (msg?.type === "send_input") { } else if (msg?.type === "send_input") {

View File

@@ -25,7 +25,8 @@
}, },
"devDependencies": { "devDependencies": {
"@gradio/preview": "0.12.0", "@gradio/preview": "0.12.0",
"prettier": "3.3.3" "prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.3.3"
}, },
"peerDependencies": { "peerDependencies": {
"svelte": "^4.0.0" "svelte": "^4.0.0"
@@ -4118,6 +4119,7 @@
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true, "dev": true,
"license": "MIT",
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -4128,6 +4130,17 @@
"url": "https://github.com/prettier/prettier?sponsor=1" "url": "https://github.com/prettier/prettier?sponsor=1"
} }
}, },
"node_modules/prettier-plugin-svelte": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz",
"integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": "^3.0.0",
"svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0"
}
},
"node_modules/prismjs": { "node_modules/prismjs": {
"version": "1.29.0", "version": "1.29.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",

View File

@@ -23,7 +23,8 @@
}, },
"devDependencies": { "devDependencies": {
"@gradio/preview": "0.12.0", "@gradio/preview": "0.12.0",
"prettier": "3.3.3" "prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.3.3"
}, },
"exports": { "exports": {
"./package.json": "./package.json", "./package.json": "./package.json",

View File

@@ -14,6 +14,7 @@
VolumeHigh, VolumeHigh,
Microphone, Microphone,
} from "@gradio/icons"; } from "@gradio/icons";
import MicrophoneMuted from "./MicrophoneMuted.svelte";
import { start, stop } from "./webrtc_utils"; import { start, stop } from "./webrtc_utils";
import { get_devices, set_available_devices } from "./stream_utils"; import { get_devices, set_available_devices } from "./stream_utils";
@@ -79,6 +80,7 @@
let selected_device: MediaDeviceInfo | null = null; let selected_device: MediaDeviceInfo | null = null;
let mic_accessed = false; let mic_accessed = false;
let is_muted = false; let is_muted = false;
let is_mic_muted = false;
const audio_source_callback = () => { const audio_source_callback = () => {
if (mode === "send") return stream; if (mode === "send") return stream;
@@ -257,9 +259,8 @@
audio: { deviceId: { exact: device_id }, ...track_constraints }, audio: { deviceId: { exact: device_id }, ...track_constraints },
}); });
selected_device = selected_device =
available_audio_devices.find( available_audio_devices.find((device) => device.deviceId === device_id) ||
(device) => device.deviceId === device_id, null;
) || null;
options_open = false; options_open = false;
}; };
@@ -270,6 +271,14 @@
} }
} }
function toggleMuteMicrophone(): void {
if (stream && stream.getAudioTracks().length > 0) {
const audioTrack = stream.getAudioTracks()[0];
audioTrack.enabled = !audioTrack.enabled;
is_mic_muted = !audioTrack.enabled;
}
}
$: if (stopword_recognized) { $: if (stopword_recognized) {
notification_sound.play(); notification_sound.play();
} }
@@ -338,10 +347,7 @@
/> />
</div> </div>
{:else} {:else}
<div <div class="icon color-primary" title="start recording">
class="icon color-primary"
title="start recording"
>
<Circle /> <Circle />
</div> </div>
{/if} {/if}
@@ -383,6 +389,24 @@
</div> </div>
</button> </button>
{/if} {/if}
{#if stream_state === "open" && mode.includes("send")}
<button
class="mute-button"
on:click={toggleMuteMicrophone}
aria-label={is_mic_muted ? "unmute mic" : "mute mic"}
>
<div
class="icon"
style={`fill: ${icon_button_color}; stroke: ${icon_button_color}; color: ${icon_button_color};`}
>
{#if is_mic_muted}
<MicrophoneMuted />
{:else}
<Microphone />
{/if}
</div>
</button>
{/if}
{#if options_open && selected_device} {#if options_open && selected_device}
<select <select
class="select-wrap" class="select-wrap"
@@ -402,8 +426,7 @@
{#each available_audio_devices as device} {#each available_audio_devices as device}
<option <option
value={device.deviceId} value={device.deviceId}
selected={selected_device.deviceId === selected={selected_device.deviceId === device.deviceId}
device.deviceId}
> >
{device.label} {device.label}
</option> </option>

View File

@@ -0,0 +1,20 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="feather feather-mic"
><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" /><path
d="M19 10v2a7 7 0 0 1-14 0v-2"
/><line x1="12" y1="19" x2="12" y2="23" /><line
x1="8"
y1="23"
x2="16"
y2="23"
/><line x1="1" y1="1" x2="23" y2="23" /></svg
>

After

Width:  |  Height:  |  Size: 489 B

View File

@@ -84,10 +84,7 @@
.catch(() => { .catch(() => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
console.info("catching"); console.info("catching");
dispatch( dispatch("error", "Too many concurrent users. Come back later!");
"error",
"Too many concurrent users. Come back later!",
);
}); });
} }
return value; return value;

View File

@@ -70,10 +70,7 @@
}) })
.catch(() => { .catch(() => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
dispatch( dispatch("error", "Too many concurrent users. Come back later!");
"error",
"Too many concurrent users. Come back later!",
);
}); });
} }
</script> </script>

View File

@@ -37,9 +37,9 @@
export let pulse_color: string = "var(--color-accent)"; export let pulse_color: string = "var(--color-accent)";
export let button_labels: { start: string; stop: string; waiting: string }; export let button_labels: { start: string; stop: string; waiting: string };
export const modify_stream: ( export const modify_stream: (state: "open" | "closed" | "waiting") => void = (
state: "open" | "closed" | "waiting", state: "open" | "closed" | "waiting",
) => void = (state: "open" | "closed" | "waiting") => { ) => {
if (state === "closed") { if (state === "closed") {
_time_limit = null; _time_limit = null;
stream_state = "closed"; stream_state = "closed";
@@ -92,12 +92,7 @@
async function access_webcam(): Promise<void> { async function access_webcam(): Promise<void> {
try { try {
get_video_stream( get_video_stream(include_audio, video_source, null, track_constraints)
include_audio,
video_source,
null,
track_constraints,
)
.then(async (local_stream) => { .then(async (local_stream) => {
webcam_accessed = true; webcam_accessed = true;
available_video_devices = await get_devices(); available_video_devices = await get_devices();
@@ -112,16 +107,12 @@
.map((track) => track.getSettings()?.deviceId)[0]; .map((track) => track.getSettings()?.deviceId)[0];
selected_device = used_devices selected_device = used_devices
? devices.find( ? devices.find((device) => device.deviceId === used_devices) ||
(device) => device.deviceId === used_devices, available_video_devices[0]
) || available_video_devices[0]
: available_video_devices[0]; : available_video_devices[0];
}); });
if ( if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
!navigator.mediaDevices ||
!navigator.mediaDevices.getUserMedia
) {
dispatch("error", i18n("image.no_webcam_support")); dispatch("error", i18n("image.no_webcam_support"));
} }
} catch (err) { } catch (err) {
@@ -321,8 +312,7 @@
{#each available_video_devices as device} {#each available_video_devices as device}
<option <option
value={device.deviceId} value={device.deviceId}
selected={selected_device.deviceId === selected={selected_device.deviceId === device.deviceId}
device.deviceId}
> >
{device.label} {device.label}
</option> </option>

View File

@@ -3,7 +3,8 @@
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
export let icon = Webcam; export let icon = Webcam;
$: text = icon === Webcam ? "Click to Access Webcam" : "Click to Access Microphone"; $: text =
icon === Webcam ? "Click to Access Webcam" : "Click to Access Microphone";
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
click: undefined; click: undefined;

View File

@@ -105,7 +105,11 @@ export async function start(
return pc; return pc;
} }
function make_offer(server_fn: any, body, reject_cb: (msg: object) => void = () => { }): Promise<object> { function make_offer(
server_fn: any,
body,
reject_cb: (msg: object) => void = () => {},
): Promise<object> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
server_fn(body).then((data) => { server_fn(body).then((data) => {
console.debug("data", data); console.debug("data", data);
@@ -150,11 +154,15 @@ async function negotiate(
}) })
.then(() => { .then(() => {
var offer = pc.localDescription; var offer = pc.localDescription;
return make_offer(server_fn, { return make_offer(
server_fn,
{
sdp: offer.sdp, sdp: offer.sdp,
type: offer.type, type: offer.type,
webrtc_id: webrtc_id, webrtc_id: webrtc_id,
}, reject_cb); },
reject_cb,
);
}) })
.then((response) => { .then((response) => {
return response; return response;

View File

@@ -52,6 +52,7 @@ format:
ruff format . ruff format .
ruff check --fix . ruff check --fix .
ruff check --select I --fix . ruff check --select I --fix .
cd frontend && npx prettier --write . && cd ..
docs: docs:
mkdocs serve -a localhost:8081 mkdocs serve -a localhost:8081