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();
}
// ...
}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:
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:
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:
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:
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 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:
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:
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.