= ({
screen for as long as any microphone is. */}
{/* The capture is bound to the menu being open, so the
- microphone is only ever held while the user is looking
- at the level. */}
+ microphone is only ever held while the user is looking at
+ the level. */}
{
expect(result.current).toEqual({ type: "unavailable" });
});
+ test("a missing microphone is reported as absent, not as silence", () => {
+ const getUserMedia = vi.fn();
+ vi.stubGlobal("navigator", { mediaDevices: { getUserMedia } });
+
+ const { result } = renderHook(() =>
+ useMicrophoneLevel(undefined, true, STEPS),
+ );
+ expect(result.current).toEqual({ type: "absent" });
+ expect(getUserMedia).not.toHaveBeenCalled();
+ });
+
test("no capture is taken while metering is disabled", () => {
const getUserMedia = vi.fn();
vi.stubGlobal("navigator", { mediaDevices: { getUserMedia } });
diff --git a/src/components/useMicrophoneLevel.ts b/src/components/useMicrophoneLevel.ts
index 9181a27f2..b68b326b9 100644
--- a/src/components/useMicrophoneLevel.ts
+++ b/src/components/useMicrophoneLevel.ts
@@ -12,13 +12,16 @@ import { logger as rootLogger } from "matrix-js-sdk/lib/logger";
* The state of the microphone level indicator.
*
* `inactive` is the resting state: nothing is being captured, because nothing
- * has asked for a level yet. `active` means we hold a capture and `level` is
- * the current signal level in the range 0..1. `denied` and `unavailable` are
- * the two failure modes we distinguish, because they need different UI: the
- * first is recoverable by the user, the second is not.
+ * has asked for a level yet. `absent` means there is no microphone to capture
+ * from, which is not the same as one that hears nothing. `active` means we
+ * hold a capture and `level` is the current signal level in the range 0..1.
+ * `denied` and `unavailable` are the two failure modes we distinguish, because
+ * they need different UI: the first is recoverable by the user, the second is
+ * not.
*/
export type MicrophoneLevelState =
| { type: "inactive" }
+ | { type: "absent" }
| { type: "active"; level: number }
| { type: "denied" }
| { type: "unavailable" };
@@ -57,10 +60,14 @@ export function useMicrophoneLevel(
});
useEffect(() => {
- if (!enabled || deviceId === undefined) {
+ if (!enabled) {
setState({ type: "inactive" });
return;
}
+ if (deviceId === undefined) {
+ setState({ type: "absent" });
+ return;
+ }
// Insecure contexts have no media devices at all; nothing can be metered.
if (!("mediaDevices" in navigator)) {
setState({ type: "unavailable" });