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:
co-authored by
Claude Opus 5.5
parent
75d55e861b
commit
3449338b3e
@@ -29,7 +29,7 @@ import {
|
||||
SoundButton,
|
||||
VideoButton,
|
||||
} from './Controls';
|
||||
import { CallEmbed, useCallControlState } from '../../plugins/call';
|
||||
import { CallEmbed, useCallControlState, useCallMicLevel } from '../../plugins/call';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { callEmbedAtom } from '../../state/callEmbed';
|
||||
@@ -85,6 +85,7 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
|
||||
const { microphone, video, sound, screenshare, spotlight, screenshareAudioMuted } =
|
||||
useCallControlState(callEmbed.control);
|
||||
const micLevel = useCallMicLevel(callEmbed);
|
||||
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
const [shareConfirm, setShareConfirm] = useState(false);
|
||||
@@ -200,7 +201,11 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
>
|
||||
<Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
|
||||
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
|
||||
<MicrophoneButton enabled={microphone} onToggle={handleMicrophoneToggle} />
|
||||
<MicrophoneButton
|
||||
enabled={microphone}
|
||||
onToggle={handleMicrophoneToggle}
|
||||
level={micLevel}
|
||||
/>
|
||||
<SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} />
|
||||
</Box>
|
||||
{!compact && showVideoGroup && <ControlDivider />}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Icon, IconButton, Icons, Line, Text, Tooltip, TooltipProvider } from 'folds';
|
||||
import { useAtom } from 'jotai';
|
||||
import { MicLevelBars } from './MicLevelBars';
|
||||
import * as css from './styles.css';
|
||||
import { MobileTouchTarget } from '../../styles/mobile.css';
|
||||
import { callChatAtom } from '../../state/callEmbed';
|
||||
@@ -15,8 +16,10 @@ export function ControlDivider() {
|
||||
type MicrophoneButtonProps = {
|
||||
enabled: boolean;
|
||||
onToggle: () => Promise<unknown>;
|
||||
/** [Gitea #146] Your input level, 0–3 bars; shown only while unmuted. */
|
||||
level?: number;
|
||||
};
|
||||
export function MicrophoneButton({ enabled, onToggle }: MicrophoneButtonProps) {
|
||||
export function MicrophoneButton({ enabled, onToggle, level = 0 }: MicrophoneButtonProps) {
|
||||
const [micState, toggleMic] = useAsyncCallback(onToggle);
|
||||
const loading = micState.status === AsyncStatus.Loading;
|
||||
|
||||
@@ -43,7 +46,10 @@ export function MicrophoneButton({ enabled, onToggle }: MicrophoneButtonProps) {
|
||||
outlined
|
||||
disabled={loading}
|
||||
>
|
||||
<Icon size="400" src={enabled ? Icons.Mic : Icons.MicMute} filled={!enabled} />
|
||||
<span style={{ position: 'relative', display: 'inline-flex' }}>
|
||||
<Icon size="400" src={enabled ? Icons.Mic : Icons.MicMute} filled={!enabled} />
|
||||
{enabled && <MicLevelBars level={level} />}
|
||||
</span>
|
||||
</IconButton>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { color } from 'folds';
|
||||
|
||||
type MicLevelBarsProps = {
|
||||
/** 0–3; nothing is drawn at 0. */
|
||||
level: number;
|
||||
};
|
||||
|
||||
const HEIGHTS = [3, 5, 7];
|
||||
|
||||
/**
|
||||
* [Gitea #146] Three small bars in the corner of the mic button showing your
|
||||
* own input level while unmuted, so you can see the mic is actually picking
|
||||
* you up. Decorative: the button's label already says whether it's on.
|
||||
*/
|
||||
export function MicLevelBars({ level }: MicLevelBarsProps) {
|
||||
if (level <= 0) return null;
|
||||
return (
|
||||
<span
|
||||
aria-hidden
|
||||
data-mic-level={level}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 1,
|
||||
bottom: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
gap: 1,
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
{HEIGHTS.map((h, i) => (
|
||||
<span
|
||||
key={h}
|
||||
style={{
|
||||
width: 2,
|
||||
height: h,
|
||||
borderRadius: 1,
|
||||
background: i < level ? color.Success.Main : 'transparent',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user