@@ -86,6 +86,9 @@ abstract class LocalTrack extends Track {
8686 bool _stopped = false ;
8787
8888 TrackProcessor ? _processor;
89+
90+ // Store original sender parameters to restore on unmute
91+ rtc.RTCRtpParameters ? _originalSenderParameters;
8992
9093 TrackProcessor ? get processor => _processor;
9194
@@ -103,30 +106,77 @@ abstract class LocalTrack extends Track {
103106 };
104107 }
105108
106- /// Mutes this [LocalTrack] . This will stop the sending of track data
109+ /// Mutes this [LocalTrack] . This will zero the audio frames
107110 /// and notify the [RemoteParticipant] with [TrackMutedEvent] .
108111 /// Returns true if muted, false if unchanged.
109112 Future <bool > mute ({bool stopOnMute = true }) async {
110113 logger.fine ('LocalTrack.mute() muted: $muted ' );
111114 if (muted) return false ; // already muted
112- await disable ();
113- if (! skipStopForTrackMute () && stopOnMute) {
114- await stop ();
115+
116+ // For audio tracks, zero the frames by manipulating sender parameters
117+ if (this is AudioTrack && sender != null ) {
118+ try {
119+ final parameters = sender! .parameters;
120+ // Store original parameters if not already stored
121+ if (_originalSenderParameters == null ) {
122+ _originalSenderParameters = parameters;
123+ }
124+ // Zero out the audio by setting maxBitrate to 0 for all encodings
125+ if (parameters.encodings != null && parameters.encodings! .isNotEmpty) {
126+ for (var encoding in parameters.encodings! ) {
127+ encoding.maxBitrate = 0 ;
128+ }
129+ await sender! .setParameters (parameters);
130+ }
131+ } catch (error) {
132+ logger.warning ('Failed to modify sender parameters on mute: $error ' );
133+ }
134+ } else {
135+ // For video tracks, use the original disable logic
136+ await disable ();
137+ if (! skipStopForTrackMute () && stopOnMute) {
138+ await stop ();
139+ }
115140 }
141+
116142 updateMuted (true , shouldSendSignal: true );
117143 return true ;
118144 }
119145
120- /// Un-mutes this [LocalTrack] . This will re-start the sending of track data
146+ /// Un-mutes this [LocalTrack] . This will restore the audio frames to normal
121147 /// and notify the [RemoteParticipant] with [TrackUnmutedEvent] .
122148 /// Returns true if un-muted, false if unchanged.
123149 Future <bool > unmute ({bool stopOnMute = true }) async {
124150 logger.fine ('LocalTrack.unmute() muted: $muted ' );
125151 if (! muted) return false ; // already un-muted
126- if (! skipStopForTrackMute () && stopOnMute) {
127- await restartTrack ();
152+
153+ // For audio tracks, restore the frames by restoring sender parameters
154+ if (this is AudioTrack && sender != null ) {
155+ try {
156+ if (_originalSenderParameters != null ) {
157+ // Restore the original sender parameters
158+ await sender! .setParameters (_originalSenderParameters! );
159+ } else {
160+ // If no stored parameters, reset to allow full bitrate
161+ final parameters = sender! .parameters;
162+ if (parameters.encodings != null && parameters.encodings! .isNotEmpty) {
163+ for (var encoding in parameters.encodings! ) {
164+ encoding.maxBitrate = null ; // Remove bitrate limitation
165+ }
166+ await sender! .setParameters (parameters);
167+ }
168+ }
169+ } catch (error) {
170+ logger.warning ('Failed to restore sender parameters on unmute: $error ' );
171+ }
172+ } else {
173+ // For video tracks, use the original enable logic
174+ if (! skipStopForTrackMute () && stopOnMute) {
175+ await restartTrack ();
176+ }
177+ await enable ();
128178 }
129- await enable ();
179+
130180 updateMuted (false , shouldSendSignal: true );
131181 return true ;
132182 }
@@ -322,4 +372,4 @@ abstract class LocalTrack extends Track {
322372 _published = false ;
323373 return true ;
324374 }
325- }
375+ }
0 commit comments