Skip to content

Commit 4424d21

Browse files
Brazolrenefloor
andauthored
chore(llc): multiple FAST reconnect attempts and improved ICE restarts (#994)
* reconnect improvements * quality and scalability fixes * fixed layers dimentions * tweaks * Calculate size of video track with actual pixels * fixes * add unit test * Add test for fast reconnect * Don't mock stateManager by default --------- Co-authored-by: Rene Floor <[email protected]>
1 parent d245042 commit 4424d21

23 files changed

+942
-100
lines changed

packages/stream_video/lib/globals.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:meta/meta.dart';
33
const String streamSdkName = 'stream-flutter';
44
const String streamVideoVersion = '0.9.6';
55
const String openapiModelsVersion = '167.9.1';
6-
const String protocolModelsVersion = '1.35.0';
6+
const String protocolModelsVersion = '1.38.0';
77
const String androidWebRTCVersion = '1.3.8';
88
const String iosWebRTCVersion = '125.6422.65';
99

packages/stream_video/lib/protobuf/video/sfu/models/models.pb.dart

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_video/lib/protobuf/video/sfu/models/models.pbenum.dart

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_video/lib/protobuf/video/sfu/models/models.pbjson.dart

Lines changed: 12 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/stream_video/lib/src/call/call.dart

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,14 @@ class Call {
227227
required SdpPolicy sdpPolicy,
228228
required RtcMediaDeviceNotifier rtcMediaDeviceNotifier,
229229
CallCredentials? credentials,
230-
}) : _sessionFactory = CallSessionFactory(
231-
callCid: stateManager.callState.callCid,
232-
sdpEditor: sdpPolicy.spdEditingEnabled
233-
? SdpEditorImpl(sdpPolicy)
234-
: NoOpSdpEditor(),
235-
),
230+
CallSessionFactory? sessionFactory,
231+
}) : _sessionFactory = sessionFactory ??
232+
CallSessionFactory(
233+
callCid: stateManager.callState.callCid,
234+
sdpEditor: sdpPolicy.spdEditingEnabled
235+
? SdpEditorImpl(sdpPolicy)
236+
: NoOpSdpEditor(),
237+
),
236238
_stateManager = stateManager,
237239
_permissionsManager = permissionManager,
238240
_coordinatorClient = coordinatorClient,
@@ -762,13 +764,7 @@ class Call {
762764
dynascaleManager: dynascaleManager,
763765
networkMonitor: networkMonitor,
764766
statsOptions: _sfuStatsOptions!,
765-
onPeerConnectionFailure: (pc) async {
766-
if (state.value.status is! CallStatusReconnecting) {
767-
await pc.pc.restartIce().onError((_, __) {
768-
_reconnect(SfuReconnectionStrategy.rejoin);
769-
});
770-
}
771-
},
767+
onReconnectionNeeded: (pc, strategy) => _reconnect(strategy),
772768
clientPublishOptions:
773769
_stateManager.callState.preferences.clientPublishOptions,
774770
);
@@ -1209,6 +1205,7 @@ class Call {
12091205
_reconnectStrategy = strategy;
12101206
_awaitNetworkAvailableFuture = _awaitNetworkAvailable();
12111207

1208+
var attempt = 0;
12121209
do {
12131210
_stateManager.lifecycleCallConnecting(
12141211
attempt: _reconnectAttempts,
@@ -1262,7 +1259,28 @@ class Call {
12621259
await Future<void>.delayed(
12631260
_retryPolicy.backoff(_reconnectAttempts),
12641261
);
1265-
_reconnectStrategy = SfuReconnectionStrategy.rejoin;
1262+
1263+
final wasMigrating =
1264+
_reconnectStrategy == SfuReconnectionStrategy.migrate;
1265+
1266+
// don't immediately switch to the REJOIN strategy, but instead attempt
1267+
// to reconnect with the FAST strategy for a few times before switching.
1268+
// in some cases, we immediately switch to the REJOIN strategy.
1269+
final shouldRejoin =
1270+
wasMigrating || // if we were migrating, but the migration failed
1271+
attempt >= 3 || // after 3 failed attempts
1272+
!(_session?.rtcManager?.publisher?.isHealthy() ??
1273+
true) || // if the publisher is not healthy
1274+
!(_session?.rtcManager?.subscriber.isHealthy() ??
1275+
true); // if the subscriber is not healthy
1276+
1277+
attempt++;
1278+
1279+
final nextStrategy = shouldRejoin
1280+
? SfuReconnectionStrategy.rejoin
1281+
: SfuReconnectionStrategy.fast;
1282+
1283+
_reconnectStrategy = nextStrategy;
12661284
}
12671285
}
12681286
} while (state.value.status is! CallStatusConnected &&
@@ -1337,10 +1355,7 @@ class Call {
13371355
final previousCheckInterval = networkMonitor.checkInterval;
13381356
networkMonitor.setIntervalAndResetTimer(const Duration(seconds: 1));
13391357

1340-
final connectionStatus = await InternetConnection.createInstance(
1341-
checkInterval: const Duration(seconds: 1),
1342-
)
1343-
.onStatusChange
1358+
final connectionStatus = await networkMonitor.onStatusChange
13441359
.startWithFuture(networkMonitor.internetStatus)
13451360
.firstWhere((status) => status == InternetStatus.connected)
13461361
.timeout(
@@ -2903,3 +2918,34 @@ extension FutureStartWithEx<T> on Stream<T> {
29032918
yield* this;
29042919
}
29052920
}
2921+
2922+
// ignore: avoid_classes_with_only_static_members
2923+
/// Call factory to create a [Call] instance.
2924+
/// Only meant for testing purposes.
2925+
@visibleForTesting
2926+
class BaseCallFactory {
2927+
static Call makeCall({
2928+
required CoordinatorClient coordinatorClient,
2929+
required StreamVideo streamVideo,
2930+
required CallStateNotifier stateManager,
2931+
required PermissionsManager permissionManager,
2932+
required InternetConnection networkMonitor,
2933+
required RetryPolicy retryPolicy,
2934+
required SdpPolicy sdpPolicy,
2935+
required RtcMediaDeviceNotifier rtcMediaDeviceNotifier,
2936+
required CallCredentials? credentials,
2937+
required CallSessionFactory? sessionFactory,
2938+
}) =>
2939+
Call._(
2940+
coordinatorClient: coordinatorClient,
2941+
streamVideo: streamVideo,
2942+
stateManager: stateManager,
2943+
permissionManager: permissionManager,
2944+
networkMonitor: networkMonitor,
2945+
retryPolicy: retryPolicy,
2946+
sdpPolicy: sdpPolicy,
2947+
rtcMediaDeviceNotifier: rtcMediaDeviceNotifier,
2948+
credentials: credentials,
2949+
sessionFactory: sessionFactory,
2950+
);
2951+
}

packages/stream_video/lib/src/call/session/call_session.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@ const _tag = 'SV:CallSession';
5555
const _debounceDuration = Duration(milliseconds: 200);
5656
const _migrationCompleteEventTimeout = Duration(seconds: 7);
5757

58-
typedef OnPeerConnectionIssue = void Function(StreamPeerConnection pc);
59-
6058
class CallSession extends Disposable {
6159
CallSession({
6260
required this.callCid,
@@ -65,7 +63,7 @@ class CallSession extends Disposable {
6563
required this.config,
6664
required this.stateManager,
6765
required this.dynascaleManager,
68-
required this.onPeerConnectionIssue,
66+
required this.onReconnectionNeeded,
6967
required SdpEditor sdpEditor,
7068
required this.networkMonitor,
7169
required this.statsOptions,
@@ -107,7 +105,7 @@ class CallSession extends Disposable {
107105
final SfuClient sfuClient;
108106
final SfuWebSocket sfuWS;
109107
final RtcManagerFactory rtcManagerFactory;
110-
final OnPeerConnectionIssue onPeerConnectionIssue;
108+
final OnReconnectionNeeded onReconnectionNeeded;
111109
final ClientPublishOptions? clientPublishOptions;
112110
final InternetConnection networkMonitor;
113111
final StatsOptions statsOptions;
@@ -358,13 +356,14 @@ class CallSession extends Disposable {
358356

359357
if (isAnonymousUser) {
360358
rtcManager = await rtcManagerFactory.makeRtcManager(
359+
sfuClient: sfuClient,
361360
clientDetails: _clientDetails,
362361
sessionSequence: sessionSeq,
363362
statsOptions: statsOptions,
364363
)
365364
..onSubscriberIceCandidate = _onLocalIceCandidate
366-
..onSubscriberIssue = onPeerConnectionIssue
367365
..onRenegotiationNeeded = _onRenegotiationNeeded
366+
..onReconnectionNeeded = onReconnectionNeeded
368367
..onRemoteTrackReceived = _onRemoteTrackReceived;
369368
} else {
370369
final currentUserId = stateManager.callState.currentUserId;
@@ -376,6 +375,7 @@ class CallSession extends Disposable {
376375
_logger.v(() => '[start] localTrackId: $localTrackId');
377376

378377
rtcManager = await rtcManagerFactory.makeRtcManager(
378+
sfuClient: sfuClient,
379379
publisherId: localTrackId,
380380
publishOptions: event.publishOptions,
381381
clientDetails: _clientDetails,
@@ -385,10 +385,9 @@ class CallSession extends Disposable {
385385
)
386386
..onPublisherIceCandidate = _onLocalIceCandidate
387387
..onSubscriberIceCandidate = _onLocalIceCandidate
388-
..onPublisherIssue = onPeerConnectionIssue
389-
..onSubscriberIssue = onPeerConnectionIssue
390388
..onLocalTrackMuted = _onLocalTrackMuted
391389
..onLocalTrackPublished = _onLocalTrackPublished
390+
..onReconnectionNeeded = onReconnectionNeeded
392391
..onRenegotiationNeeded = _onRenegotiationNeeded
393392
..onRemoteTrackReceived = _onRemoteTrackReceived;
394393
}
@@ -1071,6 +1070,7 @@ extension RtcTracksInfoMapper on List<RtcTrackInfo> {
10711070
),
10721071
bitrate: layer.parameters.encoding.maxBitrate,
10731072
fps: layer.parameters.encoding.maxFramerate,
1073+
quality: layer.parameters.encoding.quality.toDTO(),
10741074
);
10751075
}),
10761076
);

packages/stream_video/lib/src/call/session/call_session_factory.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import '../../models/call_cid.dart';
88
import '../../models/call_client_publish_options.dart';
99
import '../../models/call_credentials.dart';
1010
import '../../types/other.dart';
11+
import '../../webrtc/peer_connection.dart';
1112
import '../../webrtc/sdp/editor/sdp_editor.dart';
1213
import '../state/call_state_notifier.dart';
1314
import '../stats/tracer.dart';
@@ -32,7 +33,7 @@ class CallSessionFactory {
3233
required CallCredentials credentials,
3334
required CallStateNotifier stateManager,
3435
required DynascaleManager dynascaleManager,
35-
required OnPeerConnectionIssue onPeerConnectionFailure,
36+
required OnReconnectionNeeded onReconnectionNeeded,
3637
required InternetConnection networkMonitor,
3738
required StatsOptions statsOptions,
3839
ClientPublishOptions? clientPublishOptions,
@@ -68,7 +69,7 @@ class CallSessionFactory {
6869
stateManager: stateManager,
6970
dynascaleManager: dynascaleManager,
7071
sdpEditor: sdpEditor,
71-
onPeerConnectionIssue: onPeerConnectionFailure,
72+
onReconnectionNeeded: onReconnectionNeeded,
7273
clientPublishOptions: clientPublishOptions,
7374
networkMonitor: networkMonitor,
7475
statsOptions: statsOptions,

packages/stream_video/lib/src/errors/video_error_composer.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import 'package:tart/tart.dart';
22
import 'package:web_socket_channel/web_socket_channel.dart';
33

44
import '../../open_api/video/coordinator/api.dart';
5+
import '../../protobuf/video/sfu/models/models.pb.dart' as sfu_models;
6+
import '../sfu/data/events/sfu_event_mapper_extensions.dart';
7+
import '../sfu/data/models/sfu_error.dart';
58
import 'video_error.dart';
69

710
/// TODO
@@ -13,6 +16,17 @@ mixin VideoErrors {
1316
message: exception,
1417
stackTrace: stackTrace,
1518
);
19+
} else if (exception is sfu_models.Error) {
20+
return VideoErrorWithCause(
21+
message: exception.message,
22+
cause: SfuError(
23+
message: exception.message,
24+
code: exception.code.toDomain(),
25+
shouldRetry: exception.shouldRetry,
26+
reconnectStrategy: SfuReconnectionStrategy.unspecified,
27+
),
28+
stackTrace: stackTrace,
29+
);
1630
} else if (exception is TwirpError) {
1731
return VideoErrorWithCause(
1832
message: exception.getMsg,

0 commit comments

Comments
 (0)