onCallInvite method

Future<void> onCallInvite(
  1. Room room,
  2. String remoteUserId,
  3. String? remoteDeviceId,
  4. Map<String, dynamic> content,
)

Implementation

Future<void> onCallInvite(Room room, String remoteUserId,
    String? remoteDeviceId, Map<String, dynamic> content) async {
  Logs().v(
      '[VOIP] onCallInvite $remoteUserId:$remoteDeviceId => ${client.userID}:${client.deviceID}, \ncontent => ${content.toString()}');

  final String callId = content['call_id'];
  final int lifetime = content['lifetime'];
  final String? confId = content['conf_id'];

  final call = calls[VoipId(roomId: room.id, callId: callId)];

  Logs().d(
      '[glare] got new call ${content.tryGet('call_id')} and currently room id is mapped to ${incomingCallRoomId.tryGet(room.id)}');

  if (call != null && call.state == CallState.kEnded) {
    // Session already exist.
    Logs().v('[VOIP] onCallInvite: Session [$callId] already exist.');
    return;
  }

  final inviteeUserId = content['invitee'];
  if (inviteeUserId != null && inviteeUserId != localParticipant?.userId) {
    Logs().w('[VOIP] Ignoring call, meant for user $inviteeUserId');
    return; // This invite was meant for another user in the room
  }
  final inviteeDeviceId = content['invitee_device_id'];
  if (inviteeDeviceId != null &&
      inviteeDeviceId != localParticipant?.deviceId) {
    Logs().w('[VOIP] Ignoring call, meant for device $inviteeDeviceId');
    return; // This invite was meant for another device in the room
  }

  if (content['capabilities'] != null) {
    final capabilities = CallCapabilities.fromJson(content['capabilities']);
    Logs().v(
        '[VOIP] CallCapabilities: dtmf => ${capabilities.dtmf}, transferee => ${capabilities.transferee}');
  }

  var callType = CallType.kVoice;
  SDPStreamMetadata? sdpStreamMetadata;
  if (content[sdpStreamMetadataKey] != null) {
    sdpStreamMetadata =
        SDPStreamMetadata.fromJson(content[sdpStreamMetadataKey]);
    sdpStreamMetadata.sdpStreamMetadatas
        .forEach((streamId, SDPStreamPurpose purpose) {
      Logs().v(
          '[VOIP] [$streamId] => purpose: ${purpose.purpose}, audioMuted: ${purpose.audio_muted}, videoMuted:  ${purpose.video_muted}');

      if (!purpose.video_muted) {
        callType = CallType.kVideo;
      }
    });
  } else {
    callType = getCallType(content['offer']['sdp']);
  }

  final opts = CallOptions(
    voip: this,
    callId: callId,
    groupCallId: confId,
    dir: CallDirection.kIncoming,
    type: callType,
    room: room,
    localPartyId: localPartyId,
    iceServers: await getIceServers(),
  );

  final newCall = createNewCall(opts);

  /// both invitee userId and deviceId are set here because there can be
  /// multiple devices from same user in a call, so we specifiy who the
  /// invite is for
  newCall.remoteUserId = remoteUserId;
  newCall.remoteDeviceId = remoteDeviceId;
  newCall.remotePartyId = content['party_id'];
  newCall.remoteSessionId = content['sender_session_id'];

  // newCall.remoteSessionId = remoteParticipant.sessionId;

  if (!delegate.canHandleNewCall &&
      (confId == null ||
          currentGroupCID != VoipId(roomId: room.id, callId: confId))) {
    Logs().v(
        '[VOIP] onCallInvite: Unable to handle new calls, maybe user is busy.');
    // no need to emit here because handleNewCall was never triggered yet
    await newCall.reject(reason: CallErrorCode.userBusy, shouldEmit: false);
    await delegate.handleMissedCall(newCall);
    return;
  }

  final offer = RTCSessionDescription(
    content['offer']['sdp'],
    content['offer']['type'],
  );

  /// play ringtone. We decided to play the ringtone before adding the call to
  /// the incoming call stream because getUserMedia from initWithInvite fails
  /// on firefox unless the tab is in focus. We should atleast be able to notify
  /// the user about an incoming call
  ///
  /// Autoplay on firefox still needs interaction, without which all notifications
  /// could be blocked.
  if (confId == null) {
    await delegate.playRingtone();
  }

  // When getUserMedia throws an exception, we handle it by terminating the call,
  // and all this happens inside initWithInvite. If we set currentCID after
  // initWithInvite, we might set it to callId even after it was reset to null
  // by terminate.
  currentCID = VoipId(roomId: room.id, callId: callId);

  await newCall.initWithInvite(
      callType, offer, sdpStreamMetadata, lifetime, confId != null);

  // Popup CallingPage for incoming call.
  if (confId == null && !newCall.callHasEnded) {
    await delegate.handleNewCall(newCall);
  }

  if (confId != null) {
    // the stream is used to monitor incoming peer calls in a mesh call
    onIncomingCall.add(newCall);
  }
}