diff --git a/lib/src/track/local/local.dart b/lib/src/track/local/local.dart index 2e7e56bb..7b0ec559 100644 --- a/lib/src/track/local/local.dart +++ b/lib/src/track/local/local.dart @@ -86,6 +86,9 @@ abstract class LocalTrack extends Track { bool _stopped = false; TrackProcessor? _processor; + + // Store original sender parameters to restore on unmute + rtc.RTCRtpParameters? _originalSenderParameters; TrackProcessor? get processor => _processor; @@ -103,30 +106,77 @@ abstract class LocalTrack extends Track { }; } - /// Mutes this [LocalTrack]. This will stop the sending of track data + /// Mutes this [LocalTrack]. This will zero the audio frames /// and notify the [RemoteParticipant] with [TrackMutedEvent]. /// Returns true if muted, false if unchanged. Future mute({bool stopOnMute = true}) async { logger.fine('LocalTrack.mute() muted: $muted'); if (muted) return false; // already muted - await disable(); - if (!skipStopForTrackMute() && stopOnMute) { - await stop(); + + // For audio tracks, zero the frames by manipulating sender parameters + if (this is AudioTrack && sender != null) { + try { + final parameters = sender!.parameters; + // Store original parameters if not already stored + if (_originalSenderParameters == null) { + _originalSenderParameters = parameters; + } + // Zero out the audio by setting maxBitrate to 0 for all encodings + if (parameters.encodings != null && parameters.encodings!.isNotEmpty) { + for (var encoding in parameters.encodings!) { + encoding.maxBitrate = 0; + } + await sender!.setParameters(parameters); + } + } catch (error) { + logger.warning('Failed to modify sender parameters on mute: $error'); + } + } else { + // For video tracks, use the original disable logic + await disable(); + if (!skipStopForTrackMute() && stopOnMute) { + await stop(); + } } + updateMuted(true, shouldSendSignal: true); return true; } - /// Un-mutes this [LocalTrack]. This will re-start the sending of track data + /// Un-mutes this [LocalTrack]. This will restore the audio frames to normal /// and notify the [RemoteParticipant] with [TrackUnmutedEvent]. /// Returns true if un-muted, false if unchanged. Future unmute({bool stopOnMute = true}) async { logger.fine('LocalTrack.unmute() muted: $muted'); if (!muted) return false; // already un-muted - if (!skipStopForTrackMute() && stopOnMute) { - await restartTrack(); + + // For audio tracks, restore the frames by restoring sender parameters + if (this is AudioTrack && sender != null) { + try { + if (_originalSenderParameters != null) { + // Restore the original sender parameters + await sender!.setParameters(_originalSenderParameters!); + } else { + // If no stored parameters, reset to allow full bitrate + final parameters = sender!.parameters; + if (parameters.encodings != null && parameters.encodings!.isNotEmpty) { + for (var encoding in parameters.encodings!) { + encoding.maxBitrate = null; // Remove bitrate limitation + } + await sender!.setParameters(parameters); + } + } + } catch (error) { + logger.warning('Failed to restore sender parameters on unmute: $error'); + } + } else { + // For video tracks, use the original enable logic + if (!skipStopForTrackMute() && stopOnMute) { + await restartTrack(); + } + await enable(); } - await enable(); + updateMuted(false, shouldSendSignal: true); return true; } @@ -322,4 +372,4 @@ abstract class LocalTrack extends Track { _published = false; return true; } -} +} \ No newline at end of file diff --git a/lib/src/track/remote/audio.dart b/lib/src/track/remote/audio.dart index f691e709..ac2c2c2b 100644 --- a/lib/src/track/remote/audio.dart +++ b/lib/src/track/remote/audio.dart @@ -31,6 +31,16 @@ import '../web/_audio_api.dart' class RemoteAudioTrack extends RemoteTrack with AudioTrack, RemoteAudioManagementMixin { String? _deviceId; + double _volume = 1.0; + double get volume => _volume; + + Future setVolume(double newVolume) async { + final double clamped = newVolume.clamp(0.0, 4.0).toDouble(); + _volume = clamped; + try { + await rtc.Helper.setVolume(clamped, mediaStreamTrack); + } catch (_) {} + } RemoteAudioTrack( TrackSource source, rtc.MediaStream stream, rtc.MediaStreamTrack track, {rtc.RTCRtpReceiver? receiver}) diff --git a/pubspec.lock b/pubspec.lock index 05f3c64b..62305b98 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -219,10 +219,9 @@ packages: flutter_webrtc: dependency: "direct main" description: - name: flutter_webrtc - sha256: "945d0a38b90fbca8257eadb167d8fb9fa7075d9a1939fd2953c10054454d1de2" - url: "https://pub.dev" - source: hosted + path: "../flutter-webrtc" + relative: true + source: path version: "1.1.0" glob: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index bdc3e3b7..843f77dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,8 @@ dependencies: uuid: ^4.5.1 synchronized: ^3.0.0+3 protobuf: ^4.1.0 - flutter_webrtc: ^1.1.0 + flutter_webrtc: + path: ../flutter-webrtc device_info_plus: ^11.3.0 dart_webrtc: ^1.5.3+hotfix.5 sdp_transform: ^0.3.2