diff --git a/commet/lib/client/matrix/components/voip_room/matrix_livekit_voip_session.dart b/commet/lib/client/matrix/components/voip_room/matrix_livekit_voip_session.dart index ebb5875c8..408fe9ba2 100644 --- a/commet/lib/client/matrix/components/voip_room/matrix_livekit_voip_session.dart +++ b/commet/lib/client/matrix/components/voip_room/matrix_livekit_voip_session.dart @@ -24,6 +24,7 @@ class MatrixLivekitVoipSession implements VoipSession { lk.Room livekitRoom; Timer? heartbeatTimer; String? heartbeatDelayId; + bool _softMuted = false; final StreamController _onVolumeChanged = StreamController.broadcast(); @@ -211,7 +212,7 @@ class MatrixLivekitVoipSession implements VoipSession { livekitRoom.localParticipant?.isCameraEnabled() ?? false; @override - bool get isMicrophoneMuted => livekitRoom.localParticipant?.isMuted ?? false; + bool get isMicrophoneMuted => _softMuted; @override bool get isSharingScreen => @@ -240,7 +241,22 @@ class MatrixLivekitVoipSession implements VoipSession { @override Future setMicrophoneMute(bool state) async { - await livekitRoom.localParticipant?.setMicrophoneEnabled(!state); + final publication = livekitRoom.localParticipant + ?.getTrackPublicationBySource(lk.TrackSource.microphone); + + if (publication?.track != null) { + final mediaStreamTrack = publication!.track!.mediaStreamTrack; + // Use SetVolume(0) for a soft mute that doesn't affect the system audio + // chain. + await Helper.setVolume(state ? 0.0 : 1.0, mediaStreamTrack); + + // Signal the mute state to the LiveKit server sso remote participants + // see the correct muted indicator, without triggering track disable/stop. + // ignore: invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member + publication.track!.updateMuted(state, shouldSendSignal: true); + } + + _softMuted = state; _stateChanged.add(()); } diff --git a/commet/lib/ui/organisms/call_view/voip_stream_view.dart b/commet/lib/ui/organisms/call_view/voip_stream_view.dart index aa5d20719..c79298968 100644 --- a/commet/lib/ui/organisms/call_view/voip_stream_view.dart +++ b/commet/lib/ui/organisms/call_view/voip_stream_view.dart @@ -160,8 +160,13 @@ class _VoipStreamViewState extends State } Color getBorderColor(BuildContext context) { + final isLocalUser = + widget.stream.streamUserId == widget.session.client.self?.identifier; + final effectiveLevel = isLocalUser && widget.session.isMicrophoneMuted + ? 0.0 + : audioLevel.value; return Color.lerp(Theme.of(context).primaryColor, - Theme.of(context).colorScheme.primary, audioLevel.value)!; + Theme.of(context).colorScheme.primary, effectiveLevel)!; } void onStreamChanged(void event) { diff --git a/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon.dart b/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon.dart index ce51019c2..7902702b2 100644 --- a/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon.dart +++ b/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon.dart @@ -81,6 +81,7 @@ class _SidebarCallIconEntryState extends State color: room?.defaultColor, avatar: room?.avatar, audioLevel: audioLevel.value, + isMuted: widget.session.isMicrophoneMuted, onTap: () => EventBus.openRoom.add(( widget.session.roomId, widget.session.client.identifier diff --git a/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon_view.dart b/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon_view.dart index ef190e3d4..62e435f95 100644 --- a/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon_view.dart +++ b/commet/lib/ui/organisms/sidebar_call_icon/sidebar_call_icon_view.dart @@ -11,6 +11,7 @@ class SidebarCallIconView extends StatelessWidget { this.color, this.onTap, this.audioLevel = 0, + this.isMuted = false, required this.width, super.key}); final double width; @@ -19,6 +20,7 @@ class SidebarCallIconView extends StatelessWidget { final ImageProvider? avatar; final VoipState state; final double audioLevel; + final bool isMuted; final Function()? onTap; @override Widget build(BuildContext context) { @@ -66,8 +68,9 @@ class SidebarCallIconView extends StatelessWidget { Color getBorderColor(BuildContext context) { if (state == VoipState.connected) { + final level = isMuted ? 0.0 : audioLevel; return Color.lerp(Theme.of(context).primaryColor, - Theme.of(context).colorScheme.primary, audioLevel)!; + Theme.of(context).colorScheme.primary, level)!; } return Theme.of(context).primaryColor;