Skip to content

Commit 60e5303

Browse files
authored
Protocol v16 support (#1003)
1 parent 832a523 commit 60e5303

11 files changed

Lines changed: 236 additions & 64 deletions

File tree

.changes/protocol-v16

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
minor type="feature" "Support up to protocol v16 with room move and request response handling"

example/lib/widgets/controls.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,11 @@ class _ControlsWidgetState extends State<ControlsWidget> {
236236
}
237237

238238
if (SimulateScenarioResult.participantMetadata == result) {
239-
widget.room.localParticipant?.setMetadata('new metadata ${widget.room.localParticipant?.identity}');
239+
await widget.room.localParticipant?.setMetadata('new metadata ${widget.room.localParticipant?.identity}');
240240
}
241241

242242
if (SimulateScenarioResult.participantName == result) {
243-
widget.room.localParticipant?.setName('new name for ${widget.room.localParticipant?.identity}');
243+
await widget.room.localParticipant?.setName('new name for ${widget.room.localParticipant?.identity}');
244244
}
245245

246246
await widget.room.sendSimulateScenario(

lib/src/core/engine.dart

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,35 +1381,43 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
13811381
token = event.token;
13821382
})
13831383
..on<SignalLeaveEvent>((event) async {
1384+
logger.fine('[Signal] Leave received, action: ${event.action}, reason: ${event.reason}');
13841385
if (event.regions != null && _regionUrlProvider != null) {
13851386
logger.fine('updating regions');
13861387
_regionUrlProvider?.setServerReportedRegions(event.regions!);
13871388
}
1388-
switch (event.action) {
1389-
case lk_rtc.LeaveRequest_Action.DISCONNECT:
1390-
if (connectionState == ConnectionState.reconnecting) {
1391-
logger.warning('[Signal] Received Leave while engine is reconnecting, ignoring...');
1392-
return;
1393-
}
1394-
await signalClient.cleanUp();
1395-
fullReconnectOnNext = false;
1396-
await disconnect();
1397-
events.emit(EngineDisconnectedEvent(reason: event.reason.toSDKType()));
1398-
break;
1399-
case lk_rtc.LeaveRequest_Action.RECONNECT:
1400-
fullReconnectOnNext = true;
1401-
// reconnect immediately instead of waiting for next attempt
1402-
await handleReconnect(ClientDisconnectReason.leaveReconnect);
1403-
break;
1404-
case lk_rtc.LeaveRequest_Action.RESUME:
1405-
// reconnect immediately instead of waiting for next attempt
1406-
await handleReconnect(ClientDisconnectReason.leaveReconnect);
1407-
default:
1408-
break;
1389+
// Protocol v13: LeaveRequest.action replaces the deprecated canReconnect boolean.
1390+
// canReconnect is still checked for backward compatibility with v12 servers
1391+
// (where action defaults to DISCONNECT=0 since it's unset).
1392+
if (event.action == lk_rtc.LeaveRequest_Action.RESUME) {
1393+
fullReconnectOnNext = false;
1394+
// reconnect immediately instead of waiting for next attempt
1395+
await handleReconnect(ClientDisconnectReason.leaveReconnect);
1396+
} else if (event.action == lk_rtc.LeaveRequest_Action.RECONNECT || event.canReconnect) {
1397+
fullReconnectOnNext = true;
1398+
// reconnect immediately instead of waiting for next attempt
1399+
await handleReconnect(ClientDisconnectReason.leaveReconnect);
1400+
} else {
1401+
// DISCONNECT or v12 server with canReconnect=false
1402+
await signalClient.cleanUp();
1403+
fullReconnectOnNext = false;
1404+
await disconnect(reason: event.reason.toSDKType());
14091405
}
1406+
})
1407+
..on<SignalRequestResponseEvent>((event) async {
1408+
events.emit(EngineRequestResponseEvent(response: event.response));
1409+
})
1410+
..on<SignalRoomMovedEvent>((event) async {
1411+
logger.fine('[Signal] RoomMoved received, room: ${event.response.room.name}');
1412+
if (event.response.hasParticipant()) {
1413+
signalClient.participantSid = event.response.participant.sid;
1414+
}
1415+
events.emit(EngineRoomMovedEvent(response: event.response));
14101416
});
14111417

1412-
Future<void> disconnect() async {
1418+
Future<void> disconnect({
1419+
DisconnectReason reason = DisconnectReason.clientInitiated,
1420+
}) async {
14131421
_isClosed = true;
14141422
events.emit(EngineClosingEvent());
14151423
if (connectionState == ConnectionState.connected) {
@@ -1420,11 +1428,9 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
14201428
await signalClient.cleanUp();
14211429
await _signalListener.cancelAll();
14221430
clearPendingReconnect();
1423-
events.emit(EngineDisconnectedEvent(
1424-
reason: DisconnectReason.clientInitiated,
1425-
));
14261431
}
14271432
await cleanUp();
1433+
events.emit(EngineDisconnectedEvent(reason: reason));
14281434
}
14291435
}
14301436

lib/src/core/room.dart

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -390,15 +390,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
390390
state: publication.subscriptionState,
391391
));
392392
})
393-
..on<SignalRoomUpdateEvent>((event) async {
394-
_metadata = event.room.metadata;
395-
_roomInfo = event.room;
396-
emitWhenConnected(RoomMetadataChangedEvent(metadata: event.room.metadata));
397-
if (_isRecording != event.room.activeRecording) {
398-
_isRecording = event.room.activeRecording;
399-
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
400-
}
401-
})
393+
..on<SignalRoomUpdateEvent>((event) async => _applyRoomUpdate(event.room))
402394
..on<SignalRemoteMuteTrackEvent>((event) async {
403395
final publication = localParticipant?.trackPublications[event.sid];
404396

@@ -421,17 +413,10 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
421413

422414
void _setUpEngineListeners() => _engineListener
423415
..on<EngineJoinResponseEvent>((event) async {
424-
_roomInfo = event.response.room;
425-
_name = event.response.room.name;
426-
_metadata = event.response.room.metadata;
416+
_applyRoomUpdate(event.response.room);
427417
_serverVersion = event.response.serverVersion;
428418
_serverRegion = event.response.serverRegion;
429419

430-
if (_isRecording != event.response.room.activeRecording) {
431-
_isRecording = event.response.room.activeRecording;
432-
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
433-
}
434-
435420
logger.fine('[Engine] Received JoinResponse, '
436421
'serverVersion: ${event.response.serverVersion}');
437422

@@ -579,6 +564,39 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
579564
..on<EngineActiveSpeakersUpdateEvent>((event) => _onEngineActiveSpeakersUpdateEvent(event.speakers))
580565
..on<EngineDataPacketReceivedEvent>(_onDataMessageEvent)
581566
..on<EngineTranscriptionReceivedEvent>(_onTranscriptionEvent)
567+
..on<EngineRequestResponseEvent>((event) {
568+
localParticipant?.handleSignalRequestResponse(event.response);
569+
})
570+
..on<EngineRoomMovedEvent>((event) async {
571+
final response = event.response;
572+
logger.fine('Room moved to: ${response.room.name}');
573+
574+
// Apply room info from move response
575+
if (response.hasRoom()) {
576+
_applyRoomUpdate(response.room);
577+
}
578+
579+
// Disconnect all remote participants
580+
final identities = _remoteParticipants.byIdentity.keys.toList();
581+
for (final identity in identities) {
582+
await _handleParticipantDisconnect(identity);
583+
}
584+
585+
// Emit public event
586+
events.emit(RoomMovedEvent(roomName: response.room.name));
587+
588+
// Update local participant info
589+
if (response.hasParticipant()) {
590+
await localParticipant?.updateFromInfo(response.participant);
591+
}
592+
593+
// Add new participants
594+
if (response.otherParticipants.isNotEmpty) {
595+
await _onParticipantUpdateEvent(response.otherParticipants);
596+
}
597+
598+
notifyListeners();
599+
})
582600
..on<AudioPlaybackStarted>((event) {
583601
_handleAudioPlaybackStarted();
584602
})
@@ -993,12 +1011,30 @@ extension RoomPrivateMethods on Room {
9931011
await NativeAudioManagement.stop();
9941012

9951013
// reset params
1014+
_roomInfo = null;
9961015
_name = null;
9971016
_metadata = null;
1017+
_isRecording = false;
9981018
_serverVersion = null;
9991019
_serverRegion = null;
10001020
}
10011021

1022+
/// Applies room info from server. Skips metadata event on first join
1023+
/// since there is no previous state to compare against.
1024+
void _applyRoomUpdate(lk_models.Room room) {
1025+
final oldRoom = _roomInfo;
1026+
_roomInfo = room;
1027+
_name = room.name;
1028+
_metadata = room.metadata;
1029+
if (oldRoom != null && oldRoom.metadata != room.metadata) {
1030+
emitWhenConnected(RoomMetadataChangedEvent(metadata: room.metadata));
1031+
}
1032+
if (oldRoom?.activeRecording != room.activeRecording) {
1033+
_isRecording = room.activeRecording;
1034+
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
1035+
}
1036+
}
1037+
10021038
@internal
10031039
void emitWhenConnected(RoomEvent event) {
10041040
if (connectionState == ConnectionState.connected) {

lib/src/core/signal_client.dart

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
5757
int _pingCount = 0;
5858
String? participantSid;
5959

60+
int _requestId = 0;
61+
62+
@internal
63+
int getNextRequestId() {
64+
_requestId += 1;
65+
return _requestId;
66+
}
67+
6068
List<ConnectivityResult> _connectivityResult = [];
6169
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;
6270

@@ -199,7 +207,11 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
199207

200208
Future<void> sendLeave() async {
201209
_sendRequest(lk_rtc.SignalRequest(
202-
leave: lk_rtc.LeaveRequest(canReconnect: false, reason: lk_models.DisconnectReason.CLIENT_INITIATED)));
210+
leave: lk_rtc.LeaveRequest(
211+
reason: lk_models.DisconnectReason.CLIENT_INITIATED,
212+
// server doesn't process this field, keeping it here to indicate the intent of a full disconnect
213+
action: lk_rtc.LeaveRequest_Action.DISCONNECT,
214+
)));
203215
}
204216

205217
// resets internal state to a re-usable state
@@ -334,6 +346,17 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
334346
case lk_rtc.SignalResponse_Message.reconnect:
335347
events.emit(SignalReconnectResponseEvent(response: msg.reconnect));
336348
break;
349+
case lk_rtc.SignalResponse_Message.requestResponse:
350+
logger.fine('received request response: ${msg.requestResponse.reason}');
351+
events.emit(SignalRequestResponseEvent(response: msg.requestResponse));
352+
break;
353+
case lk_rtc.SignalResponse_Message.roomMoved:
354+
logger.fine('received room moved: ${msg.roomMoved.room.name}');
355+
if (msg.roomMoved.token.isNotEmpty) {
356+
events.emit(SignalTokenUpdatedEvent(token: msg.roomMoved.token));
357+
}
358+
events.emit(SignalRoomMovedEvent(response: msg.roomMoved));
359+
break;
337360
default:
338361
logger.warning('received unknown signal message');
339362
}
@@ -428,9 +451,12 @@ extension SignalClientRequests on SignalClient {
428451
));
429452

430453
@internal
431-
void sendUpdateLocalMetadata(lk_rtc.UpdateParticipantMetadata metadata) => _sendRequest(lk_rtc.SignalRequest(
432-
updateMetadata: metadata,
433-
));
454+
int sendUpdateLocalMetadata(lk_rtc.UpdateParticipantMetadata metadata) {
455+
final requestId = getNextRequestId();
456+
metadata.requestId = requestId;
457+
_sendRequest(lk_rtc.SignalRequest(updateMetadata: metadata));
458+
return requestId;
459+
}
434460

435461
@internal
436462
void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) => _sendRequest(lk_rtc.SignalRequest(

lib/src/events.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,3 +637,13 @@ class PreConnectAudioBufferStoppedEvent with RoomEvent {
637637
String toString() => '${runtimeType}'
638638
'(bufferedSize: ${bufferedSize}, isDataSent: ${isBufferSent})';
639639
}
640+
641+
/// Fired when the participant has been moved to a different room by the server.
642+
/// Emitted by [Room].
643+
class RoomMovedEvent with RoomEvent {
644+
final String roomName;
645+
const RoomMovedEvent({required this.roomName});
646+
647+
@override
648+
String toString() => '${runtimeType}(roomName: $roomName)';
649+
}

lib/src/extensions.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ extension ProtocolVersionExt on ProtocolVersion {
6666
ProtocolVersion.v10: '10',
6767
ProtocolVersion.v11: '11',
6868
ProtocolVersion.v12: '12',
69+
ProtocolVersion.v13: '13',
70+
ProtocolVersion.v14: '14',
71+
ProtocolVersion.v15: '15',
72+
ProtocolVersion.v16: '16',
6973
}[this]!;
7074
}
7175

lib/src/internal/events.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,10 +543,47 @@ class SignalTokenUpdatedEvent with SignalEvent, InternalEvent {
543543
String toString() => '${runtimeType}(token: ${token})';
544544
}
545545

546+
@internal
547+
class SignalRequestResponseEvent with SignalEvent, InternalEvent {
548+
final lk_rtc.RequestResponse response;
549+
const SignalRequestResponseEvent({required this.response});
550+
551+
@override
552+
String toString() => '${runtimeType}'
553+
'(requestId: ${response.requestId}, reason: ${response.reason})';
554+
}
555+
556+
@internal
557+
class SignalRoomMovedEvent with SignalEvent, InternalEvent {
558+
final lk_rtc.RoomMovedResponse response;
559+
const SignalRoomMovedEvent({required this.response});
560+
561+
@override
562+
String toString() => '${runtimeType}(room: ${response.room.name})';
563+
}
564+
546565
// ----------------------------------------------------------------------
547566
// Engine events
548567
// ----------------------------------------------------------------------
549568

569+
@internal
570+
class EngineRequestResponseEvent with EngineEvent, InternalEvent {
571+
final lk_rtc.RequestResponse response;
572+
const EngineRequestResponseEvent({required this.response});
573+
574+
@override
575+
String toString() => '${runtimeType}(requestId: ${response.requestId})';
576+
}
577+
578+
@internal
579+
class EngineRoomMovedEvent with EngineEvent, InternalEvent {
580+
final lk_rtc.RoomMovedResponse response;
581+
const EngineRoomMovedEvent({required this.response});
582+
583+
@override
584+
String toString() => '${runtimeType}(room: ${response.room.name})';
585+
}
586+
550587
@internal
551588
class EngineTrackAddedEvent with EngineEvent, InternalEvent {
552589
final rtc.MediaStreamTrack track;

lib/src/options.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class ConnectOptions {
6464
const ConnectOptions({
6565
this.autoSubscribe = true,
6666
this.rtcConfiguration = const RTCConfiguration(),
67-
this.protocolVersion = ProtocolVersion.v12,
67+
this.protocolVersion = ProtocolVersion.v16,
6868
this.timeouts = Timeouts.defaultTimeouts,
6969
});
7070
}

0 commit comments

Comments
 (0)