System Mute Indicator

Detect when a participant's microphone or camera is silenced by something outside the participant's control. The operating system muting the track, a Bluetooth headset disconnecting, an iOS audio session interruption, or transient issues.

The default ToggleAudioButton and ToggleVideoButton shipped with the SDK already do this out of the box. This guide is for cases where you build custom call controls or a custom participant label and want the same behavior.

For remote participants, interruption tracking is currently surfaced for TrackType.AUDIO only. Remote video and screen-share interruption are not tracked. For the local participant, both audio and video are covered.

Best Practices

  • Show the indicator only when the user did not intentionally mute. Distinguish "paused by your system" from "muted by choice."
  • Place the indicator next to the relevant control (mic toggle or camera toggle) so the cause-and-effect is clear.
  • Don't disable the toggle button — let the user attempt to recover once the underlying cause clears (for example, reconnecting headphones).
  • Dismiss the indicator automatically once the track resumes producing media. The interruptedTracks array updates reactively, so simply re-reading it on each render is enough.

Detecting interruption

Every participant exposes an optional interruptedTracks: TrackType[] array on the StreamVideoParticipant object. A track listed there is a track the participant intends to publish, but that is currently not producing media.

import { SfuModels, useCallStateHooks } from "@stream-io/video-react-sdk";

const useIsMicInterrupted = () => {
  const { useLocalParticipant } = useCallStateHooks();
  const localParticipant = useLocalParticipant();
  return !!localParticipant?.interruptedTracks?.includes(
    SfuModels.TrackType.AUDIO,
  );
};

The same pattern works for video. Swap TrackType.AUDIO for TrackType.VIDEO.

Building a custom mic-status indicator

Wrap the hook into a small component that renders next to your microphone toggle:

import { SfuModels, useCallStateHooks } from "@stream-io/video-react-sdk";

export const MicSystemMuteIndicator = () => {
  const { useLocalParticipant } = useCallStateHooks();
  const localParticipant = useLocalParticipant();

  const isMicInterrupted = !!localParticipant?.interruptedTracks?.includes(
    SfuModels.TrackType.AUDIO,
  );

  if (!isMicInterrupted) return null;
  return (
    <span role="status" aria-live="polite">
      Microphone is paused by your system
    </span>
  );
};

Drop it into a custom control bar alongside your toggle buttons:

const CustomCallControls = () => {
  return (
    <div className="call-controls">
      <ToggleAudioPublishingButton />
      <MicSystemMuteIndicator />
      <ToggleVideoPublishingButton />
    </div>
  );
};

Reflecting interruption on the toggle button itself

If you build a custom mic toggle (instead of using ToggleAudioButton), reflect interruption in the button's tooltip rather than its on/off state. The participant has not turned the mic off, the system did:

import {
  SfuModels,
  useCallStateHooks,
  useMicrophoneState,
} from "@stream-io/video-react-sdk";

const CustomMicToggle = () => {
  const { microphone, isMute } = useMicrophoneState();
  const { useLocalParticipant } = useCallStateHooks();
  const localParticipant = useLocalParticipant();

  const isSystemMuted = !!localParticipant?.interruptedTracks?.includes(
    SfuModels.TrackType.AUDIO,
  );

  const title = isSystemMuted
    ? "Microphone is paused by your system"
    : isMute
      ? "Turn on microphone"
      : "Turn off microphone";

  return (
    <button title={title} onClick={() => microphone.toggle()}>
      {/* your icon */}
    </button>
  );
};

This mirrors what the SDK's built-in ToggleAudioButton does internally.

See also