diff --git a/lib/src/voip/backend/call_backend_model.dart b/lib/src/voip/backend/call_backend_model.dart index 814d87ce..40607f77 100644 --- a/lib/src/voip/backend/call_backend_model.dart +++ b/lib/src/voip/backend/call_backend_model.dart @@ -73,6 +73,8 @@ abstract class CallBackend { List anyLeft, ); + Future preShareKey(GroupCallSession groupCall); + Future requestEncrytionKey( GroupCallSession groupCall, List remoteParticipants, diff --git a/lib/src/voip/backend/livekit_backend.dart b/lib/src/voip/backend/livekit_backend.dart index 87238c31..13ad6baf 100644 --- a/lib/src/voip/backend/livekit_backend.dart +++ b/lib/src/voip/backend/livekit_backend.dart @@ -52,9 +52,10 @@ class LiveKitBackend extends CallBackend { return newIndex; } + @override Future preShareKey(GroupCallSession groupCall) async { await groupCall.onMemberStateChanged(); - await _makeNewSenderKey(groupCall, false); + await _changeEncryptionKey(groupCall, groupCall.participants, false); } /// makes a new e2ee key for local user and sets it with a delay if specified @@ -120,6 +121,19 @@ class LiveKitBackend extends CallBackend { ); } + Future _changeEncryptionKey( + GroupCallSession groupCall, + List anyJoined, + bool delayBeforeUsingKeyOurself, + ) async { + if (!e2eeEnabled) return; + if (groupCall.voip.enableSFUE2EEKeyRatcheting) { + await _ratchetLocalParticipantKey(groupCall, anyJoined); + } else { + await _makeNewSenderKey(groupCall, delayBeforeUsingKeyOurself); + } + } + /// sets incoming keys and also sends the key if it was for the local user /// if sendTo is null, its sent to all _participants, see `_sendEncryptionKeysEvent` Future _setEncryptionKey( @@ -180,8 +194,6 @@ class LiveKitBackend extends CallBackend { int keyIndex, { List? sendTo, }) async { - Logs().i('Sending encryption keys event'); - final myKeys = _getKeysForParticipant(groupCall.localParticipant!); final myLatestKey = myKeys?[keyIndex]; @@ -235,6 +247,7 @@ class LiveKitBackend extends CallBackend { Map data, String eventType, ) async { + if (remoteParticipants.isEmpty) return; Logs().v( '[VOIP] _sendToDeviceEvent: sending ${data.toString()} to ${remoteParticipants.map((e) => e.id)} '); final txid = @@ -392,14 +405,8 @@ class LiveKitBackend extends CallBackend { Future onNewParticipant( GroupCallSession groupCall, List anyJoined, - ) async { - if (!e2eeEnabled) return; - if (groupCall.voip.enableSFUE2EEKeyRatcheting) { - await _ratchetLocalParticipantKey(groupCall, anyJoined); - } else { - await _makeNewSenderKey(groupCall, true); - } - } + ) => + _changeEncryptionKey(groupCall, anyJoined, true); @override Future onLeftParticipant( diff --git a/lib/src/voip/backend/mesh_backend.dart b/lib/src/voip/backend/mesh_backend.dart index 4a0cb47c..150cba6a 100644 --- a/lib/src/voip/backend/mesh_backend.dart +++ b/lib/src/voip/backend/mesh_backend.dart @@ -877,4 +877,9 @@ class MeshBackend extends CallBackend { List remoteParticipants) async { return; } + + @override + Future preShareKey(GroupCallSession groupCall) async { + return; + } } diff --git a/lib/src/voip/group_call_session.dart b/lib/src/voip/group_call_session.dart index 71eccf32..77e5bad7 100644 --- a/lib/src/voip/group_call_session.dart +++ b/lib/src/voip/group_call_session.dart @@ -48,7 +48,7 @@ class GroupCallSession { CallParticipant? get localParticipant => voip.localParticipant; List get participants => List.unmodifiable(_participants); - final List _participants = []; + final Set _participants = {}; String groupCallId; @@ -218,7 +218,7 @@ class GroupCallSession { '[VOIP] Ignored ${mem.userId}\'s mem event ${mem.toJson()} while updating _participants list for callId: $groupCallId, expiry status: ${mem.isExpired}'); } - final List newP = []; + final Set newP = {}; for (final mem in memsForCurrentGroupCall) { final rp = CallParticipant( @@ -239,23 +239,29 @@ class GroupCallSession { await backend.setupP2PCallWithNewMember(this, rp, mem); } - final newPcopy = List.from(newP); - final oldPcopy = List.from(_participants); - final anyJoined = newPcopy.where((element) => !oldPcopy.contains(element)); - final anyLeft = oldPcopy.where((element) => !newPcopy.contains(element)); + final newPcopy = Set.from(newP); + final oldPcopy = Set.from(_participants); + final anyJoined = newPcopy.difference(oldPcopy); + final anyLeft = oldPcopy.difference(newPcopy); if (anyJoined.isNotEmpty || anyLeft.isNotEmpty) { if (anyJoined.isNotEmpty) { - Logs().d('anyJoined: ${anyJoined.map((e) => e.id).toString()}'); + final nonLocalAnyJoined = anyJoined..remove(localParticipant); + if (nonLocalAnyJoined.isNotEmpty && state == GroupCallState.entered) { + Logs().v( + 'nonLocalAnyJoined: ${nonLocalAnyJoined.map((e) => e.id).toString()} roomId: ${room.id} groupCallId: $groupCallId'); + await backend.onNewParticipant(this, nonLocalAnyJoined.toList()); + } _participants.addAll(anyJoined); - await backend.onNewParticipant(this, anyJoined.toList()); } if (anyLeft.isNotEmpty) { - Logs().d('anyLeft: ${anyLeft.map((e) => e.id).toString()}'); - for (final leftp in anyLeft) { - _participants.remove(leftp); + final nonLocalAnyLeft = anyLeft..remove(localParticipant); + if (nonLocalAnyLeft.isNotEmpty && state == GroupCallState.entered) { + Logs().v( + 'nonLocalAnyLeft: ${nonLocalAnyLeft.map((e) => e.id).toString()} roomId: ${room.id} groupCallId: $groupCallId'); + await backend.onLeftParticipant(this, nonLocalAnyLeft.toList()); } - await backend.onLeftParticipant(this, anyLeft.toList()); + _participants.removeAll(anyLeft); } onGroupCallEvent.add(GroupCallStateChange.participantsChanged); diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index 1b78e3d4..7a752c04 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -756,37 +756,44 @@ class VoIP { /// [application] normal group call, thrirdroom, etc /// /// [scope] room, between specifc users, etc. + /// + /// [preShareKey] for livekit calls it creates and shares a key with other + /// participants in the call without entering, useful on onboarding screens. + /// does not do anything in mesh calls Future fetchOrCreateGroupCall( String groupCallId, Room room, CallBackend backend, String? application, - String? scope, - ) async { + String? scope, { + bool preShareKey = true, + }) async { if (!room.groupCallsEnabledForEveryone) { await room.enableGroupCalls(); } - final groupCall = getGroupCallById(room.id, groupCallId); - - if (groupCall != null) { - if (!room.canJoinGroupCall) { - throw MatrixSDKVoipException( - 'User ${client.userID}:${client.deviceID} is not allowed to join famedly calls in room ${room.id}, canJoinGroupCall: ${room.canJoinGroupCall}, room.canJoinGroupCall: ${room.groupCallsEnabledForEveryone}', - ); - } - return groupCall; + if (!room.canJoinGroupCall) { + throw MatrixSDKVoipException( + 'User ${client.userID}:${client.deviceID} is not allowed to join famedly calls in room ${room.id}, canJoinGroupCall: ${room.canJoinGroupCall}, room.canJoinGroupCall: ${room.groupCallsEnabledForEveryone}', + ); } - // The call doesn't exist, but we can create it - return await _newGroupCall( + GroupCallSession? groupCall = getGroupCallById(room.id, groupCallId); + + groupCall ??= await _newGroupCall( groupCallId, room, backend, application, scope, ); + + if (preShareKey) { + await groupCall.backend.preShareKey(groupCall); + } + + return groupCall; } GroupCallSession? getGroupCallById(String roomId, String groupCallId) {