Skip to content

Commit

Permalink
fix(llc): fixed enabling/disabling cloned tracks (#828)
Browse files Browse the repository at this point in the history
* fixed cloned track disabling/enabling

* tweak
  • Loading branch information
Brazol authored Jan 16, 2025
1 parent 16f5bca commit 4fa5e1e
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 33 deletions.
81 changes: 57 additions & 24 deletions packages/stream_video/lib/src/webrtc/rtc_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,19 @@ class RtcManager extends Disposable {

// take the track from the existing transceiver for the same track type,
// and publish it with the new publish options
await _addTransceiver(item.track, publishOption);
final result = await _addTransceiver(item.track, publishOption);

if (result is Success) {
final localTrack = tracks[item.track.trackId] as RtcLocalTrack?;
if (localTrack != null) {
tracks[item.track.trackId] = localTrack.copyWith(
clonedTracks: [
...localTrack.clonedTracks,
result.getDataOrNull()!.mediaTrack,
],
);
}
}
}

for (final item in transceiversManager.items().toList()) {
Expand Down Expand Up @@ -632,21 +644,25 @@ extension PublisherRtcManager on RtcManager {
// callback.
_logger.i(() => '[publishAudioTrack] track: $track');
tracks[track.trackId] = track;
var updatedTrack = track.copyWith(stopTrackOnMute: stopTrackOnMute);

final transceivers = <rtc.RTCRtpTransceiver>[];
for (final option in publishOptions) {
if (option.trackType != track.trackType) continue;

final transceiver = await _addTransceiver(track, option);
if (transceiver is Failure) return transceiver;
transceivers.add(transceiver.getDataOrNull()!);
final transceiverResult = await _addTransceiver(track, option);
if (transceiverResult is Failure) return transceiverResult;
transceivers.add(transceiverResult.getDataOrNull()!.transceiver);

_logger.v(() => '[publishAudioTrack] transceiver: $transceiver');
}
_logger.v(() => '[publishAudioTrack] transceiver: $transceiverResult');

final updatedTrack = track.copyWith(
stopTrackOnMute: stopTrackOnMute,
);
updatedTrack = updatedTrack.copyWith(
clonedTracks: [
...updatedTrack.clonedTracks,
transceiverResult.getDataOrNull()!.mediaTrack,
],
);
}

// Notify listeners.
onLocalTrackPublished?.call(updatedTrack);
Expand All @@ -668,6 +684,10 @@ extension PublisherRtcManager on RtcManager {
// callback.
_logger.i(() => '[publishVideoTrack] track: $track');
tracks[track.trackId] = track;
var updatedTrack = track.copyWith(
videoDimension: _getTrackDimension(track),
stopTrackOnMute: stopTrackOnMute,
);

if (!publishOptions.any((o) => o.trackType == track.trackType)) {
_logger.w(
Expand All @@ -684,10 +704,18 @@ extension PublisherRtcManager on RtcManager {

final cashedTransceiver = transceiversManager.get(option);
if (cashedTransceiver == null) {
final transceiver = await _addTransceiver(track, option);
if (transceiver is Failure) return transceiver;
final transceiverResult = await _addTransceiver(track, option);
if (transceiverResult is Failure) return transceiverResult;

updatedTrack = updatedTrack.copyWith(
clonedTracks: [
...updatedTrack.clonedTracks,
transceiverResult.getDataOrNull()!.mediaTrack,
],
);

_logger.v(() => '[publishVideoTrack] new transceiver: $transceiver');
_logger
.v(() => '[publishVideoTrack] new transceiver: $transceiverResult');
} else {
final previousTrack = cashedTransceiver.sender.track;

Expand All @@ -704,11 +732,6 @@ extension PublisherRtcManager on RtcManager {
}
}

final updatedTrack = track.copyWith(
videoDimension: _getTrackDimension(track),
stopTrackOnMute: stopTrackOnMute,
);

// Notify listeners.
onLocalTrackPublished?.call(updatedTrack);
tracks[updatedTrack.trackId] = updatedTrack;
Expand Down Expand Up @@ -759,7 +782,12 @@ extension PublisherRtcManager on RtcManager {
.toList();
}

Future<Result<rtc.RTCRtpTransceiver>> _addTransceiver(
Future<
Result<
({
rtc.RTCRtpTransceiver transceiver,
rtc.MediaStreamTrack mediaTrack,
})>> _addTransceiver(
RtcLocalTrack track,
SfuPublishOptions publishOptions,
) async {
Expand All @@ -771,16 +799,16 @@ extension PublisherRtcManager on RtcManager {

// create a clone of the track as otherwise the same trackId will
// appear in the SDP in multiple transceivers
final mediaTrack = await track.originalMediaTrack.clone();
final mediaTrackClone = await track.mediaTrack.clone();

_logger.v(
() =>
'[addTransceiver] adding transceiver for: ${publishOptions.codec.name}, trackId: ${mediaTrack.id}',
'[addTransceiver] adding transceiver for: ${publishOptions.codec.name}, trackId: ${mediaTrackClone.id}',
);

if (track is RtcLocalAudioTrack) {
transceiverResult = await publisher!.addAudioTransceiver(
track: mediaTrack,
track: mediaTrackClone,
encodings: [
rtc.RTCRtpEncoding(rid: 'a', maxBitrate: AudioBitrate.music),
],
Expand All @@ -800,7 +828,7 @@ extension PublisherRtcManager on RtcManager {
}

transceiverResult = await publisher!.addVideoTransceiver(
track: mediaTrack,
track: mediaTrackClone,
encodings: sendEncodings,
);
} else {
Expand All @@ -812,12 +840,17 @@ extension PublisherRtcManager on RtcManager {

final transceiver = transceiverResult.getDataOrNull()!;
transceiversManager.add(
track.copyWith(mediaTrack: mediaTrack),
track.copyWith(mediaTrack: mediaTrackClone),
publishOptions,
transceiver,
);

return Result.success(transceiver);
return Result.success(
(
transceiver: transceiver,
mediaTrack: mediaTrackClone,
),
);
}

Future<Result<RtcLocalTrack>> muteTrack({required String trackId}) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
required super.mediaStream,
required super.mediaTrack,
required this.mediaConstraints,
required this.originalMediaTrack,
this.stopTrackOnMute = true,
this.clonedTracks = const [],
super.videoDimension,
});

Expand All @@ -52,7 +52,6 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
trackType: SfuTrackType.audio,
mediaStream: stream,
mediaTrack: audioTrack,
originalMediaTrack: audioTrack,
mediaConstraints: constraints,
);

Expand All @@ -77,7 +76,6 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
trackType: SfuTrackType.video,
mediaStream: stream,
mediaTrack: videoTrack,
originalMediaTrack: videoTrack,
mediaConstraints: constraints,
);

Expand All @@ -103,15 +101,13 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
trackType: SfuTrackType.screenShare,
mediaStream: stream,
mediaTrack: videoTrack,
originalMediaTrack: videoTrack,
mediaConstraints: constraints,
);

return track;
}

/// The original media track used to create this track.
final rtc.MediaStreamTrack originalMediaTrack;
final List<rtc.MediaStreamTrack> clonedTracks;

/// The media constraints used to create this track.
///
Expand Down Expand Up @@ -140,6 +136,9 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
streamLog.i(_tag, () => 'Stopping track: $trackId');
try {
await mediaTrack.stop();
for (final track in clonedTracks) {
await track.stop();
}
} catch (e) {
streamLog.w(_tag, () => 'Error stopping mediaTrack: $e');
}
Expand All @@ -151,6 +150,38 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
}
}

@override
void enable() {
// Return if the track is already enabled.
if (mediaTrack.enabled) return;

streamLog.i(_tag, () => 'Enabling track $trackId');
try {
mediaTrack.enabled = true;
for (final track in clonedTracks) {
track.enabled = true;
}
} catch (_) {
streamLog.w(_tag, () => 'Failed to enable track $trackId');
}
}

@override
void disable() {
// Return if the track is already disabled.
if (!mediaTrack.enabled) return;

streamLog.i(_tag, () => 'Disabling track $trackId');
try {
mediaTrack.enabled = false;
for (final track in clonedTracks) {
track.enabled = false;
}
} catch (_) {
streamLog.w(_tag, () => 'Failed to disable track $trackId');
}
}

@override
RtcLocalTrack<T> copyWith({
String? trackIdPrefix,
Expand All @@ -160,7 +191,7 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
T? mediaConstraints,
bool? stopTrackOnMute,
RtcVideoDimension? videoDimension,
rtc.MediaStreamTrack? originalMediaTrack,
List<rtc.MediaStreamTrack>? clonedTracks,
}) {
return RtcLocalTrack(
trackIdPrefix: trackIdPrefix ?? this.trackIdPrefix,
Expand All @@ -170,7 +201,7 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
mediaConstraints: mediaConstraints ?? this.mediaConstraints,
stopTrackOnMute: stopTrackOnMute ?? this.stopTrackOnMute,
videoDimension: videoDimension ?? this.videoDimension,
originalMediaTrack: originalMediaTrack ?? this.originalMediaTrack,
clonedTracks: clonedTracks ?? this.clonedTracks,
);
}

Expand All @@ -190,6 +221,7 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
// Create a new track with the new constraints.
final newStream = await rtc.navigator.mediaDevices.getMedia(constraints);
final newTrack = newStream.getTracks().first;
final clonedTracks = <rtc.MediaStreamTrack>[];

// Replace the track on the transceiver if it exists.
for (final transceiver in transceivers) {
Expand All @@ -198,15 +230,17 @@ class RtcLocalTrack<T extends MediaConstraints> extends RtcTrack {
}

final clonedTrack = await newTrack.clone();
clonedTracks.add(clonedTrack);

streamLog.i(_tag, () => 'Replacing track on sender');
await transceiver.sender.replaceTrack(clonedTrack);
}

return copyWith(
mediaTrack: newTrack,
originalMediaTrack: newTrack,
mediaStream: newStream,
mediaConstraints: constraints,
clonedTracks: clonedTracks,
);
}

Expand Down

0 comments on commit 4fa5e1e

Please sign in to comment.