@@ -36,6 +36,7 @@ import '../proto/livekit_models.pb.dart' as lk_models;
3636import '../proto/livekit_rtc.pb.dart' as lk_rtc;
3737import '../publication/local.dart' ;
3838import '../support/disposable.dart' ;
39+ import '../support/region_url_provider.dart' ;
3940import '../support/websocket.dart' ;
4041import '../track/local/video.dart' ;
4142import '../types/internal.dart' ;
@@ -130,6 +131,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
130131
131132 bool attemptingReconnect = false ;
132133
134+ RegionUrlProvider ? _regionUrlProvider;
135+
133136 void clearReconnectTimeout () {
134137 if (reconnectTimeout != null ) {
135138 reconnectTimeout? .cancel ();
@@ -171,6 +174,7 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
171174 ConnectOptions ? connectOptions,
172175 RoomOptions ? roomOptions,
173176 FastConnectOptions ? fastConnectOptions,
177+ RegionUrlProvider ? regionUrlProvider,
174178 }) async {
175179 this .url = url;
176180 this .token = token;
@@ -179,6 +183,10 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
179183 this .roomOptions = roomOptions ?? this .roomOptions;
180184 this .fastConnectOptions = fastConnectOptions;
181185
186+ if (regionUrlProvider != null ) {
187+ _regionUrlProvider = regionUrlProvider;
188+ }
189+
182190 try {
183191 // wait for socket to connect rtc server
184192 await signalClient.connect (
@@ -192,7 +200,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
192200 await _signalListener.waitFor <SignalJoinResponseEvent >(
193201 duration: this .connectOptions.timeouts.connection,
194202 onTimeout: () => throw ConnectException (
195- 'Timed out waiting for SignalJoinResponseEvent' ),
203+ 'Timed out waiting for SignalJoinResponseEvent' ,
204+ reason: ConnectionErrorReason .Timeout ),
196205 );
197206
198207 logger.fine ('Waiting for engine to connect...' );
@@ -663,6 +672,11 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
663672 ));
664673
665674 clearReconnectTimeout ();
675+ if (token != null && _regionUrlProvider != null ) {
676+ // token may have been refreshed, we do not want to recreate the regionUrlProvider
677+ // since the current engine may have inherited a regional url
678+ _regionUrlProvider! .updateToken (token! );
679+ }
666680 logger.fine (
667681 'WebSocket reconnecting in $delay ms, retry times $reconnectAttempts ' );
668682 reconnectTimeout = Timer (Duration (milliseconds: delay), () async {
@@ -700,7 +714,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
700714 duration: connectOptions.timeouts.connection * 10 ,
701715 filter: (event) => ! event.state.contains (ConnectivityResult .none),
702716 onTimeout: () => throw ConnectException (
703- 'attemptReconnect: Timed out waiting for SignalConnectivityChangedEvent' ),
717+ 'attemptReconnect: Timed out waiting for SignalConnectivityChangedEvent' ,
718+ reason: ConnectionErrorReason .Timeout ),
704719 );
705720 }
706721
@@ -756,7 +771,8 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
756771 await events.waitFor <SignalReconnectedEvent >(
757772 duration: connectOptions.timeouts.connection,
758773 onTimeout: () => throw ConnectException (
759- 'resumeConnection: Timed out waiting for SignalReconnectedEvent' ),
774+ 'resumeConnection: Timed out waiting for SignalReconnectedEvent' ,
775+ reason: ConnectionErrorReason .Timeout ),
760776 );
761777
762778 logger.fine ('resumeConnection: reason: ${reason .name }' );
@@ -789,53 +805,65 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
789805 }
790806
791807 @internal
792- Future <void > restartConnection ([ bool signalEvents = false ] ) async {
808+ Future <void > restartConnection ({ String ? regionUrl} ) async {
793809 if (_isClosed) {
794810 return ;
795811 }
796812
797- events.emit (const EngineFullRestartingEvent ());
813+ try {
814+ events.emit (const EngineFullRestartingEvent ());
798815
799- if (signalClient.connectionState == ConnectionState .connected) {
800- await signalClient.sendLeave ();
801- }
816+ if (signalClient.connectionState == ConnectionState .connected) {
817+ await signalClient.sendLeave ();
818+ }
802819
803- await publisher? .dispose ();
804- publisher = null ;
820+ await publisher? .dispose ();
821+ publisher = null ;
805822
806- await subscriber? .dispose ();
807- subscriber = null ;
823+ await subscriber? .dispose ();
824+ subscriber = null ;
808825
809- _reliableDCSub = null ;
810- _reliableDCPub = null ;
811- _lossyDCSub = null ;
812- _lossyDCPub = null ;
826+ _reliableDCSub = null ;
827+ _reliableDCPub = null ;
828+ _lossyDCSub = null ;
829+ _lossyDCPub = null ;
813830
814- await _signalListener.cancelAll ();
831+ await _signalListener.cancelAll ();
815832
816- _signalListener = signalClient.createListener (synchronized: true );
817- _setUpSignalListeners ();
833+ _signalListener = signalClient.createListener (synchronized: true );
834+ _setUpSignalListeners ();
818835
819- await connect (
820- url! ,
821- token! ,
822- roomOptions: roomOptions,
823- connectOptions: connectOptions,
824- fastConnectOptions: fastConnectOptions,
825- );
826-
827- if (_hasPublished) {
828- await negotiate ();
829- logger.fine ('restartConnection: Waiting for publisher to ice-connect...' );
830- await events.waitFor <EnginePublisherPeerStateUpdatedEvent >(
831- filter: (event) => event.state.isConnected (),
832- duration: connectOptions.timeouts.peerConnection,
836+ await connect (
837+ regionUrl ?? url! ,
838+ token! ,
839+ roomOptions: roomOptions,
840+ connectOptions: connectOptions,
841+ fastConnectOptions: fastConnectOptions,
833842 );
834- }
835-
836- fullReconnectOnNext = false ;
837843
838- events.emit (const EngineRestartedEvent ());
844+ if (_hasPublished) {
845+ await negotiate ();
846+ logger
847+ .fine ('restartConnection: Waiting for publisher to ice-connect...' );
848+ await events.waitFor <EnginePublisherPeerStateUpdatedEvent >(
849+ filter: (event) => event.state.isConnected (),
850+ duration: connectOptions.timeouts.peerConnection,
851+ );
852+ }
853+ fullReconnectOnNext = false ;
854+ _regionUrlProvider? .resetAttempts ();
855+ events.emit (const EngineRestartedEvent ());
856+ } catch (error) {
857+ final nextRegionUrl = await _regionUrlProvider? .getNextBestRegionUrl ();
858+ if (nextRegionUrl != null ) {
859+ await restartConnection (regionUrl: nextRegionUrl);
860+ return ;
861+ } else {
862+ // no more regions to try (or we're not on cloud)
863+ _regionUrlProvider? .resetAttempts ();
864+ rethrow ;
865+ }
866+ }
839867 }
840868
841869 @internal
@@ -992,19 +1020,32 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
9921020 token = event.token;
9931021 })
9941022 ..on < SignalLeaveEvent > ((event) async {
995- if (event.canReconnect) {
996- fullReconnectOnNext = true ;
997- // reconnect immediately instead of waiting for next attempt
998- await handleDisconnect (ClientDisconnectReason .leaveReconnect);
999- } else {
1000- if (connectionState == ConnectionState .reconnecting) {
1001- logger.warning (
1002- '[Signal] Received Leave while engine is reconnecting, ignoring...' );
1003- return ;
1004- }
1005- await signalClient.cleanUp ();
1006- await cleanUp ();
1007- events.emit (EngineDisconnectedEvent (reason: event.reason.toSDKType ()));
1023+ if (event.regions != null && _regionUrlProvider != null ) {
1024+ logger.fine ('updating regions' );
1025+ _regionUrlProvider? .setServerReportedRegions (event.regions! );
1026+ }
1027+ switch (event.action) {
1028+ case lk_rtc.LeaveRequest_Action .DISCONNECT :
1029+ if (connectionState == ConnectionState .reconnecting) {
1030+ logger.warning (
1031+ '[Signal] Received Leave while engine is reconnecting, ignoring...' );
1032+ return ;
1033+ }
1034+ await signalClient.cleanUp ();
1035+ await cleanUp ();
1036+ events
1037+ .emit (EngineDisconnectedEvent (reason: event.reason.toSDKType ()));
1038+ break ;
1039+ case lk_rtc.LeaveRequest_Action .RECONNECT :
1040+ fullReconnectOnNext = true ;
1041+ // reconnect immediately instead of waiting for next attempt
1042+ await handleDisconnect (ClientDisconnectReason .leaveReconnect);
1043+ break ;
1044+ case lk_rtc.LeaveRequest_Action .RESUME :
1045+ // reconnect immediately instead of waiting for next attempt
1046+ await handleDisconnect (ClientDisconnectReason .leaveReconnect);
1047+ default :
1048+ break ;
10081049 }
10091050 });
10101051
@@ -1016,6 +1057,10 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
10161057 await cleanUp ();
10171058 }
10181059 }
1060+
1061+ void setRegionUrlProvider (RegionUrlProvider provider) {
1062+ _regionUrlProvider = provider;
1063+ }
10191064}
10201065
10211066extension EnginePrivateMethods on Engine {
0 commit comments