diff --git a/.changes/fix-buffer-status-busy-wait b/.changes/fix-buffer-status-busy-wait new file mode 100644 index 000000000..9f048b1d1 --- /dev/null +++ b/.changes/fix-buffer-status-busy-wait @@ -0,0 +1 @@ +patch type="fixed" "Fix waitForBufferStatusLow busy-wait after engine close" diff --git a/.changes/fix-connected-server-address b/.changes/fix-connected-server-address new file mode 100644 index 000000000..e55d6ccea --- /dev/null +++ b/.changes/fix-connected-server-address @@ -0,0 +1 @@ +patch type="fixed" "Fix connected server address using wrong peer connection" diff --git a/.changes/fix-deferred-track-listener-leak b/.changes/fix-deferred-track-listener-leak new file mode 100644 index 000000000..fa6955d75 --- /dev/null +++ b/.changes/fix-deferred-track-listener-leak @@ -0,0 +1 @@ +patch type="fixed" "Fix deferred track listener leak across reconnects" diff --git a/.changes/fix-log-interpolation b/.changes/fix-log-interpolation new file mode 100644 index 000000000..8919c3a8e --- /dev/null +++ b/.changes/fix-log-interpolation @@ -0,0 +1 @@ +patch type="fixed" "Fix string interpolation in forceRelay log messages" diff --git a/.changes/fix-premature-publication-dispose b/.changes/fix-premature-publication-dispose new file mode 100644 index 000000000..e97a95a32 --- /dev/null +++ b/.changes/fix-premature-publication-dispose @@ -0,0 +1 @@ +patch type="fixed" "Fix premature publication dispose during unpublish" diff --git a/.changes/fix-reconnect-counter b/.changes/fix-reconnect-counter new file mode 100644 index 000000000..7291fd827 --- /dev/null +++ b/.changes/fix-reconnect-counter @@ -0,0 +1 @@ +patch type="fixed" "Fix reconnect counter null assertion on first reconnect attempt" diff --git a/.changes/fix-region-failover-condition b/.changes/fix-region-failover-condition new file mode 100644 index 000000000..722a10933 --- /dev/null +++ b/.changes/fix-region-failover-condition @@ -0,0 +1 @@ +patch type="fixed" "Fix region failover condition allowing null provider dereference" diff --git a/.changes/fix-send-sync-state-return-type b/.changes/fix-send-sync-state-return-type new file mode 100644 index 000000000..ed91cbb36 --- /dev/null +++ b/.changes/fix-send-sync-state-return-type @@ -0,0 +1 @@ +patch type="fixed" "Fix sendSyncState using async void and swallowing sync-state preparation errors" diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index d5c113cbc..97700d65d 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -121,20 +121,19 @@ class Engine extends Disposable with EventsEmittable { late EventsListener _signalListener = signalClient.createListener(synchronized: true); - int? reconnectAttempts; - - Timer? reconnectTimeout; - DateTime? reconnectStart; + int _reconnectAttempts = 0; + Timer? _reconnectTimeout; + DateTime? _reconnectStart; bool _isClosed = false; bool get isClosed => _isClosed; - bool get isPendingReconnect => reconnectStart != null && reconnectTimeout != null; + bool get isPendingReconnect => _reconnectStart != null && _reconnectTimeout != null; final int _reconnectCount = defaultRetryDelaysInMs.length; - bool attemptingReconnect = false; + bool _attemptingReconnect = false; RegionUrlProvider? _regionUrlProvider; @@ -179,17 +178,17 @@ class Engine extends Disposable with EventsEmittable { return null; } - void clearReconnectTimeout() { - if (reconnectTimeout != null) { - reconnectTimeout?.cancel(); - reconnectTimeout = null; + void _clearReconnectTimeout() { + if (_reconnectTimeout != null) { + _reconnectTimeout?.cancel(); + _reconnectTimeout = null; } } - void clearPendingReconnect() { - clearReconnectTimeout(); - reconnectAttempts = 0; - reconnectStart = null; + void _clearPendingReconnect() { + _clearReconnectTimeout(); + _reconnectAttempts = 0; + _reconnectStart = null; } Engine({ @@ -290,7 +289,7 @@ class Engine extends Disposable with EventsEmittable { await signalClient.cleanUp(); fullReconnectOnNext = false; - attemptingReconnect = false; + _attemptingReconnect = false; // Reset reliability state _reliableDataSequence = 1; @@ -298,7 +297,7 @@ class Engine extends Disposable with EventsEmittable { _reliableReceivedState.clear(); _isReconnecting = false; - clearPendingReconnect(); + _clearPendingReconnect(); } @internal @@ -357,7 +356,7 @@ class Engine extends Disposable with EventsEmittable { events.once((e) => onClosing()); - while (!_dcBufferStatus[kind]!) { + while (!completer.isCompleted && !_dcBufferStatus[kind]!) { await Future.delayed(const Duration(milliseconds: 10)); } if (completer.isCompleted) { @@ -725,7 +724,7 @@ class Engine extends Disposable with EventsEmittable { signalClient.connectionState == ConnectionState.connecting) { final track = event.track; final receiver = event.receiver; - events.on((event) async { + events.once((event) async { Timer(const Duration(milliseconds: 10), () { events.emit(EngineTrackAddedEvent( track: track, @@ -829,7 +828,7 @@ class Engine extends Disposable with EventsEmittable { Future _handleGettingConnectedServerAddress(rtc.RTCPeerConnection pc) async { try { - final remoteAddress = await getConnectedAddress(publisher!.pc); + final remoteAddress = await getConnectedAddress(pc); logger.fine('Connected address: $remoteAddress'); if (_connectedServerAddress == null || _connectedServerAddress != remoteAddress) { _connectedServerAddress = remoteAddress; @@ -999,11 +998,11 @@ class Engine extends Disposable with EventsEmittable { _isReconnecting = true; - if (reconnectAttempts == 0) { - reconnectStart = DateTime.timestamp(); + if (_reconnectAttempts == 0) { + _reconnectStart = DateTime.timestamp(); } - if (reconnectAttempts! >= _reconnectCount) { + if (_reconnectAttempts >= _reconnectCount) { logger.fine('reconnectAttempts exceeded, disconnecting...'); _isClosed = true; await cleanUp(); @@ -1014,26 +1013,26 @@ class Engine extends Disposable with EventsEmittable { return; } - var delay = defaultRetryDelaysInMs[reconnectAttempts!]; + var delay = defaultRetryDelaysInMs[_reconnectAttempts]; // Add random jitter to prevent thundering herd on reconnect - if (reconnectAttempts! > 1) { + if (_reconnectAttempts > 1) { delay += math.Random().nextInt(1000); } events.emit(EngineAttemptReconnectEvent( - attempt: reconnectAttempts! + 1, + attempt: _reconnectAttempts + 1, maxAttempts: _reconnectCount, nextRetryDelaysInMs: delay, )); - clearReconnectTimeout(); + _clearReconnectTimeout(); if (token != null && _regionUrlProvider != null) { // token may have been refreshed, we do not want to recreate the regionUrlProvider // since the current engine may have inherited a regional url _regionUrlProvider!.updateToken(token!); } - logger.fine('WebSocket reconnecting in $delay ms, retry times $reconnectAttempts'); - reconnectTimeout = Timer(Duration(milliseconds: delay), () async { + logger.fine('WebSocket reconnecting in $delay ms, retry times $_reconnectAttempts'); + _reconnectTimeout = Timer(Duration(milliseconds: delay), () async { await attemptReconnect( reason, reconnectReason: reconnectReason, @@ -1051,7 +1050,7 @@ class Engine extends Disposable with EventsEmittable { } // guard for attempting reconnection multiple times while one attempt is still not finished - if (attemptingReconnect) { + if (_attemptingReconnect) { return; } @@ -1065,7 +1064,7 @@ class Engine extends Disposable with EventsEmittable { } try { - attemptingReconnect = true; + _attemptingReconnect = true; if (await signalClient.networkIsAvailable() == false) { logger.fine('no internet connection, waiting...'); @@ -1086,11 +1085,11 @@ class Engine extends Disposable with EventsEmittable { reconnectReason: reconnectReason, ); } - clearPendingReconnect(); - attemptingReconnect = false; + _clearPendingReconnect(); + _attemptingReconnect = false; _isReconnecting = false; } catch (e) { - reconnectAttempts = reconnectAttempts! + 1; + _reconnectAttempts = _reconnectAttempts + 1; bool recoverable = true; if (e is WebSocketException || e is MediaConnectException) { // cannot resume connection, need to do full reconnect @@ -1111,7 +1110,7 @@ class Engine extends Disposable with EventsEmittable { await cleanUp(); } } finally { - attemptingReconnect = false; + _attemptingReconnect = false; } } @@ -1230,7 +1229,7 @@ class Engine extends Disposable with EventsEmittable { } @internal - void sendSyncState({ + Future sendSyncState({ required lk_rtc.UpdateSubscription subscription, required Iterable? publishTracks, required List trackSidsDisabled, @@ -1281,7 +1280,7 @@ class Engine extends Disposable with EventsEmittable { logger.fine('onConnected subscriberPrimary: ${_subscriberPrimary}, ' 'serverVersion: ${event.response.serverVersion}, ' 'iceServers: ${event.response.iceServers}, ' - 'forceRelay: $event.response.clientConfiguration.forceRelay'); + 'forceRelay: ${event.response.clientConfiguration.forceRelay}'); final rtcConfiguration = await _buildRtcConfiguration( serverResponseForceRelay: event.response.clientConfiguration.forceRelay, @@ -1313,7 +1312,7 @@ class Engine extends Disposable with EventsEmittable { logger.fine('Handle ReconnectResponse: ' 'iceServers: ${event.response.iceServers}, ' - 'forceRelay: $event.response.clientConfiguration.forceRelay, ' + 'forceRelay: ${event.response.clientConfiguration.forceRelay}, ' 'lastMessageSeq: ${event.response.lastMessageSeq}'); final rtcConfiguration = await _buildRtcConfiguration( @@ -1336,7 +1335,7 @@ class Engine extends Disposable with EventsEmittable { }) ..on((event) async { logger.fine('Signal connected'); - reconnectAttempts = 0; + _reconnectAttempts = 0; events.emit(const EngineConnectedEvent()); }) ..on((event) async { @@ -1456,7 +1455,7 @@ class Engine extends Disposable with EventsEmittable { logger.fine('disconnect: Cancel the reconnection processing!'); await signalClient.cleanUp(); await _signalListener.cancelAll(); - clearPendingReconnect(); + _clearPendingReconnect(); } await cleanUp(); events.emit(EngineDisconnectedEvent(reason: reason)); diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index e68c9c062..f274a0289 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -302,8 +302,8 @@ class Room extends DisposableChangeNotifier with EventsEmittable { ); } catch (e) { logger.warning('could not connect to $url $e'); - if (_regionUrlProvider != null && e is WebSocketException || - (e is ConnectException && e.reason != ConnectionErrorReason.NotAllowed)) { + if (_regionUrlProvider != null && + (e is WebSocketException || (e is ConnectException && e.reason != ConnectionErrorReason.NotAllowed))) { String? nextUrl; try { nextUrl = await _regionUrlProvider!.getNextBestRegionUrl(); @@ -971,7 +971,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { } } - engine.sendSyncState( + await engine.sendSyncState( subscription: lk_rtc.UpdateSubscription( participantTracks: [], trackSids: trackSids, diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index 56544de00..e785b72fa 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -546,8 +546,6 @@ class LocalParticipant extends Participant { logger.warning('Publication not found $trackSid'); return; } - await pub.dispose(); - final track = pub.track; if (track != null) { if (room.roomOptions.stopLocalTrackOnUnpublish) { diff --git a/lib/src/participant/remote.dart b/lib/src/participant/remote.dart index 95ebd82b7..5a8c9af62 100644 --- a/lib/src/participant/remote.dart +++ b/lib/src/participant/remote.dart @@ -297,8 +297,6 @@ class RemoteParticipant extends Participant { logger.warning('Publication not found $trackSid'); return; } - await pub.dispose(); - final track = pub.track; // if has track if (track != null) {