Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changes/protocol-v16
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
minor type="feature" "Support up to protocol v16 with room move and request response handling"
4 changes: 2 additions & 2 deletions example/lib/widgets/controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,11 @@ class _ControlsWidgetState extends State<ControlsWidget> {
}

if (SimulateScenarioResult.participantMetadata == result) {
widget.room.localParticipant?.setMetadata('new metadata ${widget.room.localParticipant?.identity}');
await widget.room.localParticipant?.setMetadata('new metadata ${widget.room.localParticipant?.identity}');
}

if (SimulateScenarioResult.participantName == result) {
widget.room.localParticipant?.setName('new name for ${widget.room.localParticipant?.identity}');
await widget.room.localParticipant?.setName('new name for ${widget.room.localParticipant?.identity}');
}

await widget.room.sendSimulateScenario(
Expand Down
56 changes: 31 additions & 25 deletions lib/src/core/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1381,35 +1381,43 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
token = event.token;
})
..on<SignalLeaveEvent>((event) async {
logger.fine('[Signal] Leave received, action: ${event.action}, reason: ${event.reason}');
if (event.regions != null && _regionUrlProvider != null) {
logger.fine('updating regions');
_regionUrlProvider?.setServerReportedRegions(event.regions!);
}
switch (event.action) {
case lk_rtc.LeaveRequest_Action.DISCONNECT:
if (connectionState == ConnectionState.reconnecting) {
logger.warning('[Signal] Received Leave while engine is reconnecting, ignoring...');
return;
}
await signalClient.cleanUp();
fullReconnectOnNext = false;
await disconnect();
events.emit(EngineDisconnectedEvent(reason: event.reason.toSDKType()));
break;
case lk_rtc.LeaveRequest_Action.RECONNECT:
fullReconnectOnNext = true;
// reconnect immediately instead of waiting for next attempt
await handleReconnect(ClientDisconnectReason.leaveReconnect);
break;
case lk_rtc.LeaveRequest_Action.RESUME:
// reconnect immediately instead of waiting for next attempt
await handleReconnect(ClientDisconnectReason.leaveReconnect);
default:
break;
// Protocol v13: LeaveRequest.action replaces the deprecated canReconnect boolean.
// canReconnect is still checked for backward compatibility with v12 servers
// (where action defaults to DISCONNECT=0 since it's unset).
if (event.action == lk_rtc.LeaveRequest_Action.RESUME) {
fullReconnectOnNext = false;
// reconnect immediately instead of waiting for next attempt
await handleReconnect(ClientDisconnectReason.leaveReconnect);
} else if (event.action == lk_rtc.LeaveRequest_Action.RECONNECT || event.canReconnect) {
fullReconnectOnNext = true;
// reconnect immediately instead of waiting for next attempt
await handleReconnect(ClientDisconnectReason.leaveReconnect);
} else {
// DISCONNECT or v12 server with canReconnect=false
await signalClient.cleanUp();
fullReconnectOnNext = false;
await disconnect(reason: event.reason.toSDKType());
}
})
..on<SignalRequestResponseEvent>((event) async {
events.emit(EngineRequestResponseEvent(response: event.response));
})
..on<SignalRoomMovedEvent>((event) async {
logger.fine('[Signal] RoomMoved received, room: ${event.response.room.name}');
if (event.response.hasParticipant()) {
signalClient.participantSid = event.response.participant.sid;
}
events.emit(EngineRoomMovedEvent(response: event.response));
});

Future<void> disconnect() async {
Future<void> disconnect({
DisconnectReason reason = DisconnectReason.clientInitiated,
}) async {
_isClosed = true;
events.emit(EngineClosingEvent());
if (connectionState == ConnectionState.connected) {
Expand All @@ -1420,11 +1428,9 @@ class Engine extends Disposable with EventsEmittable<EngineEvent> {
await signalClient.cleanUp();
await _signalListener.cancelAll();
clearPendingReconnect();
events.emit(EngineDisconnectedEvent(
reason: DisconnectReason.clientInitiated,
));
}
await cleanUp();
events.emit(EngineDisconnectedEvent(reason: reason));
}
}

Expand Down
70 changes: 53 additions & 17 deletions lib/src/core/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,7 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
state: publication.subscriptionState,
));
})
..on<SignalRoomUpdateEvent>((event) async {
_metadata = event.room.metadata;
_roomInfo = event.room;
emitWhenConnected(RoomMetadataChangedEvent(metadata: event.room.metadata));
if (_isRecording != event.room.activeRecording) {
_isRecording = event.room.activeRecording;
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
}
})
..on<SignalRoomUpdateEvent>((event) async => _applyRoomUpdate(event.room))
..on<SignalRemoteMuteTrackEvent>((event) async {
final publication = localParticipant?.trackPublications[event.sid];

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

void _setUpEngineListeners() => _engineListener
..on<EngineJoinResponseEvent>((event) async {
_roomInfo = event.response.room;
_name = event.response.room.name;
_metadata = event.response.room.metadata;
_applyRoomUpdate(event.response.room);
_serverVersion = event.response.serverVersion;
_serverRegion = event.response.serverRegion;

if (_isRecording != event.response.room.activeRecording) {
_isRecording = event.response.room.activeRecording;
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
}

logger.fine('[Engine] Received JoinResponse, '
'serverVersion: ${event.response.serverVersion}');

Expand Down Expand Up @@ -579,6 +564,39 @@ class Room extends DisposableChangeNotifier with EventsEmittable<RoomEvent> {
..on<EngineActiveSpeakersUpdateEvent>((event) => _onEngineActiveSpeakersUpdateEvent(event.speakers))
..on<EngineDataPacketReceivedEvent>(_onDataMessageEvent)
..on<EngineTranscriptionReceivedEvent>(_onTranscriptionEvent)
..on<EngineRequestResponseEvent>((event) {
localParticipant?.handleSignalRequestResponse(event.response);
})
..on<EngineRoomMovedEvent>((event) async {
final response = event.response;
logger.fine('Room moved to: ${response.room.name}');

// Apply room info from move response
if (response.hasRoom()) {
_applyRoomUpdate(response.room);
}

// Disconnect all remote participants
final identities = _remoteParticipants.byIdentity.keys.toList();
for (final identity in identities) {
await _handleParticipantDisconnect(identity);
}

// Emit public event
events.emit(RoomMovedEvent(roomName: response.room.name));

// Update local participant info
if (response.hasParticipant()) {
await localParticipant?.updateFromInfo(response.participant);
}

// Add new participants
if (response.otherParticipants.isNotEmpty) {
await _onParticipantUpdateEvent(response.otherParticipants);
}

notifyListeners();
})
..on<AudioPlaybackStarted>((event) {
_handleAudioPlaybackStarted();
})
Expand Down Expand Up @@ -993,12 +1011,30 @@ extension RoomPrivateMethods on Room {
await NativeAudioManagement.stop();

// reset params
_roomInfo = null;
_name = null;
_metadata = null;
_isRecording = false;
_serverVersion = null;
_serverRegion = null;
}

/// Applies room info from server. Skips metadata event on first join
/// since there is no previous state to compare against.
void _applyRoomUpdate(lk_models.Room room) {
final oldRoom = _roomInfo;
_roomInfo = room;
_name = room.name;
_metadata = room.metadata;
if (oldRoom != null && oldRoom.metadata != room.metadata) {
emitWhenConnected(RoomMetadataChangedEvent(metadata: room.metadata));
}
if (oldRoom?.activeRecording != room.activeRecording) {
_isRecording = room.activeRecording;
emitWhenConnected(RoomRecordingStatusChanged(activeRecording: _isRecording));
}
}

@internal
void emitWhenConnected(RoomEvent event) {
if (connectionState == ConnectionState.connected) {
Expand Down
34 changes: 30 additions & 4 deletions lib/src/core/signal_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
int _pingCount = 0;
String? participantSid;

int _requestId = 0;

@internal
int getNextRequestId() {
_requestId += 1;
return _requestId;
}

List<ConnectivityResult> _connectivityResult = [];
StreamSubscription<List<ConnectivityResult>>? _connectivitySubscription;

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

Future<void> sendLeave() async {
_sendRequest(lk_rtc.SignalRequest(
leave: lk_rtc.LeaveRequest(canReconnect: false, reason: lk_models.DisconnectReason.CLIENT_INITIATED)));
leave: lk_rtc.LeaveRequest(
reason: lk_models.DisconnectReason.CLIENT_INITIATED,
// server doesn't process this field, keeping it here to indicate the intent of a full disconnect
action: lk_rtc.LeaveRequest_Action.DISCONNECT,
)));
}

// resets internal state to a re-usable state
Expand Down Expand Up @@ -334,6 +346,17 @@ class SignalClient extends Disposable with EventsEmittable<SignalEvent> {
case lk_rtc.SignalResponse_Message.reconnect:
events.emit(SignalReconnectResponseEvent(response: msg.reconnect));
break;
case lk_rtc.SignalResponse_Message.requestResponse:
logger.fine('received request response: ${msg.requestResponse.reason}');
events.emit(SignalRequestResponseEvent(response: msg.requestResponse));
break;
case lk_rtc.SignalResponse_Message.roomMoved:
logger.fine('received room moved: ${msg.roomMoved.room.name}');
if (msg.roomMoved.token.isNotEmpty) {
events.emit(SignalTokenUpdatedEvent(token: msg.roomMoved.token));
}
events.emit(SignalRoomMovedEvent(response: msg.roomMoved));
break;
default:
logger.warning('received unknown signal message');
}
Expand Down Expand Up @@ -428,9 +451,12 @@ extension SignalClientRequests on SignalClient {
));

@internal
void sendUpdateLocalMetadata(lk_rtc.UpdateParticipantMetadata metadata) => _sendRequest(lk_rtc.SignalRequest(
updateMetadata: metadata,
));
int sendUpdateLocalMetadata(lk_rtc.UpdateParticipantMetadata metadata) {
final requestId = getNextRequestId();
metadata.requestId = requestId;
_sendRequest(lk_rtc.SignalRequest(updateMetadata: metadata));
return requestId;
}

@internal
void sendUpdateTrackSettings(lk_rtc.UpdateTrackSettings settings) => _sendRequest(lk_rtc.SignalRequest(
Expand Down
10 changes: 10 additions & 0 deletions lib/src/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,13 @@ class PreConnectAudioBufferStoppedEvent with RoomEvent {
String toString() => '${runtimeType}'
'(bufferedSize: ${bufferedSize}, isDataSent: ${isBufferSent})';
}

/// Fired when the participant has been moved to a different room by the server.
/// Emitted by [Room].
class RoomMovedEvent with RoomEvent {
final String roomName;
const RoomMovedEvent({required this.roomName});

@override
String toString() => '${runtimeType}(roomName: $roomName)';
}
4 changes: 4 additions & 0 deletions lib/src/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ extension ProtocolVersionExt on ProtocolVersion {
ProtocolVersion.v10: '10',
ProtocolVersion.v11: '11',
ProtocolVersion.v12: '12',
ProtocolVersion.v13: '13',
ProtocolVersion.v14: '14',
ProtocolVersion.v15: '15',
ProtocolVersion.v16: '16',
}[this]!;
}

Expand Down
37 changes: 37 additions & 0 deletions lib/src/internal/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -543,10 +543,47 @@ class SignalTokenUpdatedEvent with SignalEvent, InternalEvent {
String toString() => '${runtimeType}(token: ${token})';
}

@internal
class SignalRequestResponseEvent with SignalEvent, InternalEvent {
final lk_rtc.RequestResponse response;
const SignalRequestResponseEvent({required this.response});

@override
String toString() => '${runtimeType}'
'(requestId: ${response.requestId}, reason: ${response.reason})';
}

@internal
class SignalRoomMovedEvent with SignalEvent, InternalEvent {
final lk_rtc.RoomMovedResponse response;
const SignalRoomMovedEvent({required this.response});

@override
String toString() => '${runtimeType}(room: ${response.room.name})';
}

// ----------------------------------------------------------------------
// Engine events
// ----------------------------------------------------------------------

@internal
class EngineRequestResponseEvent with EngineEvent, InternalEvent {
final lk_rtc.RequestResponse response;
const EngineRequestResponseEvent({required this.response});

@override
String toString() => '${runtimeType}(requestId: ${response.requestId})';
}

@internal
class EngineRoomMovedEvent with EngineEvent, InternalEvent {
final lk_rtc.RoomMovedResponse response;
const EngineRoomMovedEvent({required this.response});

@override
String toString() => '${runtimeType}(room: ${response.room.name})';
}

@internal
class EngineTrackAddedEvent with EngineEvent, InternalEvent {
final rtc.MediaStreamTrack track;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class ConnectOptions {
const ConnectOptions({
this.autoSubscribe = true,
this.rtcConfiguration = const RTCConfiguration(),
this.protocolVersion = ProtocolVersion.v12,
this.protocolVersion = ProtocolVersion.v16,
this.timeouts = Timeouts.defaultTimeouts,
});
}
Expand Down
Loading