placeCall method

Future<NexusCall?> placeCall({
  1. required String to,
  2. VoiceEndpointType type = VoiceEndpointType.app,
  3. String? callerId,
  4. String? displayName,
  5. String? callerDisplayName,
  6. String? callerAvatarUrl,
  7. bool record = false,
  8. Map<String, dynamic> metadata = const {},
})

Place a call to to — a Nexus identity (app), a phone number (pstn) or a SIP URI. This device joins as a WebRTC leg; the control plane dials to.

Implementation

Future<NexusCall?> placeCall({
  required String to,
  VoiceEndpointType type = VoiceEndpointType.app,
  String? callerId,
  String? displayName,
  String? callerDisplayName,
  String? callerAvatarUrl,
  bool record = false,
  Map<String, dynamic> metadata = const {},
}) async {
  try {
    if (!await _ensureMicPermission()) return _fail(null, 'microphone_denied');
    // The name + avatar the CALLEE sees for us (rings + call screen). Ride in
    // session metadata → the backend puts them in the ring payload as
    // `callerName` / `callerAvatar`.
    final md = <String, dynamic>{
      ...metadata,
      if (callerDisplayName != null && callerDisplayName.isNotEmpty) 'callerName': callerDisplayName,
      if (callerAvatarUrl != null && callerAvatarUrl.isNotEmpty) 'callerAvatar': callerAvatarUrl,
    };
    final session = await _post('/partner/voice/calls', {
      'direction': 'outbound',
      'recordingMode': record ? 'mixed' : 'disabled',
      if (md.isNotEmpty) 'metadata': md,
    });
    final sessionId = _id(session?['session']);
    if (sessionId == null) return _fail(null, 'session_create_failed');

    _set(NexusCall(
      sessionId: sessionId,
      direction: VoiceCallDirection.outbound,
      state: VoiceCallState.connecting,
      remoteAddress: to,
      remoteName: displayName,
      startedAt: DateTime.now(),
      metadata: metadata,
    ));
    // On ANDROID we deliberately do NOT start a native outgoing call: a
    // self-managed outgoing ConnectionService can auto-end and echo back an
    // "ended" event that would tear down a healthy call, and a foreground
    // outbound call is driven by the app's own UI + the media engine.
    // On iOS the opposite is true and mandatory: CallKit only activates the
    // audio session for a call it knows about, so an unreported outbound call
    // would connect with no audio at all.
    if (!kIsWeb && Platform.isIOS) {
      unawaited(_guard(() => _callKit.reportOutgoing(
            callId: sessionId,
            handle: to,
            displayName: displayName ?? callerDisplayName,
          )));
    }

    // 1) This device's WebRTC leg → join the media room. Carry our identity so
    // the control plane can tell the callee WHO is calling (the ring's `from`).
    final myLeg = await _post('/partner/voice/legs', {
      'sessionId': sessionId,
      'role': 'caller',
      'endpointType': 'webrtc',
      'direction': 'outbound',
      'identityId': _identityId,
    });
    final legId = _id(myLeg?['leg']);
    final token = NexusJoinToken.tryParse(myLeg?['join'] as Map<String, dynamic>?);
    if (myLeg?['error'] != null) {
      NexusLog.warn('voice: backend could not provision media (${myLeg!['error']}) — '
          'is LiveKit configured + reachable from the backend?');
      return _fail(sessionId, myLeg['error'].toString());
    }
    if (legId == null || token == null) return _fail(sessionId, 'join_failed');
    NexusLog.info('voice: joining media room "${token.room}" at ${token.url}');
    _set(current.value!.copyWith(legId: legId, state: VoiceCallState.ringing));
    try {
      await _engine.connect(token);
    } catch (e) {
      NexusLog.error('voice: media connect failed to ${token.url} — $e. '
          'The DEVICE must be able to reach the media server; a private LAN IP is '
          'unreachable over a tunnel / cellular / different Wi-Fi.');
      return _fail(sessionId, 'media_connect_failed');
    }

    // 2) Dial the remote party (app/pstn/sip).
    final callee = await _post('/partner/voice/legs', {
      'sessionId': sessionId,
      'role': 'callee',
      'endpointType': type.name,
      'direction': 'outbound',
      'address': to,
      'callerId': ?callerId,
      if (type == VoiceEndpointType.app) 'identityId': to,
    });
    if (callee?['error'] != null) return _fail(sessionId, callee!['error'].toString());

    _startPresence();
    _armRingTimeout(sessionId); // auto-end if the callee never answers
    return current.value;
  } catch (e) {
    NexusLog.error('voice.placeCall failed: $e');
    return _fail(current.value?.sessionId, 'error');
  }
}