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:
Hiroshi Horie
2022-01-25 00:53:18 +07:00
committed by GitHub
parent 3d37f4491d
commit 224047c31f
25 changed files with 575 additions and 275 deletions
+98 -48
View File
@@ -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,
),
));
}