# Speaking While Muted

It's a UI best practice to show visual feedback when the user is speaking while muted. The Flutter SDK ships a ready-made helper for this: `SpeakingWhileMutedRecognition`.

## How It Works

`SpeakingWhileMutedRecognition` observes the call state and automatically starts audio detection when the local participant is muted (and has permission to send audio). While active, it emits a `SpeakingWhileMutedState` whenever speech is detected or stops. Detection stops automatically when the user unmutes or the call disconnects.

The state only changes on a transition, so you get one `isSpeakingWhileMuted: true` per detected utterance rather than a continuous stream. It returns to `false` shortly after speech stops — or, if the platform doesn't report the end, after a few seconds — so the next utterance is reported again.

## Showing a Notification

This example shows a snackbar when the user is speaking while muted:

```dart
class CallScreenState extends State<CallScreen> {
  late SpeakingWhileMutedRecognition _speakingWhileMutedRecognition;
  late StreamSubscription<SpeakingWhileMutedState> _speechSubscription;

  @override
  void initState() {
    super.initState();
    _speakingWhileMutedRecognition =
        SpeakingWhileMutedRecognition(call: widget.call);
    _speechSubscription =
        _speakingWhileMutedRecognition.stream.listen((state) {
      if (state.isSpeakingWhileMuted && context.mounted) {
        ScaffoldMessenger.maybeOf(context)?.showSnackBar(
          const SnackBar(
            content: Text('You are speaking while muted'),
            behavior: SnackBarBehavior.floating,
          ),
        );
      }
    });
  }

  @override
  void dispose() {
    _speechSubscription.cancel();
    _speakingWhileMutedRecognition.dispose();
    super.dispose();
  }
  // ...
}
```

### Throttling Notifications

Because each utterance is reported separately, a user who keeps talking while muted triggers the listener repeatedly, and snackbars stack up. Throttle them. The sample app waits a second before showing one (so a momentary blip is ignored) and then holds a five second cooldown:

```dart
Timer? _debounce;
DateTime? _lastShownAt;

void _onSpeakingWhileMutedChanged(SpeakingWhileMutedState state) {
  if (!state.isSpeakingWhileMuted) {
    _debounce?.cancel();
    _debounce = null;
    return;
  }

  if (_debounce?.isActive ?? false) return;

  _debounce = Timer(const Duration(seconds: 1), () {
    if (!mounted) return;

    final now = DateTime.now();
    if (_lastShownAt != null &&
        now.difference(_lastShownAt!) < const Duration(seconds: 5)) {
      return;
    }

    _lastShownAt = now;
    ScaffoldMessenger.maybeOf(context)?.showSnackBar(
      const SnackBar(
        content: Text('You are muted. Unmute to speak.'),
        behavior: SnackBarBehavior.floating,
      ),
    );
  });
}
```

Remember to cancel the timer in `dispose()`.

## Platform Requirements

| Platform        | Supported | Requirements                                                                      |
| --------------- | --------- | --------------------------------------------------------------------------------- |
| Android         | ✅        | Works with the default mute.                                                      |
| iOS / macOS     | ✅        | Mute with `stopTrackOnMute: false` (see below).                                   |
| Web             | ✅        | Works out of the box.                                                             |
| Windows / Linux | ⚠️        | Not supported by the default implementation — supply a custom `AudioRecognition`. |

### iOS and macOS

On Apple platforms, speech events are produced by the audio engine's muted-talker detection, which only works while the microphone capture keeps running. This means the microphone must be muted **without stopping the audio track**:

```dart
await call.setMicrophoneEnabled(enabled: false, stopTrackOnMute: false);
```

If you use the prebuilt call controls, pass the same flag to the microphone toggle. Note that the default control set (`StreamCallControls.withDefaultOptions`) does not set it, so provide your own options list:

```dart
ToggleMicrophoneOption(
  call: call,
  stopTrackOnMute: false,
),
```

With the default mute (the track is stopped and released) no speech events are delivered on iOS and macOS. Keeping the track alive means the OS microphone-in-use indicator stays visible while muted — the same mute behavior as our Swift SDK. See [Controlling Mute Behavior](https://getstream.io/video/docs/flutter/guides/camera-and-microphone/microphone-and-audio/) for details.

### Web

Browsers don't expose native speech-activity events, so on web the SDK analyses a dedicated microphone stream with the Web Audio API while detection is active. This works with the default mute, follows the call's selected audio input device, and restarts automatically when that device changes. Note that the browser's "microphone in use" indicator stays on while detection is active.

### Windows and Linux

The default implementation doesn't support desktop platforms other than macOS. You can supply your own detection by implementing the `AudioRecognition` interface:

```dart
SpeakingWhileMutedRecognition(
  call: call,
  audioRecognition: MyCustomAudioRecognition(),
);
```

## Joining a Call Muted

Detection starts automatically only when the user transitions to muted during the call. If the user joins a call already muted, start detection manually:

```dart
await _speakingWhileMutedRecognition.start();
```

On web this works immediately, since detection uses its own microphone stream. On Android, iOS, and macOS, speech events come from the native audio capture, so no events are delivered until the microphone has been enabled at least once during the call.


---

This page was last updated at 2026-08-28T16:17:36.938Z.

For the most recent version of this documentation, visit [https://getstream.io/video/docs/flutter/ui-cookbook/speaking-while-muted/](https://getstream.io/video/docs/flutter/ui-cookbook/speaking-while-muted/).