feat(call): mic level meter on the mute button (#146)

While unmuted, the microphone button (call view and the bottom call bar)
shows three small green bars in the icon's corner driven by the fork's
io.lotus.mic_level (element-call v0.25.0-lotus.16), so you can see the mic is
actually picking you up. Nothing is drawn while muted or silent; the button's
label is unchanged (the bars are decorative).

Your own name no longer appears in the "… is speaking" line: that is now for
everyone else, and your voice is on the meter. Avatar speaking rings still
include you, and the "You're muted" talking-while-muted notice is unchanged.

Verified in a local call with a fake-tone mic: data-mic-level cycles 3/2/1
with the tone on both buttons, disappears on mute and returns on unmute; the
status line reads "bob is speaking..." only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-25 12:09:34 -04:00
co-authored by Claude Opus 5.5
parent 75d55e861b
commit 3449338b3e
7 changed files with 123 additions and 11 deletions
+13 -1
View File
@@ -3,9 +3,10 @@ import {
IWidgetApiAcknowledgeResponseData,
IWidgetApiRequestData,
} from 'matrix-widget-api';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';
import { CallControl, CallControlEvent } from './CallControl';
import { CallControlState } from './CallControlState';
import type { CallEmbed } from './CallEmbed';
export const useClientWidgetApiEvent = <T>(
api: ClientWidgetApi | undefined,
@@ -61,3 +62,14 @@ export const useCallControlState = (control: CallControl | undefined): CallContr
if (entry.control !== control) return control?.getState() ?? DEFAULT_CONTROL_STATE;
return entry.state;
};
const noSubscribe = () => () => undefined;
/** [Gitea #146] The local mic level (0–3 bars) reported by the call, or 0. */
export const useCallMicLevel = (callEmbed: CallEmbed | undefined): number => {
const subscribe = useCallback(
(cb: () => void) => (callEmbed ? callEmbed.onMicLevel(cb) : noSubscribe()),
[callEmbed],
);
return useSyncExternalStore(subscribe, () => callEmbed?.getMicLevel() ?? 0);
};