From 0d8441ebfb8a9811dbb5c1bc4413790cbfccc08a Mon Sep 17 00:00:00 2001 From: CloudWebRTC Date: Tue, 8 Nov 2022 16:06:09 +0800 Subject: [PATCH] e2e: fix resume/full-reconnect. (#194) * fix: full re-connect. * chore: Reset fullReConnect state after ConnectionState is emitted. * Support full reconnect, fix Migration, NodeFailure, FullReconnect test cases. * revert Timeouts.connection. * update. * Refactor the resume/reconnect implementation. * more changes. * update. * Changed to simpler reconnect/recovery logic. * update. * update. * code format. * tidy. * bump version for flutter-webrtc. * chore: Remove unused exception class (SignalReconnectError). * chore: rename `fullReconnectOnNext` to `fullReconnect`. * update. * chore: Revert changes for `unpublishTrack`. * chore: Reconnect signalClient when resumeConnection. * revert `rethrow` in engine.connect. * Use removePublishedTrack instead of unpublishTrack (RemoteParticipant). * chore: Correctly handle PeerConnectionState for resume/full-reconnect. * tidy. --- example/lib/exts.dart | 1 + example/lib/widgets/controls.dart | 2 + example/pubspec.lock | 8 +- lib/src/core/engine.dart | 265 ++++++++++++++++----------- lib/src/core/room.dart | 76 ++++++-- lib/src/core/signal_client.dart | 5 + lib/src/core/transport.dart | 11 +- lib/src/events.dart | 14 ++ lib/src/exceptions.dart | 5 + lib/src/extensions.dart | 3 + lib/src/internal/events.dart | 2 + lib/src/participant/local.dart | 12 ++ lib/src/participant/participant.dart | 7 +- lib/src/participant/remote.dart | 16 +- lib/src/types/other.dart | 5 +- pubspec.lock | 6 +- pubspec.yaml | 6 +- 17 files changed, 300 insertions(+), 144 deletions(-) diff --git a/example/lib/exts.dart b/example/lib/exts.dart index 956924d..03b8567 100644 --- a/example/lib/exts.dart +++ b/example/lib/exts.dart @@ -171,6 +171,7 @@ extension LKExampleExt on BuildContext { } enum SimulateScenarioResult { + signalReconnect, nodeFailure, migration, serverLeave, diff --git a/example/lib/widgets/controls.dart b/example/lib/widgets/controls.dart index 203816c..2889898 100644 --- a/example/lib/widgets/controls.dart +++ b/example/lib/widgets/controls.dart @@ -219,6 +219,8 @@ class _ControlsWidgetState extends State { if (result != null) { print('${result}'); await widget.room.sendSimulateScenario( + signalReconnect: + result == SimulateScenarioResult.signalReconnect ? true : null, nodeFailure: result == SimulateScenarioResult.nodeFailure ? true : null, migration: result == SimulateScenarioResult.migration ? true : null, serverLeave: result == SimulateScenarioResult.serverLeave ? true : null, diff --git a/example/pubspec.lock b/example/pubspec.lock index 15cf238..acf29f6 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -98,7 +98,7 @@ packages: name: dart_webrtc url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "1.0.10" dbus: dependency: transitive description: @@ -225,7 +225,7 @@ packages: name: flutter_webrtc url: "https://pub.dartlang.org" source: hosted - version: "0.9.11" + version: "0.9.12" google_fonts: dependency: "direct main" description: @@ -274,7 +274,7 @@ packages: path: ".." relative: true source: path - version: "1.1.6" + version: "1.1.7" logging: dependency: "direct main" description: @@ -566,7 +566,7 @@ packages: name: webrtc_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.8" + version: "1.0.9" win32: dependency: transitive description: diff --git a/lib/src/core/engine.dart b/lib/src/core/engine.dart index 395173c..6a633cd 100644 --- a/lib/src/core/engine.dart +++ b/lib/src/core/engine.dart @@ -28,7 +28,6 @@ import 'transport.dart'; class Engine extends Disposable with EventsEmittable { static const _lossyDCLabel = '_lossy'; static const _reliableDCLabel = '_reliable'; - final SignalClient signalClient; final PeerConnectionCreate _peerConnectionCreate; @@ -61,6 +60,8 @@ class Engine extends Disposable with EventsEmittable { // this is helpful to know if we need to restart ICE on the publisher connection bool _hasPublished = false; + lk_models.ClientConfiguration? _clientConfiguration; + // remember url and token for reconnect String? url; String? token; @@ -75,10 +76,13 @@ class Engine extends Disposable with EventsEmittable { String? _connectedServerAddress; String? get connectedServerAddress => _connectedServerAddress; + bool fullReconnect = false; + // server-provided ice servers List _serverProvidedIceServers = []; - late final _signalListener = signalClient.createListener(synchronized: true); + late EventsListener _signalListener = + signalClient.createListener(synchronized: true); Engine({ required this.connectOptions, @@ -117,11 +121,6 @@ class Engine extends Disposable with EventsEmittable { this.roomOptions = roomOptions ?? this.roomOptions; this.fastConnectOptions = fastConnectOptions; - if (connectionState == ConnectionState.connected) { - logger.fine('already connected'); - return; - } - _updateConnectionState(ConnectionState.connecting); try { @@ -136,7 +135,8 @@ class Engine extends Disposable with EventsEmittable { // wait for join response await _signalListener.waitFor( duration: this.connectOptions.timeouts.connection, - onTimeout: () => throw ConnectException(), + onTimeout: () => throw ConnectException( + 'Timed out waiting for SignalJoinResponseEvent'), ); logger.fine('Waiting for engine to connect...'); @@ -145,7 +145,8 @@ class Engine extends Disposable with EventsEmittable { await events.waitFor( filter: (event) => event.isPrimary && event.state.isConnected(), duration: this.connectOptions.timeouts.connection, - onTimeout: () => throw ConnectException(), + onTimeout: () => throw ConnectException( + 'Timed out waiting for EnginePeerStateUpdatedEvent'), ); _updateConnectionState(ConnectionState.connected); @@ -210,9 +211,15 @@ class Engine extends Disposable with EventsEmittable { if (publisher == null) { return; } - _hasPublished = true; - publisher!.negotiate(null); + try { + publisher!.negotiate(null); + } catch (error) { + if (error is NegotiationError) { + fullReconnect = true; + } + await handleDisconnect(DisconnectReason.negotiationFailed); + } } @internal @@ -268,75 +275,6 @@ class Engine extends Disposable with EventsEmittable { await channel.send(message); } - @internal - Future reconnect() async { - if (_connectionState == ConnectionState.disconnected) { - logger.fine('Reconnect: Already closed.'); - return; - } - - if (url == null || token == null) { - throw ConnectException('could not reconnect without url and token'); - } - - Future sequence() async { - // - await signalClient.connect( - url!, - token!, - connectOptions: connectOptions, - roomOptions: roomOptions, - reconnect: true, - sid: _participantSid, - ); - - if (publisher == null || subscriber == null) { - throw UnexpectedStateException('publisher or subscribers is null'); - } - - subscriber!.restartingIce = true; - - if (_hasPublished) { - logger.fine('Reconnect: negotiating publisher...'); - await publisher!.createAndSendOffer(const RTCOfferOptions( - iceRestart: true, - )); - } - - final iceConnected = primary?.pc.connectionState?.isConnected() ?? false; - - logger.fine('Reconnect: iceConnected: $iceConnected'); - - if (!iceConnected) { - logger.fine('Reconnect: Waiting for primary to connect...'); - - await events.waitFor( - filter: (event) => event.isPrimary && event.state.isConnected(), - duration: connectOptions.timeouts.iceRestart, - onTimeout: () => throw ConnectException(), - ); - } - } - - try { - _updateConnectionState(ConnectionState.reconnecting); - await Utils.retry( - (tries, errors) { - logger.fine('Retrying connect sequence remaining ${tries} tries...'); - return sequence(); - }, - retryCondition: (_, __) => - _connectionState == ConnectionState.reconnecting, - tries: 3, - delay: const Duration(seconds: 3), - ); - _updateConnectionState(ConnectionState.connected); - } catch (error) { - // - _updateConnectionState(ConnectionState.disconnected); - } - } - Future _configurePeerConnections( {required lk_models.ClientConfigSetting forceRelay, required List serverProvidedIceServers}) async { @@ -429,13 +367,10 @@ class Engine extends Disposable with EventsEmittable { )); events.on((event) { - // - final isPrimaryOrPublisher = event.isPrimary || - (_hasPublished && event is EnginePublisherPeerStateUpdatedEvent); - - if (isPrimaryOrPublisher && event.state.isDisconnectedOrFailed()) { - // trigger reconnect sequence - _onDisconnected(DisconnectReason.peerConnection); + if (event.state.isDisconnectedOrFailed()) { + handleDisconnect(DisconnectReason.reconnect); + } else if (event.state.isClosed()) { + handleDisconnect(DisconnectReason.peerConnectionClosed); } }); @@ -599,21 +534,130 @@ class Engine extends Disposable with EventsEmittable { } } - Future _onDisconnected(DisconnectReason reason) async { + Future handleDisconnect(DisconnectReason reason) async { logger .info('onDisconnected state:${_connectionState} reason:${reason.name}'); - if (_connectionState == ConnectionState.disconnected) { - logger.fine('[$objectId] Already disconnected... $reason'); - return; + + if (!fullReconnect) { + fullReconnect = _clientConfiguration?.resumeConnection == + lk_models.ClientConfigSetting.DISABLED || + [ + DisconnectReason.leaveReconnect, + DisconnectReason.negotiationFailed, + DisconnectReason.peerConnectionClosed + ].contains(reason); } - if (_connectionState == ConnectionState.reconnecting) { + + if (_connectionState == ConnectionState.reconnecting && !fullReconnect) { logger.fine('[$objectId] Already reconnecting...'); return; } - logger.fine('[$runtimeType] Should attempt reconnect sequence...'); + if (_connectionState == ConnectionState.disconnected) { + logger.fine('[$objectId] Already disconnected... $reason'); + return; + } - await reconnect(); + logger.fine('[$runtimeType] Should attempt reconnect sequence...'); + if (fullReconnect) { + await restartConnection(); + } else { + await resumeConnection(); + } + } + + Future resumeConnection() async { + if (_connectionState == ConnectionState.disconnected) { + logger.fine('resumeConnection: Already closed.'); + return; + } + + if (url == null || token == null) { + throw ConnectException( + 'could not resume connection without url and token'); + } + + Future sequence() async { + await signalClient.connect( + url!, + token!, + connectOptions: connectOptions, + roomOptions: roomOptions, + reconnect: true, + sid: _participantSid, + ); + + if (publisher == null || subscriber == null) { + throw UnexpectedStateException('publisher or subscribers is null'); + } + + subscriber!.restartingIce = true; + + if (_hasPublished) { + logger.fine('resumeConnection: negotiating publisher...'); + await publisher!.createAndSendOffer(const RTCOfferOptions( + iceRestart: true, + )); + } + + final iceConnected = primary?.pc.connectionState?.isConnected() ?? false; + + logger.fine('resumeConnection: iceConnected: $iceConnected'); + + if (!iceConnected) { + logger.fine('resumeConnection: Waiting for primary to connect...'); + + await events.waitFor( + filter: (event) => event.isPrimary && event.state.isConnected(), + duration: connectOptions.timeouts.iceRestart, + onTimeout: () => throw ConnectException(), + ); + } + } + + try { + _updateConnectionState(ConnectionState.reconnecting); + await Utils.retry( + (tries, errors) { + logger.fine('Retrying connect sequence remaining ${tries} tries...'); + return sequence(); + }, + retryCondition: (_, __) => + _connectionState == ConnectionState.reconnecting, + tries: 3, + delay: const Duration(seconds: 3), + ); + _updateConnectionState(ConnectionState.connected); + } catch (error) { + _updateConnectionState(ConnectionState.disconnected); + } + } + + Future restartConnection([bool signalEvents = false]) async { + await publisher?.dispose(); + publisher = null; + _hasPublished = false; + + await subscriber?.dispose(); + subscriber = null; + + _reliableDCSub = null; + _reliableDCPub = null; + _lossyDCSub = null; + _lossyDCPub = null; + await _signalListener.cancelAll(); + _signalListener = signalClient.createListener(synchronized: true); + _setUpSignalListeners(); + + await connect( + url!, + token!, + roomOptions: roomOptions, + connectOptions: connectOptions, + fastConnectOptions: fastConnectOptions, + ); + + fullReconnect = false; } @internal @@ -650,6 +694,8 @@ class Engine extends Disposable with EventsEmittable { _serverProvidedIceServers = iceServersFromServer; } + _clientConfiguration = event.response.clientConfiguration; + logger.fine('onConnected subscriberPrimary: ${_subscriberPrimary}, ' 'serverVersion: ${event.response.serverVersion}, ' 'iceServers: ${event.response.iceServers}, ' @@ -666,7 +712,7 @@ class Engine extends Disposable with EventsEmittable { }) ..on((event) async { if (event.newState == ConnectionState.disconnected) { - await _onDisconnected(DisconnectReason.signal); + await handleDisconnect(DisconnectReason.signal); } }) ..on((event) async { @@ -717,12 +763,20 @@ class Engine extends Disposable with EventsEmittable { token = event.token; }) ..on((event) async { - if (_connectionState == ConnectionState.reconnecting) { - logger.warning( - '[Signal] Received Leave while engine is reconnecting, ignoring...'); - return; + if (event.canReconnect) { + fullReconnect = true; + // reconnect immediately instead of waiting for next attempt + _connectionState = ConnectionState.reconnecting; + await handleDisconnect(DisconnectReason.leaveReconnect); + } else { + if (_connectionState == ConnectionState.reconnecting) { + logger.warning( + '[Signal] Received Leave while engine is reconnecting, ignoring...'); + return; + } + _updateConnectionState(ConnectionState.disconnected); + await cleanUp(); } - await cleanUp(); }); } @@ -752,16 +806,19 @@ extension EnginePrivateMethods on Engine { newState: _connectionState, oldState: oldState, didReconnect: didReconnect, + fullReconnect: fullReconnect, )); } } extension EngineInternalMethods on Engine { @internal - List dataChannelInfo() => [ - _reliableDCPub, - _lossyDCPub - ].whereNotNull().map((e) => e.toLKInfoType()).toList(); + List dataChannelInfo() => + [_reliableDCPub, _lossyDCPub] + .whereNotNull() + .where((e) => e.id != -1) + .map((e) => e.toLKInfoType()) + .toList(); } Future getConnectedAddress(rtc.RTCPeerConnection pc) async { diff --git a/lib/src/core/room.dart b/lib/src/core/room.dart index e5a79a7..5a5e5bd 100644 --- a/lib/src/core/room.dart +++ b/lib/src/core/room.dart @@ -74,7 +74,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { // suppport for multiple event listeners late final EventsListener _engineListener; // - late final EventsListener _signalListener; + late EventsListener _signalListener; Room({ ConnectOptions connectOptions = const ConnectOptions(), @@ -140,11 +140,15 @@ class Room extends DisposableChangeNotifier with EventsEmittable { logger.fine('[Engine] Received JoinResponse, ' 'serverVersion: ${event.response.serverVersion}'); - _localParticipant = LocalParticipant( + _localParticipant ??= LocalParticipant( room: this, info: event.response.participant, ); + if (engine.fullReconnect) { + _localParticipant!.updateFromInfo(event.response.participant); + } + if (connectOptions.protocolVersion.index >= ProtocolVersion.v8.index && engine.fastConnectOptions != null) { var options = engine.fastConnectOptions!; @@ -264,19 +268,47 @@ class Room extends DisposableChangeNotifier with EventsEmittable { void _setUpEngineListeners() => _engineListener ..on((event) async { if (event.didReconnect) { + events.emit(const RoomReconnectedEvent()); // re-send tracks permissions localParticipant?.sendTrackSubscriptionPermissions(); - events.emit(const RoomReconnectedEvent()); - await _handlePostReconnect(false); + } else if (event.fullReconnect && + event.newState == ConnectionState.connecting) { + events.emit(const RoomRestartingEvent()); + // clean up RemoteParticipants + for (final participant in _participants.values) { + events.emit(ParticipantDisconnectedEvent(participant: participant)); + await participant.dispose(); + } + _participants.clear(); + _activeSpeakers.clear(); + // reset params + _name = null; + _sid = null; + _metadata = null; + _serverVersion = null; + _serverRegion = null; + } else if (event.fullReconnect && + event.newState == ConnectionState.connected) { + events.emit(const RoomRestartedEvent()); + // recreate signal listener. + await _signalListener.cancelAll(); + await _signalListener.dispose(); + _signalListener = engine.signalClient.createListener(); + _setUpSignalListeners(); + await _handlePostReconnect(event.fullReconnect); } else if (event.newState == ConnectionState.reconnecting) { events.emit(const RoomReconnectingEvent()); } else if (event.newState == ConnectionState.disconnected) { - await _cleanUp(); - events.emit(const RoomDisconnectedEvent()); + if (!event.fullReconnect) { + await _cleanUp(); + events.emit(const RoomDisconnectedEvent()); + } } // always notify ChangeNotifier notifyListeners(); }) + ..on((event) {}) + ..on((event) {}) ..on( (event) => _onEngineActiveSpeakersUpdateEvent(event.speakers)) ..on(_onDataMessageEvent) @@ -319,7 +351,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable { } Future reconnect() async { - await engine.reconnect(); + await engine.restartConnection(); } RemoteParticipant _getOrCreateRemoteParticipant( @@ -528,13 +560,13 @@ class Room extends DisposableChangeNotifier with EventsEmittable { Future _handlePostReconnect(bool isFullReconnect) async { if (isFullReconnect) { - // TODO republish tracks on full reconnect - } else { - for (var participant in participants.values) { - for (var pub in participant.trackPublications.values) { - if (pub.subscribed) { - pub.sendUpdateTrackSettings(); - } + // re-publish all tracks + await localParticipant?.rePublishAllTracks(); + } + for (var participant in participants.values) { + for (var pub in participant.trackPublications.values) { + if (pub.subscribed) { + pub.sendUpdateTrackSettings(); } } } @@ -585,13 +617,17 @@ extension RoomDebugMethods on Room { bool? migration, bool? serverLeave, bool? switchCandidate, + bool? signalReconnect, }) async { + if (signalReconnect != null && signalReconnect) { + await engine.signalClient.cleanUp(); + return; + } engine.signalClient.sendSimulateScenario( - speakerUpdate: speakerUpdate, - nodeFailure: nodeFailure, - migration: migration, - serverLeave: serverLeave, - switchCandidate: switchCandidate, - ); + speakerUpdate: speakerUpdate, + nodeFailure: nodeFailure, + migration: migration, + serverLeave: serverLeave, + switchCandidate: switchCandidate); } } diff --git a/lib/src/core/signal_client.dart b/lib/src/core/signal_client.dart index 32a124e..af33830 100644 --- a/lib/src/core/signal_client.dart +++ b/lib/src/core/signal_client.dart @@ -35,6 +35,10 @@ class SignalClient extends Disposable with EventsEmittable { Duration? _pingIntervalDuration; Timer? _pingIntervalTimer; + int get pingCount => _pingCount; + + int _pingCount = 0; + @internal SignalClient(WebSocketConnector wsConnector) : _wsConnector = wsConnector { events.listen((event) { @@ -240,6 +244,7 @@ class SignalClient extends Disposable with EventsEmittable { logger.info('signal message not set'); break; case lk_rtc.SignalResponse_Message.pong: + _pingCount++; _resetPingTimeout(); break; default: diff --git a/lib/src/core/transport.dart b/lib/src/core/transport.dart index 1d74f29..9b9ff6c 100644 --- a/lib/src/core/transport.dart +++ b/lib/src/core/transport.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc; import 'package:livekit_client/src/options.dart'; +import '../exceptions.dart'; import '../extensions.dart'; import '../internal/types.dart'; import '../logger.dart'; @@ -126,10 +127,18 @@ class Transport extends Disposable { } } + if (restartingIce && !rtc.WebRTC.platformIsWeb) { + await pc.restartIce(); + } + // actually negotiate logger.fine('starting to negotiate'); final offer = await pc.createOffer(options?.toMap() ?? {}); - await pc.setLocalDescription(offer); + try { + await pc.setLocalDescription(offer); + } catch (e) { + throw NegotiationError(e.toString()); + } onOffer?.call(offer); } diff --git a/lib/src/events.dart b/lib/src/events.dart index 05d5073..3eb7a8f 100644 --- a/lib/src/events.dart +++ b/lib/src/events.dart @@ -48,6 +48,20 @@ class RoomReconnectedEvent with RoomEvent { String toString() => '${runtimeType}()'; } +class RoomRestartingEvent with RoomEvent { + const RoomRestartingEvent(); + + @override + String toString() => '${runtimeType}()'; +} + +class RoomRestartedEvent with RoomEvent { + const RoomRestartedEvent(); + + @override + String toString() => '${runtimeType}()'; +} + /// Disconnected from the room /// Emitted by [Room]. class RoomDisconnectedEvent with RoomEvent { diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart index ec377b5..638a718 100644 --- a/lib/src/exceptions.dart +++ b/lib/src/exceptions.dart @@ -23,6 +23,11 @@ class UnexpectedStateException extends LiveKitException { : super._(msg); } +/// Exception thrown when pc negotiation fails. +class NegotiationError extends LiveKitException { + NegotiationError([String msg = 'Negotiation Error']) : super._(msg); +} + /// Failed to create a local track. /// Common reasons: /// - Required permissions not yet granted to the platform. diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index 32cff24..a3dce7a 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -80,6 +80,9 @@ extension RTCPeerConnectionStateExt on rtc.RTCPeerConnectionState { bool isConnected() => this == rtc.RTCPeerConnectionState.RTCPeerConnectionStateConnected; + bool isClosed() => + this == rtc.RTCPeerConnectionState.RTCPeerConnectionStateClosed; + bool isDisconnectedOrFailed() => [ rtc.RTCPeerConnectionState.RTCPeerConnectionStateDisconnected, rtc.RTCPeerConnectionState.RTCPeerConnectionStateFailed, diff --git a/lib/src/internal/events.dart b/lib/src/internal/events.dart index ed051f1..615497a 100644 --- a/lib/src/internal/events.dart +++ b/lib/src/internal/events.dart @@ -139,10 +139,12 @@ class SignalConnectionStateUpdatedEvent extends ConnectionStateUpdatedEvent @internal class EngineConnectionStateUpdatedEvent extends ConnectionStateUpdatedEvent with EngineEvent { + final bool fullReconnect; const EngineConnectionStateUpdatedEvent({ required ConnectionState newState, required ConnectionState oldState, required bool didReconnect, + required this.fullReconnect, DisconnectReason? disconnectReason, }) : super( newState: newState, diff --git a/lib/src/participant/local.dart b/lib/src/participant/local.dart index f99b2ba..e4ce1e2 100644 --- a/lib/src/participant/local.dart +++ b/lib/src/participant/local.dart @@ -240,6 +240,18 @@ class LocalParticipant extends Participant { await pub.dispose(); } + Future rePublishAllTracks() async { + final tracks = trackPublications.values.toList(); + trackPublications.clear(); + for (LocalTrackPublication track in tracks) { + if (track.track is LocalAudioTrack) { + await publishAudioTrack(track.track as LocalAudioTrack); + } else if (track.track is LocalVideoTrack) { + await publishVideoTrack(track.track as LocalVideoTrack); + } + } + } + /// Publish a new data payload to the room. /// @param destinationSids When empty, data will be forwarded to each participant in the room. Future publishData( diff --git a/lib/src/participant/participant.dart b/lib/src/participant/participant.dart index aa9dbaa..2bc8548 100644 --- a/lib/src/participant/participant.dart +++ b/lib/src/participant/participant.dart @@ -39,7 +39,7 @@ abstract class Participant double audioLevel = 0; /// Server assigned unique id. - final String sid; + String sid; /// User-assigned identity. String identity; @@ -160,7 +160,7 @@ abstract class Participant void updateFromInfo(lk_models.ParticipantInfo info) { identity = info.identity; _name = info.name; - // participantSid = info.sid; + sid = info.sid; if (info.metadata.isNotEmpty) { _setMetadata(info.metadata); } @@ -189,7 +189,8 @@ abstract class Participant Future unpublishTrack(String trackSid, {bool notify = true}); /// Convenience method to unpublish all tracks. - Future unpublishAllTracks({bool notify = true}) async { + Future unpublishAllTracks( + {bool notify = true, bool? stopOnUnpublish}) async { final trackSids = trackPublications.keys.toSet(); for (final trackid in trackSids) { await unpublishTrack(trackid, notify: notify); diff --git a/lib/src/participant/remote.dart b/lib/src/participant/remote.dart index 1791834..cfc2e5f 100644 --- a/lib/src/participant/remote.dart +++ b/lib/src/participant/remote.dart @@ -180,18 +180,18 @@ class RemoteParticipant extends Participant { } } - // unpublish any track that is not in the info + // remove any published track that is not in the info final validSids = info.tracks.map((e) => e.sid); final removeSids = trackPublications.keys.where((e) => !validSids.contains(e)).toSet(); for (final sid in removeSids) { - await unpublishTrack(sid); + await removePublishedTrack(sid); } } - @override - Future unpublishTrack(String trackSid, {bool notify = true}) async { - logger.finer('Unpublish track sid: $trackSid, notify: $notify'); + Future removePublishedTrack(String trackSid, + {bool notify = true}) async { + logger.finer('removePublishedTrack track sid: $trackSid, notify: $notify'); final pub = trackPublications.remove(trackSid); if (pub == null) { logger.warning('Publication not found $trackSid'); @@ -220,6 +220,12 @@ class RemoteParticipant extends Participant { await pub.dispose(); } + @Deprecated( + '`unpublishTrack` is deprecated, use `removePublishedTrack` instead') + @override + Future unpublishTrack(String trackSid, {bool notify = true}) => + removePublishedTrack(trackSid, notify: notify); + @internal lk_models.ParticipantTracks participantTracks() => lk_models.ParticipantTracks( diff --git a/lib/src/types/other.dart b/lib/src/types/other.dart index 572e2fb..8af1a35 100644 --- a/lib/src/types/other.dart +++ b/lib/src/types/other.dart @@ -62,8 +62,11 @@ enum StreamState { enum DisconnectReason { user, - peerConnection, + peerConnectionClosed, + negotiationFailed, signal, + reconnect, + leaveReconnect, } /// The reason why a track failed to publish. diff --git a/pubspec.lock b/pubspec.lock index 8c7682b..ac91303 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -154,7 +154,7 @@ packages: name: dart_webrtc url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "1.0.10" dbus: dependency: transitive description: @@ -260,7 +260,7 @@ packages: name: flutter_webrtc url: "https://pub.dartlang.org" source: hosted - version: "0.9.11" + version: "0.9.12" glob: dependency: transitive description: @@ -552,7 +552,7 @@ packages: name: webrtc_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.8" + version: "1.0.9" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1e4b4bf..ea088fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,10 +23,10 @@ dependencies: uuid: ^3.0.6 synchronized: ^3.0.0+3 protobuf: ^2.1.0 - flutter_webrtc: 0.9.11 - dart_webrtc: 1.0.9 + flutter_webrtc: 0.9.12 + dart_webrtc: 1.0.10 device_info_plus: ^6.0.0 - webrtc_interface: 1.0.8 + webrtc_interface: 1.0.9 dev_dependencies: flutter_test: