Session migration (#72)
* impl * retry logic * simulate * pass checks * adjust log level * retry condition * fix simulate scenario params * format * use `RTCPeerConnectionState` instead of `RTCIceConnectionState` * adjust retry * additional check for completer * more logging * fix id * pub * change package * doc update * retry tests * format * apply suggestions
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../events.dart';
|
||||
import '../exceptions.dart';
|
||||
@@ -23,6 +24,7 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
|
||||
LiveKitWebSocket? _ws;
|
||||
|
||||
@internal
|
||||
SignalClient() {
|
||||
events.listen((event) {
|
||||
logger.fine('[SignalEvent] $event');
|
||||
@@ -30,24 +32,33 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
|
||||
onDispose(() async {
|
||||
await events.dispose();
|
||||
await close();
|
||||
await _cleanUp();
|
||||
});
|
||||
}
|
||||
|
||||
@internal
|
||||
Future<void> connect(
|
||||
String uriString,
|
||||
String token, {
|
||||
ConnectOptions? connectOptions,
|
||||
bool reconnect = false,
|
||||
}) async {
|
||||
final rtcUri = await Utils.buildUri(
|
||||
uriString,
|
||||
token: token,
|
||||
connectOptions: connectOptions,
|
||||
reconnect: reconnect,
|
||||
);
|
||||
|
||||
logger.fine('SignalClient connecting with url: $rtcUri');
|
||||
|
||||
try {
|
||||
_updateConnectionState(reconnect
|
||||
? ConnectionState.reconnecting
|
||||
: ConnectionState.connecting);
|
||||
// Clean up existing socket
|
||||
await _cleanUp();
|
||||
// Attempt to connect
|
||||
_ws = await LiveKitWebSocket.connect(
|
||||
rtcUri,
|
||||
WebSocketEventHandlers(
|
||||
@@ -56,18 +67,23 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
onError: _handleError,
|
||||
),
|
||||
);
|
||||
// Successful connection
|
||||
_updateConnectionState(ConnectionState.connected);
|
||||
} catch (socketError) {
|
||||
// Re-build same uri for validate mode
|
||||
final validateUri = await Utils.buildUri(
|
||||
uriString,
|
||||
token: token,
|
||||
connectOptions: connectOptions,
|
||||
validate: true,
|
||||
forceSecure: rtcUri.isSecureScheme,
|
||||
);
|
||||
|
||||
// Attempt Validation
|
||||
try {
|
||||
// Skip validation if reconnect mode
|
||||
if (reconnect) rethrow;
|
||||
|
||||
// Re-build same uri for validate mode
|
||||
final validateUri = await Utils.buildUri(
|
||||
uriString,
|
||||
token: token,
|
||||
connectOptions: connectOptions,
|
||||
validate: true,
|
||||
forceSecure: rtcUri.isSecureScheme,
|
||||
);
|
||||
|
||||
final validateResponse = await http.get(validateUri);
|
||||
if (validateResponse.statusCode != 200) {
|
||||
throw ConnectException(validateResponse.body);
|
||||
@@ -78,44 +94,22 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
if (error is ConnectException) rethrow;
|
||||
// HTTP doesn't work either
|
||||
throw ConnectException();
|
||||
} finally {
|
||||
_updateConnectionState(ConnectionState.disconnected);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reconnect(
|
||||
String uriString,
|
||||
String token, {
|
||||
ConnectOptions? connectOptions,
|
||||
}) async {
|
||||
logger.fine('SignalClient reconnecting...');
|
||||
_connectionState = ConnectionState.reconnecting;
|
||||
Future<void> _cleanUp() async {
|
||||
await _ws?.dispose();
|
||||
_ws = null;
|
||||
|
||||
final rtcUri = await Utils.buildUri(
|
||||
uriString,
|
||||
token: token,
|
||||
reconnect: true,
|
||||
connectOptions: connectOptions,
|
||||
);
|
||||
|
||||
_ws = await LiveKitWebSocket.connect(
|
||||
rtcUri,
|
||||
WebSocketEventHandlers(
|
||||
onData: _onSocketData,
|
||||
onDispose: _onSocketDispose,
|
||||
onError: _handleError,
|
||||
),
|
||||
);
|
||||
|
||||
logger.fine('SignalClient socket reconnected');
|
||||
_connectionState = ConnectionState.connected;
|
||||
}
|
||||
|
||||
Future<void> close() async {
|
||||
logger.fine('SignalClient close');
|
||||
await _ws?.dispose();
|
||||
_ws = null;
|
||||
@internal
|
||||
Future<void> disconnect() async {
|
||||
logger.fine('SignalClient disconnect');
|
||||
await _cleanUp();
|
||||
}
|
||||
|
||||
void _sendRequest(lk_rtc.SignalRequest req) {
|
||||
@@ -129,13 +123,30 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
_ws?.send(buf);
|
||||
}
|
||||
|
||||
void _updateConnectionState(ConnectionState newValue) {
|
||||
if (_connectionState == newValue) return;
|
||||
|
||||
logger.fine('SignalClient ConnectionState '
|
||||
'${_connectionState.name} -> ${newValue.name}');
|
||||
|
||||
bool didReconnect = _connectionState == ConnectionState.reconnecting &&
|
||||
newValue == ConnectionState.connected;
|
||||
|
||||
_connectionState = newValue;
|
||||
|
||||
events.emit(SignalConnectionStateUpdatedEvent(
|
||||
connectionState: _connectionState,
|
||||
didReconnect: didReconnect,
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> _onSocketData(dynamic message) async {
|
||||
if (message is! List<int>) return;
|
||||
final msg = lk_rtc.SignalResponse.fromBuffer(message);
|
||||
|
||||
switch (msg.whichMessage()) {
|
||||
case lk_rtc.SignalResponse_Message.join:
|
||||
events.emit(SignalConnectedEvent(response: msg.join));
|
||||
events.emit(SignalJoinResponseEvent(response: msg.join));
|
||||
break;
|
||||
case lk_rtc.SignalResponse_Message.answer:
|
||||
events.emit(SignalAnswerEvent(sd: msg.answer.toSDKType()));
|
||||
@@ -209,23 +220,25 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
|
||||
// don't emit event's when reconnecting state
|
||||
if (_connectionState != ConnectionState.reconnecting) {
|
||||
logger.fine('SignalClient did disconnect ${_connectionState}');
|
||||
_connectionState = ConnectionState.disconnected;
|
||||
events.emit(const SignalCloseEvent());
|
||||
_updateConnectionState(ConnectionState.disconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SignalClientRequests on SignalClient {
|
||||
@internal
|
||||
void sendOffer(rtc.RTCSessionDescription offer) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
offer: offer.toSDKType(),
|
||||
offer: offer.toPBType(),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendAnswer(rtc.RTCSessionDescription answer) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
answer: answer.toSDKType(),
|
||||
answer: answer.toPBType(),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendIceCandidate(
|
||||
rtc.RTCIceCandidate candidate, lk_rtc.SignalTarget target) =>
|
||||
_sendRequest(
|
||||
@@ -237,6 +250,7 @@ extension SignalClientRequests on SignalClient {
|
||||
),
|
||||
);
|
||||
|
||||
@internal
|
||||
void sendMuteTrack(String trackSid, bool muted) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
mute: lk_rtc.MuteTrackRequest(
|
||||
@@ -245,6 +259,7 @@ extension SignalClientRequests on SignalClient {
|
||||
),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendAddTrack({
|
||||
required String cid,
|
||||
required String name,
|
||||
@@ -252,7 +267,7 @@ extension SignalClientRequests on SignalClient {
|
||||
required lk_models.TrackSource source,
|
||||
VideoDimensions? dimensions,
|
||||
bool? dtx,
|
||||
List<lk_models.VideoLayer>? videoLayers,
|
||||
Iterable<lk_models.VideoLayer>? videoLayers,
|
||||
}) {
|
||||
final req = lk_rtc.AddTrackRequest(
|
||||
cid: cid,
|
||||
@@ -284,19 +299,22 @@ extension SignalClientRequests on SignalClient {
|
||||
));
|
||||
}
|
||||
|
||||
@internal
|
||||
void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
trackSetting: settings,
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendUpdateSubscription(lk_rtc.UpdateSubscription subscription) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
subscription: subscription,
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendUpdateVideoLayers(
|
||||
String trackSid,
|
||||
List<lk_models.VideoLayer> layers,
|
||||
Iterable<lk_models.VideoLayer> layers,
|
||||
) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
updateLayers: lk_rtc.UpdateVideoLayers(
|
||||
@@ -305,9 +323,10 @@ extension SignalClientRequests on SignalClient {
|
||||
),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendUpdateSubscriptionPermissions({
|
||||
required bool allParticipants,
|
||||
required List<lk_rtc.TrackPermission> trackPermissions,
|
||||
required Iterable<lk_rtc.TrackPermission> trackPermissions,
|
||||
}) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
subscriptionPermissions: lk_rtc.UpdateSubscriptionPermissions(
|
||||
@@ -316,7 +335,38 @@ extension SignalClientRequests on SignalClient {
|
||||
),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendLeave() => _sendRequest(lk_rtc.SignalRequest(
|
||||
leave: lk_rtc.LeaveRequest(),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendSyncState({
|
||||
required lk_rtc.SessionDescription? answer,
|
||||
required lk_rtc.UpdateSubscription subscription,
|
||||
required Iterable<lk_rtc.TrackPublishedResponse>? publishTracks,
|
||||
}) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
syncState: lk_rtc.SyncState(
|
||||
answer: answer,
|
||||
subscription: subscription,
|
||||
publishTracks: publishTracks,
|
||||
),
|
||||
));
|
||||
|
||||
@internal
|
||||
void sendSimulateScenario({
|
||||
int? speakerUpdate,
|
||||
bool? nodeFailure,
|
||||
bool? migration,
|
||||
bool? serverLeave,
|
||||
}) =>
|
||||
_sendRequest(lk_rtc.SignalRequest(
|
||||
simulate: lk_rtc.SimulateScenario(
|
||||
speakerUpdate: speakerUpdate,
|
||||
nodeFailure: nodeFailure,
|
||||
migration: migration,
|
||||
serverLeave: serverLeave,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user