LCOV - code coverage report
Current view: top level - lib/src/voip/backend - livekit_backend.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 0.0 % 212 0
Test Date: 2025-10-13 02:23:18 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:async';
       2              : import 'dart:convert';
       3              : import 'dart:typed_data';
       4              : 
       5              : import 'package:matrix/matrix.dart';
       6              : import 'package:matrix/src/utils/crypto/crypto.dart';
       7              : 
       8              : class LiveKitBackend extends CallBackend {
       9              :   final String livekitServiceUrl;
      10              :   final String livekitAlias;
      11              : 
      12              :   @override
      13              :   final bool e2eeEnabled;
      14              : 
      15            0 :   LiveKitBackend({
      16              :     required this.livekitServiceUrl,
      17              :     required this.livekitAlias,
      18              :     super.type = 'livekit',
      19              :     this.e2eeEnabled = true,
      20              :   });
      21              : 
      22              :   Timer? _memberLeaveEncKeyRotateDebounceTimer;
      23              : 
      24              :   /// participant:keyIndex:keyBin
      25              :   final Map<CallParticipant, Map<int, Uint8List>> _encryptionKeysMap = {};
      26              : 
      27              :   final List<Future> _setNewKeyTimeouts = [];
      28              : 
      29              :   int _indexCounter = 0;
      30              : 
      31              :   /// used to send the key again incase someone `onCallEncryptionKeyRequest` but don't just send
      32              :   /// the last one because you also cycle back in your window which means you
      33              :   /// could potentially end up sharing a past key
      34              :   /// we don't really care about what if setting or sending fails right now
      35            0 :   int get latestLocalKeyIndex => _latestLocalKeyIndex;
      36              :   int _latestLocalKeyIndex = 0;
      37              : 
      38              :   /// stores when the last new key was made (makeNewSenderKey), is not used
      39              :   /// for ratcheted keys at the moment
      40              :   DateTime _lastNewKeyTime = DateTime(1900);
      41              : 
      42              :   /// the key currently being used by the local cryptor, can possibly not be the latest
      43              :   /// key, check `latestLocalKeyIndex` for latest key
      44            0 :   int get currentLocalKeyIndex => _currentLocalKeyIndex;
      45              :   int _currentLocalKeyIndex = 0;
      46              : 
      47            0 :   Map<int, Uint8List>? _getKeysForParticipant(CallParticipant participant) {
      48            0 :     return _encryptionKeysMap[participant];
      49              :   }
      50              : 
      51              :   /// always chooses the next possible index, we cycle after 16 because
      52              :   /// no real adv with infinite list
      53            0 :   int _getNewEncryptionKeyIndex(int keyRingSize) {
      54            0 :     final newIndex = _indexCounter % keyRingSize;
      55            0 :     _indexCounter++;
      56              :     return newIndex;
      57              :   }
      58              : 
      59            0 :   @override
      60              :   Future<void> preShareKey(GroupCallSession groupCall) async {
      61            0 :     await groupCall.onMemberStateChanged();
      62            0 :     await _changeEncryptionKey(groupCall, groupCall.participants, false);
      63              :   }
      64              : 
      65              :   /// makes a new e2ee key for local user and sets it with a delay if specified
      66              :   /// used on first join and when someone leaves
      67              :   ///
      68              :   /// also does the sending for you
      69            0 :   Future<void> _makeNewSenderKey(
      70              :     GroupCallSession groupCall,
      71              :     bool delayBeforeUsingKeyOurself, {
      72              :     bool skipJoinDebounce = false,
      73              :   }) async {
      74            0 :     if (_lastNewKeyTime
      75            0 :             .add(groupCall.voip.timeouts!.makeKeyOnJoinDelay)
      76            0 :             .isAfter(DateTime.now()) &&
      77              :         !skipJoinDebounce) {
      78            0 :       Logs().d(
      79            0 :         '_makeNewSenderKey using previous key because last created at ${_lastNewKeyTime.toString()}',
      80              :       );
      81              :       // still a fairly new key, just send that
      82            0 :       await _sendEncryptionKeysEvent(
      83              :         groupCall,
      84            0 :         _latestLocalKeyIndex,
      85              :       );
      86              :       return;
      87              :     }
      88              : 
      89            0 :     final key = secureRandomBytes(32);
      90            0 :     final keyIndex = _getNewEncryptionKeyIndex(groupCall.voip.keyRingSize);
      91            0 :     Logs().i('[VOIP E2EE] Generated new key $key at index $keyIndex');
      92              : 
      93            0 :     await _setEncryptionKey(
      94              :       groupCall,
      95            0 :       groupCall.localParticipant!,
      96              :       keyIndex,
      97              :       key,
      98              :       delayBeforeUsingKeyOurself: delayBeforeUsingKeyOurself,
      99              :       send: true,
     100              :     );
     101              :   }
     102              : 
     103              :   /// also does the sending for you
     104            0 :   Future<void> _ratchetLocalParticipantKey(
     105              :     GroupCallSession groupCall,
     106              :     List<CallParticipant> sendTo,
     107              : 
     108              :     /// only used for makeSenderKey fallback
     109              :     bool delayBeforeUsingKeyOurself,
     110              :   ) async {
     111            0 :     final keyProvider = groupCall.voip.delegate.keyProvider;
     112              : 
     113              :     if (keyProvider == null) {
     114            0 :       throw MatrixSDKVoipException(
     115              :         '_ratchetKey called but KeyProvider was null',
     116              :       );
     117              :     }
     118              : 
     119            0 :     final myKeys = _encryptionKeysMap[groupCall.localParticipant];
     120              : 
     121            0 :     if (myKeys == null || myKeys.isEmpty) {
     122            0 :       await _makeNewSenderKey(groupCall, false);
     123              :       return;
     124              :     }
     125              : 
     126              :     Uint8List? ratchetedKey;
     127              : 
     128              :     int ratchetTryCounter = 0;
     129              : 
     130            0 :     while (ratchetTryCounter <= 8 &&
     131            0 :         (ratchetedKey == null || ratchetedKey.isEmpty)) {
     132            0 :       Logs().d(
     133            0 :         '[VOIP E2EE] Ignoring empty ratcheted key, ratchetTryCounter: $ratchetTryCounter',
     134              :       );
     135              : 
     136            0 :       ratchetedKey = await keyProvider.onRatchetKey(
     137            0 :         groupCall.localParticipant!,
     138            0 :         latestLocalKeyIndex,
     139              :       );
     140            0 :       ratchetTryCounter++;
     141              :     }
     142              : 
     143            0 :     if (ratchetedKey == null || ratchetedKey.isEmpty) {
     144            0 :       Logs().d(
     145              :         '[VOIP E2EE] ratcheting failed, falling back to creating a new key',
     146              :       );
     147            0 :       await _makeNewSenderKey(groupCall, delayBeforeUsingKeyOurself);
     148              :       return;
     149              :     }
     150              : 
     151            0 :     await _setEncryptionKey(
     152              :       groupCall,
     153            0 :       groupCall.localParticipant!,
     154            0 :       latestLocalKeyIndex,
     155              :       ratchetedKey,
     156              :       delayBeforeUsingKeyOurself: false,
     157              :       send: true,
     158              :       setKey: false,
     159              :       sendTo: sendTo,
     160              :     );
     161              :   }
     162              : 
     163            0 :   Future<void> _changeEncryptionKey(
     164              :     GroupCallSession groupCall,
     165              :     List<CallParticipant> anyJoined,
     166              :     bool delayBeforeUsingKeyOurself,
     167              :   ) async {
     168            0 :     if (!e2eeEnabled) return;
     169            0 :     if (groupCall.voip.enableSFUE2EEKeyRatcheting) {
     170            0 :       await _ratchetLocalParticipantKey(
     171              :         groupCall,
     172              :         anyJoined,
     173              :         delayBeforeUsingKeyOurself,
     174              :       );
     175              :     } else {
     176            0 :       await _makeNewSenderKey(groupCall, delayBeforeUsingKeyOurself);
     177              :     }
     178              :   }
     179              : 
     180              :   /// sets incoming keys and also sends the key if it was for the local user
     181              :   /// if sendTo is null, its sent to all _participants, see `_sendEncryptionKeysEvent`
     182            0 :   Future<void> _setEncryptionKey(
     183              :     GroupCallSession groupCall,
     184              :     CallParticipant participant,
     185              :     int encryptionKeyIndex,
     186              :     Uint8List encryptionKeyBin, {
     187              :     bool delayBeforeUsingKeyOurself = false,
     188              :     bool send = false,
     189              : 
     190              :     /// ratchet seems to set on call, so no need to set manually
     191              :     bool setKey = true,
     192              :     List<CallParticipant>? sendTo,
     193              :   }) async {
     194              :     final encryptionKeys =
     195            0 :         _encryptionKeysMap[participant] ?? <int, Uint8List>{};
     196              : 
     197            0 :     encryptionKeys[encryptionKeyIndex] = encryptionKeyBin;
     198            0 :     _encryptionKeysMap[participant] = encryptionKeys;
     199            0 :     if (participant.isLocal) {
     200            0 :       _latestLocalKeyIndex = encryptionKeyIndex;
     201            0 :       _lastNewKeyTime = DateTime.now();
     202              :     }
     203              : 
     204              :     if (send) {
     205            0 :       await _sendEncryptionKeysEvent(
     206              :         groupCall,
     207              :         encryptionKeyIndex,
     208              :         sendTo: sendTo,
     209              :       );
     210              :     }
     211              : 
     212              :     if (!setKey) {
     213            0 :       Logs().i(
     214            0 :         '[VOIP E2EE] sent ratchetd key $encryptionKeyBin but not setting',
     215              :       );
     216              :       return;
     217              :     }
     218              : 
     219              :     if (delayBeforeUsingKeyOurself) {
     220            0 :       Logs().d(
     221            0 :         '[VOIP E2EE] starting delayed set for ${participant.id} idx $encryptionKeyIndex key $encryptionKeyBin, current idx $currentLocalKeyIndex key ${encryptionKeys[currentLocalKeyIndex]}',
     222              :       );
     223              :       // now wait for the key to propogate and then set it, hopefully users can
     224              :       // stil decrypt everything
     225              :       final useKeyTimeout =
     226            0 :           Future.delayed(groupCall.voip.timeouts!.useKeyDelay, () async {
     227            0 :         Logs().i(
     228            0 :           '[VOIP E2EE] delayed setting key changed event for ${participant.id} idx $encryptionKeyIndex key $encryptionKeyBin',
     229              :         );
     230            0 :         await groupCall.voip.delegate.keyProvider?.onSetEncryptionKey(
     231              :           participant,
     232              :           encryptionKeyBin,
     233              :           encryptionKeyIndex,
     234              :         );
     235            0 :         if (participant.isLocal) {
     236            0 :           _currentLocalKeyIndex = encryptionKeyIndex;
     237              :         }
     238              :       });
     239            0 :       _setNewKeyTimeouts.add(useKeyTimeout);
     240              :     } else {
     241            0 :       Logs().i(
     242            0 :         '[VOIP E2EE] setting key changed event for ${participant.id} idx $encryptionKeyIndex key $encryptionKeyBin',
     243              :       );
     244            0 :       await groupCall.voip.delegate.keyProvider?.onSetEncryptionKey(
     245              :         participant,
     246              :         encryptionKeyBin,
     247              :         encryptionKeyIndex,
     248              :       );
     249            0 :       if (participant.isLocal) {
     250            0 :         _currentLocalKeyIndex = encryptionKeyIndex;
     251              :       }
     252              :     }
     253              :   }
     254              : 
     255              :   /// sends the enc key to the devices using todevice, passing a list of
     256              :   /// sendTo only sends events to them
     257              :   /// setting keyIndex to null will send the latestKey
     258            0 :   Future<void> _sendEncryptionKeysEvent(
     259              :     GroupCallSession groupCall,
     260              :     int keyIndex, {
     261              :     List<CallParticipant>? sendTo,
     262              :   }) async {
     263            0 :     final myKeys = _getKeysForParticipant(groupCall.localParticipant!);
     264            0 :     final myLatestKey = myKeys?[keyIndex];
     265              : 
     266              :     final sendKeysTo =
     267            0 :         sendTo ?? groupCall.participants.where((p) => !p.isLocal);
     268              : 
     269              :     if (myKeys == null || myLatestKey == null) {
     270            0 :       Logs().w(
     271              :         '[VOIP E2EE] _sendEncryptionKeysEvent Tried to send encryption keys event but no keys found!',
     272              :       );
     273            0 :       await _makeNewSenderKey(groupCall, false);
     274            0 :       await _sendEncryptionKeysEvent(
     275              :         groupCall,
     276              :         keyIndex,
     277              :         sendTo: sendTo,
     278              :       );
     279              :       return;
     280              :     }
     281              : 
     282              :     try {
     283            0 :       final keyContent = EncryptionKeysEventContent(
     284            0 :         [EncryptionKeyEntry(keyIndex, base64Encode(myLatestKey))],
     285            0 :         groupCall.groupCallId,
     286              :       );
     287            0 :       final Map<String, Object> data = {
     288            0 :         ...keyContent.toJson(),
     289              :         // used to find group call in groupCalls when ToDeviceEvent happens,
     290              :         // plays nicely with backwards compatibility for mesh calls
     291            0 :         'conf_id': groupCall.groupCallId,
     292            0 :         'device_id': groupCall.client.deviceID!,
     293            0 :         'room_id': groupCall.room.id,
     294              :       };
     295            0 :       await _sendToDeviceEvent(
     296              :         groupCall,
     297            0 :         sendTo ?? sendKeysTo.toList(),
     298              :         data,
     299              :         EventTypes.GroupCallMemberEncryptionKeys,
     300              :       );
     301              :     } catch (e, s) {
     302            0 :       Logs().e('[VOIP E2EE] Failed to send e2ee keys, retrying', e, s);
     303            0 :       await _sendEncryptionKeysEvent(
     304              :         groupCall,
     305              :         keyIndex,
     306              :         sendTo: sendTo,
     307              :       );
     308              :     }
     309              :   }
     310              : 
     311            0 :   Future<void> _sendToDeviceEvent(
     312              :     GroupCallSession groupCall,
     313              :     List<CallParticipant> remoteParticipants,
     314              :     Map<String, Object> data,
     315              :     String eventType,
     316              :   ) async {
     317            0 :     if (remoteParticipants.isEmpty) return;
     318            0 :     Logs().v(
     319            0 :       '[VOIP E2EE] _sendToDeviceEvent: sending ${data.toString()} to ${remoteParticipants.map((e) => e.id)} ',
     320              :     );
     321              :     final txid =
     322            0 :         VoIP.customTxid ?? groupCall.client.generateUniqueTransactionId();
     323              :     final mustEncrypt =
     324            0 :         groupCall.room.encrypted && groupCall.client.encryptionEnabled;
     325              : 
     326              :     // could just combine the two but do not want to rewrite the enc thingy
     327              :     // wrappers here again.
     328            0 :     final List<DeviceKeys> mustEncryptkeysToSendTo = [];
     329              :     final Map<String, Map<String, Map<String, Object>>> unencryptedDataToSend =
     330            0 :         {};
     331              : 
     332            0 :     for (final participant in remoteParticipants) {
     333            0 :       if (participant.deviceId == null) continue;
     334              :       if (mustEncrypt) {
     335            0 :         await groupCall.client.userDeviceKeysLoading;
     336            0 :         final deviceKey = groupCall.client.userDeviceKeys[participant.userId]
     337            0 :             ?.deviceKeys[participant.deviceId];
     338              :         if (deviceKey != null) {
     339            0 :           mustEncryptkeysToSendTo.add(deviceKey);
     340              :         }
     341              :       } else {
     342            0 :         unencryptedDataToSend.addAll({
     343            0 :           participant.userId: {participant.deviceId!: data},
     344              :         });
     345              :       }
     346              :     }
     347              : 
     348              :     // prepped data, now we send
     349              :     if (mustEncrypt) {
     350            0 :       await groupCall.client.sendToDeviceEncrypted(
     351              :         mustEncryptkeysToSendTo,
     352              :         eventType,
     353              :         data,
     354              :       );
     355              :     } else {
     356            0 :       await groupCall.client.sendToDevice(
     357              :         eventType,
     358              :         txid,
     359              :         unencryptedDataToSend,
     360              :       );
     361              :     }
     362              :   }
     363              : 
     364            0 :   @override
     365              :   Map<String, Object?> toJson() {
     366            0 :     return {
     367            0 :       'type': type,
     368            0 :       'livekit_service_url': livekitServiceUrl,
     369            0 :       'livekit_alias': livekitAlias,
     370              :     };
     371              :   }
     372              : 
     373            0 :   @override
     374              :   Future<void> requestEncrytionKey(
     375              :     GroupCallSession groupCall,
     376              :     List<CallParticipant> remoteParticipants,
     377              :   ) async {
     378            0 :     final Map<String, Object> data = {
     379            0 :       'conf_id': groupCall.groupCallId,
     380            0 :       'device_id': groupCall.client.deviceID!,
     381            0 :       'room_id': groupCall.room.id,
     382              :     };
     383              : 
     384            0 :     await _sendToDeviceEvent(
     385              :       groupCall,
     386              :       remoteParticipants,
     387              :       data,
     388              :       EventTypes.GroupCallMemberEncryptionKeysRequest,
     389              :     );
     390              :   }
     391              : 
     392            0 :   @override
     393              :   Future<void> onCallEncryption(
     394              :     GroupCallSession groupCall,
     395              :     String userId,
     396              :     String deviceId,
     397              :     Map<String, dynamic> content,
     398              :   ) async {
     399            0 :     if (!e2eeEnabled) {
     400            0 :       Logs().w('[VOIP E2EE] got sframe key but we do not support e2ee');
     401              :       return;
     402              :     }
     403            0 :     final keyContent = EncryptionKeysEventContent.fromJson(content);
     404              : 
     405            0 :     final callId = keyContent.callId;
     406              :     final p =
     407            0 :         CallParticipant(groupCall.voip, userId: userId, deviceId: deviceId);
     408              : 
     409            0 :     if (keyContent.keys.isEmpty) {
     410            0 :       Logs().w(
     411            0 :         '[VOIP E2EE] Received m.call.encryption_keys where keys is empty: callId=$callId',
     412              :       );
     413              :       return;
     414              :     } else {
     415            0 :       Logs().i(
     416            0 :         '[VOIP E2EE]: onCallEncryption, got keys from ${p.id} ${keyContent.toJson()}',
     417              :       );
     418              :     }
     419              : 
     420            0 :     for (final key in keyContent.keys) {
     421            0 :       final encryptionKey = key.key;
     422            0 :       final encryptionKeyIndex = key.index;
     423            0 :       await _setEncryptionKey(
     424              :         groupCall,
     425              :         p,
     426              :         encryptionKeyIndex,
     427              :         // base64Decode here because we receive base64Encoded version
     428            0 :         base64Decode(encryptionKey),
     429              :         delayBeforeUsingKeyOurself: false,
     430              :         send: false,
     431              :       );
     432              :     }
     433              :   }
     434              : 
     435            0 :   @override
     436              :   Future<void> onCallEncryptionKeyRequest(
     437              :     GroupCallSession groupCall,
     438              :     String userId,
     439              :     String deviceId,
     440              :     Map<String, dynamic> content,
     441              :   ) async {
     442            0 :     if (!e2eeEnabled) {
     443            0 :       Logs().w('[VOIP E2EE] got sframe key request but we do not support e2ee');
     444              :       return;
     445              :     }
     446              : 
     447            0 :     Future<bool> checkPartcipantStatusAndRequestKey() async {
     448            0 :       final mems = groupCall.room.getCallMembershipsForUser(
     449              :         userId,
     450              :         deviceId,
     451            0 :         groupCall.voip,
     452              :       );
     453              : 
     454              :       if (mems
     455            0 :           .where(
     456            0 :             (mem) =>
     457            0 :                 mem.callId == groupCall.groupCallId &&
     458            0 :                 mem.userId == userId &&
     459            0 :                 mem.deviceId == deviceId &&
     460            0 :                 !mem.isExpired &&
     461              :                 // sanity checks
     462            0 :                 mem.backend.type == groupCall.backend.type &&
     463            0 :                 mem.roomId == groupCall.room.id &&
     464            0 :                 mem.application == groupCall.application,
     465              :           )
     466            0 :           .isNotEmpty) {
     467            0 :         Logs().d(
     468            0 :           '[VOIP E2EE] onCallEncryptionKeyRequest: request checks out, sending key on index: $latestLocalKeyIndex to $userId:$deviceId',
     469              :         );
     470            0 :         await _sendEncryptionKeysEvent(
     471              :           groupCall,
     472            0 :           _latestLocalKeyIndex,
     473            0 :           sendTo: [
     474            0 :             CallParticipant(
     475            0 :               groupCall.voip,
     476              :               userId: userId,
     477              :               deviceId: deviceId,
     478              :             ),
     479              :           ],
     480              :         );
     481              :         return true;
     482              :       } else {
     483              :         return false;
     484              :       }
     485              :     }
     486              : 
     487            0 :     if ((!await checkPartcipantStatusAndRequestKey())) {
     488            0 :       Logs().i(
     489              :         '[VOIP E2EE] onCallEncryptionKeyRequest: checkPartcipantStatusAndRequestKey returned false, therefore retrying by getting state from server and rebuilding participant list for sanity',
     490              :       );
     491              :       final stateKey =
     492            0 :           (groupCall.room.roomVersion?.contains('msc3757') ?? false)
     493              :               ? '${userId}_$deviceId'
     494            0 :               : userId;
     495            0 :       await groupCall.room.client.getRoomStateWithKey(
     496            0 :         groupCall.room.id,
     497              :         EventTypes.GroupCallMember,
     498              :         stateKey,
     499              :       );
     500            0 :       await groupCall.onMemberStateChanged();
     501            0 :       await checkPartcipantStatusAndRequestKey();
     502              :     }
     503              :   }
     504              : 
     505            0 :   @override
     506              :   Future<void> onNewParticipant(
     507              :     GroupCallSession groupCall,
     508              :     List<CallParticipant> anyJoined,
     509              :   ) =>
     510            0 :       _changeEncryptionKey(groupCall, anyJoined, true);
     511              : 
     512            0 :   @override
     513              :   Future<void> onLeftParticipant(
     514              :     GroupCallSession groupCall,
     515              :     List<CallParticipant> anyLeft,
     516              :   ) async {
     517            0 :     _encryptionKeysMap.removeWhere((key, value) => anyLeft.contains(key));
     518              : 
     519              :     // debounce it because people leave at the same time
     520            0 :     if (_memberLeaveEncKeyRotateDebounceTimer != null) {
     521            0 :       _memberLeaveEncKeyRotateDebounceTimer!.cancel();
     522              :     }
     523            0 :     _memberLeaveEncKeyRotateDebounceTimer =
     524            0 :         Timer(groupCall.voip.timeouts!.makeKeyOnLeaveDelay, () async {
     525              :       // we skipJoinDebounce here because we want to make sure a new key is generated
     526              :       // and that the join debounce does not block us from making a new key
     527            0 :       await _makeNewSenderKey(
     528              :         groupCall,
     529              :         true,
     530              :         skipJoinDebounce: true,
     531              :       );
     532              :     });
     533              :   }
     534              : 
     535            0 :   @override
     536              :   Future<void> dispose(GroupCallSession groupCall) async {
     537              :     // only remove our own, to save requesting if we join again, yes the other side
     538              :     // will send it anyway but welp
     539            0 :     _encryptionKeysMap.remove(groupCall.localParticipant!);
     540            0 :     _currentLocalKeyIndex = 0;
     541            0 :     _latestLocalKeyIndex = 0;
     542            0 :     _memberLeaveEncKeyRotateDebounceTimer?.cancel();
     543              :   }
     544              : 
     545            0 :   @override
     546              :   List<Map<String, String>>? getCurrentFeeds() {
     547              :     return null;
     548              :   }
     549              : 
     550            0 :   @override
     551              :   bool operator ==(Object other) =>
     552              :       identical(this, other) ||
     553            0 :       (other is LiveKitBackend &&
     554            0 :           type == other.type &&
     555            0 :           livekitServiceUrl == other.livekitServiceUrl &&
     556            0 :           livekitAlias == other.livekitAlias);
     557              : 
     558            0 :   @override
     559            0 :   int get hashCode => Object.hash(
     560            0 :         type.hashCode,
     561            0 :         livekitServiceUrl.hashCode,
     562            0 :         livekitAlias.hashCode,
     563              :       );
     564              : 
     565              :   /// get everything else from your livekit sdk in your client
     566            0 :   @override
     567              :   Future<WrappedMediaStream?> initLocalStream(
     568              :     GroupCallSession groupCall, {
     569              :     WrappedMediaStream? stream,
     570              :   }) async {
     571              :     return null;
     572              :   }
     573              : 
     574            0 :   @override
     575              :   CallParticipant? get activeSpeaker => null;
     576              : 
     577              :   /// these are unimplemented on purpose so that you know you have
     578              :   /// used the wrong method
     579            0 :   @override
     580              :   bool get isLocalVideoMuted =>
     581            0 :       throw UnimplementedError('Use livekit sdk for this');
     582              : 
     583            0 :   @override
     584              :   bool get isMicrophoneMuted =>
     585            0 :       throw UnimplementedError('Use livekit sdk for this');
     586              : 
     587            0 :   @override
     588              :   WrappedMediaStream? get localScreenshareStream =>
     589            0 :       throw UnimplementedError('Use livekit sdk for this');
     590              : 
     591            0 :   @override
     592              :   WrappedMediaStream? get localUserMediaStream =>
     593            0 :       throw UnimplementedError('Use livekit sdk for this');
     594              : 
     595            0 :   @override
     596              :   List<WrappedMediaStream> get screenShareStreams =>
     597            0 :       throw UnimplementedError('Use livekit sdk for this');
     598              : 
     599            0 :   @override
     600              :   List<WrappedMediaStream> get userMediaStreams =>
     601            0 :       throw UnimplementedError('Use livekit sdk for this');
     602              : 
     603            0 :   @override
     604              :   Future<void> setDeviceMuted(
     605              :     GroupCallSession groupCall,
     606              :     bool muted,
     607              :     MediaInputKind kind,
     608              :   ) async {
     609              :     return;
     610              :   }
     611              : 
     612            0 :   @override
     613              :   Future<void> setScreensharingEnabled(
     614              :     GroupCallSession groupCall,
     615              :     bool enabled,
     616              :     String desktopCapturerSourceId,
     617              :   ) async {
     618              :     return;
     619              :   }
     620              : 
     621            0 :   @override
     622              :   Future<void> setupP2PCallWithNewMember(
     623              :     GroupCallSession groupCall,
     624              :     CallParticipant rp,
     625              :     CallMembership mem,
     626              :   ) async {
     627              :     return;
     628              :   }
     629              : 
     630            0 :   @override
     631              :   Future<void> setupP2PCallsWithExistingMembers(
     632              :     GroupCallSession groupCall,
     633              :   ) async {
     634              :     return;
     635              :   }
     636              : 
     637            0 :   @override
     638              :   Future<void> updateMediaDeviceForCalls() async {
     639              :     return;
     640              :   }
     641              : }
        

Generated by: LCOV version 2.0-1