Firetell Flutter WebRTC SDK

A Flutter SDK for building VoIP-enabled mobile applications with the Firetell platform. Supports audio and video calls, camera controls (switch camera, mute/unmute video), hold, mute, DTMF, call transfer, and VoIP push notifications (FCM & APNs) with native WebSocket event-based signaling.

Features

  • Authentication — Workspace domain + JWT-based authentication
  • Outbound Calls — Initiate audio and video calls via REST API + WebRTC
  • Video Calls — Full video calling with local Picture-in-Picture preview, remote video rendering, camera switching (front/back), and video mute/unmute
  • Phone Numbers (DIDs) — Query agent/team accessible numbers to use as outbound Caller ID
  • Incoming Calls — Accept/reject via WebSocket or VoIP push notifications (auto-detects audio vs video)
  • Call Controls — Mute, speakerphone (loudspeaker/earpiece), camera toggle, switch camera, hold/unhold, DTMF, transfer
  • VoIP Push — FCM (Android) and APNs VoIP (iOS) push notification support
  • Full ICE — Complete ICE candidate gathering before SDP exchange
  • Real-time Events — SSE stream for workspace events (agent state, call ring, etc.)

Installation

Add to your pubspec.yaml:

dependencies:
  firetell_flutter_sdk: ^1.1.1

Quick Start

1. Initialize the Client

import 'package:firetell_flutter_sdk/firetell_flutter_sdk.dart';

final client = FiretellClient(
  jwt: 'your_agent_or_client_jwt_token',
  domain: 'your_workspace.firetell.app',
);

// Wait for initialization
final session = await client.ready;
print('Connected as ${session.username}');

2. Make an Outbound Call

// Fetch accessible phone numbers (DIDs) for Caller ID
final phoneNumbers = await client.getPhoneNumbers();
final callerId = phoneNumbers.firstOrNull?.number; // e.g. '+14155552671'

final call = await client.makeOutboundCall(
  to: '+1234567890',
  from: callerId, // Outbound Caller ID (required for PSTN/mobile calls)
);

// Listen for call state changes
call.onStateChange.listen((event) {
  print('Call state: ${event.state}');
  if (event.state == CallState.active) {
    print('Call connected!');
  }
});

// Hang up
await call.hangup();

3. Handle Incoming Calls (SSE — Foreground)

client.onCallRing.listen((params) {
  print('Incoming call from ${params.callerName} (${params.callerNumber})');
  // Show incoming call UI...
});

client.onCallOffer.listen((call) async {
  // User taps "Answer"
  await call.accept();
});

4. Handle Incoming Calls (VoIP Push — Background)

// Parse the push payload
final ringParams = CallRingParams.fromFcmData(pushData);

// Show native incoming call UI via flutter_callkit_incoming
// ...

// When user answers:
final call = await client.handlePushIncomingCall(ringParams);
await call.accept();

// When user declines (fast HTTP reject, no WS needed):
final tempCall = Call(iceServers: client.iceServers);
tempCall.callId = ringParams.callId;
await tempCall.rejectViaHttp(
  baseUrl: client.baseUrl,
  callToken: ringParams.callToken,
);

5. Register Push Tokens

// Register VoIP push token (on every cold launch / token refresh)
await PushTokenService.registerVoipPushToken(
  baseUrl: client.baseUrl,
  jwt: client.jwt,
  pushToken: fcmToken, // or APNs VoIP token
  deviceId: await DeviceIdHelper.getOrCreate(),
  platform: 'android', // or 'ios'
);

// Register notification token (for call.canceled / call.ended dismissal)
await PushTokenService.registerNotificationPushToken(
  baseUrl: client.baseUrl,
  jwt: client.jwt,
  notificationToken: fcmToken,
  deviceId: await DeviceIdHelper.getOrCreate(),
  platform: 'android',
);

Call Controls

// Mute / Unmute
await call.mute();
await call.unmute();
await call.toggleMute();

// Speakerphone (Loudspeaker / Earpiece)
await call.setSpeakerphoneOn(true);  // Turn on loudspeaker
await call.setSpeakerphoneOn(false); // Route back to earpiece
await call.toggleSpeaker();
print('Speaker active: ${call.isSpeakerOn}');

// Video & Camera Controls
await call.switchCamera();           // Switch front / back camera
await call.muteVideo();              // Turn off camera
await call.unmuteVideo();            // Turn on camera
await call.toggleCamera();           // Toggle camera on/off
print('Camera off: ${call.isCameraOff}');

// Hold / Unhold
await call.onhold();
await call.unhold();

// DTMF
call.sendDTMF('1');
call.sendDTMF('#');

// Transfer
await call.transfer('+1987654321', reason: 'Customer request');

// Hang up
await call.hangup();

Call State Machine

Outbound: none → initiated → ringing → answered → active → ended
Inbound:  none → ringing → answered → active → ended
Hold:     active ↔ onHold

WebSocket Signaling Protocol

The SDK uses the same native WebSocket event-based JSON signaling protocol as the Firetell browser SDK:

Event Direction Description
session.connect Client → Server Authenticate with call_token (must be within 3s)
session.connected Server → Client Authentication ACK
call.offer Client → Server SDP Offer (audio / video)
call.answer Client → Server SDP Answer
call.hold Client → Server Hold call (with renegotiated SDP)
call.unhold Client → Server Unhold call (with renegotiated SDP)
call.hangup Client → Server End call
call.reject Client → Server Reject incoming call
call.mute Client → Server Mute/unmute microphone notification
call.camera Client ⇄ Server Camera state change notification (muted: true/false)
call.dtmf Client → Server DTMF digit
call.transfer Client → Server Transfer call

Architecture

FiretellClient
├── REST API (POST /api/v1/call-center/calls → call_id, call_token, ws_url)
├── SSE Stream (GET /stream → real-time workspace events)
└── Call (per-call instance)
    ├── Native WebSocket (ws_url, authenticated via call_token)
    └── RTCPeerConnection (flutter_webrtc, Full ICE)

Platform Setup

Android

Add to AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />

<!-- Bluetooth headset audio routing (Required for Android 12+) -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

iOS

Add to Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>Firetell needs microphone access for VoIP calls</string>
<key>NSCameraUsageDescription</key>
<string>Firetell needs camera access for video calls</string>
<key>UIBackgroundModes</key>
<array>
  <string>voip</string>
  <string>audio</string>
  <string>fetch</string>
  <string>remote-notification</string>
</array>

License

MIT — see LICENSE for details.

Libraries

firetell_flutter_sdk
Firetell WebRTC VoIP SDK for Flutter.