utd_calls_kit 0.9.2 copy "utd_calls_kit: ^0.9.2" to clipboard
utd_calls_kit: ^0.9.2 copied to clipboard

PlatformAndroid

LiveKit-based WhatsApp-style 1:1 audio & video calling for Flutter — independent UTD-Stream kit.

utd_calls_kit #

LiveKit-based, WhatsApp-style 1:1 audio & video calling for Flutter — an independent UTD-Stream kit (no dependency on utd_stream_sdk).

Features #

  • 1:1 audio & video calls over the UTD-Stream Engine (LiveKit).
  • No-backend mint: ship app_id + the publishable app_key; the per-user bearer is minted server-side. The project server_secret never ships.
  • Presence + incoming-call signalling over a single WebSocket.
  • Default call UI (incoming / outgoing / in-call), live audio↔video upgrade, speaker/earpiece/bluetooth routing, proximity blanking, in-app minimize and Android OS Picture-in-Picture.
  • OS-native incoming-call UI — CallKit on iOS, ConnectionService on Android.
  • v0.9: wake-from-killed — iOS PushKit VoIP and Android FCM data pushes ring the device into CallKit even when the app is backgrounded or killed; accepting from a cold start resumes and joins the call.

Capabilities & billing #

Calling rides the paid signaling capability (call invitation + presence), billed separately from chat. Both are sold via a subscription plan (Free / Signaling / Pro / Enterprise); billing is MAU-based — no per-minute or per-message charge.

The session mint surfaces the flags + plan limits on the controller (all nullable — null means the engine didn't report it, on an older engine):

await controller.init();
if (controller.signalingEnabled == false) {
  // Signaling isn't enabled for this plan — show an upsell instead of calling.
}

If a call is refused because signaling isn't enabled, the failure ends with UTDCallEndReason.notActivated and the rethrown UTDStreamException carries isFeatureDisabled / isSignalingDisabled, so the UI can show an upsell rather than a generic error.

Getting started #

final controller = UTDCallController(
  const UTDCallsConfig(
    appId: '9325906999',
    appKey: 'pk_live_...',     // publishable app key (no server secret)
    userId: 'user-42',
    userName: 'Sara',
    // enableCallKit: true,    // default — native incoming-call UI
  ),
);

await controller.init();           // mint bearer + connect signal WS + start CallKit
await controller.startCall('user-99', name: 'Omar', type: UTDStreamType.videoCall);

Incoming calls — native UI (v0.5) #

When UTDCallsConfig.enableCallKit is true (the default), an incoming call.invitation over the signal WebSocket shows the OS-native call screen (CallKit / ConnectionService) via UTDCallKitBridge:

  • Accept on the native screen → controller.acceptIncoming().
  • Decline / timeoutcontroller.reject().
  • End from the native UI → hangs up / declines as appropriate.
  • The native call timer starts when the media room connects (setConnected).
  • The native UI is dismissed on any terminal state.

The existing in-app UTDIncomingCallScreen remains the fallback whenever CallKit is disabled or the host app is not configured for it. Set enableCallKit: false to use the in-app screen only.

Wake-from-killed — VoIP / FCM push (v0.9) #

When UTDCallsConfig.enablePush is true (the default) the kit registers this device's push tokens with the engine and wakes the app for an incoming call even when it is backgrounded or killed:

  • iOS (PushKit VoIP): the engine sends a VoIP push; iOS delivers it straight into CallKit via flutter_callkit_incoming (no Dart needed while killed). The user's accept/decline flows through UTDCallKitBridge → the controller. On a cold start (accepted while killed), the controller looks up the call_id, fetches the call, and joins.
  • Android (FCM data): a top-level @pragma('vm:entry-point') background handler (utdFirebaseBackgroundHandler) shows the full-screen CallKit incoming UI (backed by the plugin's foreground service) on a {type:'call'} data message. Foreground messages take the same path.

Token collection is automatic on controller.init(): FlutterCallkitIncoming.getDevicePushTokenVoIP() (iOS VoIP) and/or FirebaseMessaging.instance.getToken() (FCM) are registered via POST /api/v1/devices/push-token; refreshes re-register. Call controller.unregisterPush() on sign-out.

Token collection degrades gracefully: if the host's Firebase / PushKit setup is missing, no tokens are registered and the foreground signal-WS path keeps working. Set enablePush: false to opt out entirely.

Engine gating: the engine only dispatches pushes once its APNS_* (iOS) and FCM_* (Android) credentials are provisioned. Until then devices register tokens but receive no background pushes — calls arrive only via the foreground signal WebSocket.

REQUIRED HOST APP config #

Firebase (both platforms) #

firebase_messaging requires the host app's Firebase project files (this package cannot provide them):

  • Android: android/app/google-services.json, the Google Services Gradle plugin applied in the host app.
  • iOS: ios/Runner/GoogleService-Info.plist.
  • The host must call await Firebase.initializeApp() (e.g. in main(), with WidgetsFlutterBinding.ensureInitialized()) before controller.init(). The kit does not initialise Firebase for you.
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();   // host responsibility
  runApp(const MyApp());
}

flutter_callkit_incoming needs native configuration in the host app (it cannot be provided by this package). Without it the native call UI will not appear and only the in-app fallback works.

iOS (ios/Runner/Info.plist) #

<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
  <string>voip</string>
</array>
  • voip enables PushKit VoIP wake-from-killed; audio keeps the audio session alive in-call.
  • APNs + PushKit: in Xcode enable the Push Notifications capability and the Background Modes → Voice over IP + Remote notifications options. Upload your APNs key/cert to the Firebase project (FCM proxies APNs) and/or provision the engine's APNS_* credentials for direct VoIP pushes.
  • Microphone / camera usage descriptions are already needed for calls: NSMicrophoneUsageDescription, NSCameraUsageDescription.
  • Minimum iOS 12 for CallKit / PushKit.

Android (android/app/src/main/AndroidManifest.xml) #

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Already required by the calls kit: -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
  • On Android 13+ request POST_NOTIFICATIONS at runtime, and on Android 14+ the full-screen-intent permission may need FlutterCallkitIncoming.requestFullIntentPermission() / requestNotificationPermission(...).
  • compileSdk/targetSdk 34+ recommended; minSdk 21+.
  • Follow the flutter_callkit_incoming README for the CallkitIncomingActivity / receiver manifest entries if you customise the native screen.
  • FCM: the Google Services Gradle plugin + google-services.json are required for firebase_messaging to receive the {type:'call'} data message that the kit's background handler turns into a CallKit screen.

Engine env (ops / backend) #

Push dispatch is performed by the engine and is gated until its push credentials are provisioned. Until then, devices register tokens normally but no background push is sent — calls arrive only via the foreground signal WebSocket.

  • APNS_* — Apple Push (PushKit VoIP) credentials for iOS.
  • FCM_* — Firebase Cloud Messaging credentials for Android.

The engine endpoints the kit consumes (already implemented): POST / DELETE /api/v1/devices/push-token (Bearer) for registration, and the data-push payload {type:'call', call_id, caller_identity, caller_name, call_type} the kit parses.

Additional information #

  • The kit follows the UTD-Stream layering (lib/src/..., utd_ prefix, flutter_lints); only the public API is re-exported from utd_calls_kit.dart.
  • Device QA pending — 1.0.0 is gated on it. Push wake-from-killed, CallKit, the pickers, and cold start cannot be exercised by flutter analyze or unit tests. Required checklist:
    • iOS (PushKit VoIP): incoming call rings via CallKit while the app is (a) locked, (b) backgrounded, (c) killed; accept from each foregrounds the app and joins; decline rejects; cold-start accept resumes + joins; VoIP token registers and re-registers on rotation.
    • Android 13 & 14 (FCM data): incoming call shows the full-screen CallKit UI while the app is (a) backgrounded and (b) killed; accept joins; decline rejects; POST_NOTIFICATIONS (13+) and full-screen-intent (14+) prompts behave; FCM token registers and re-registers on refresh.
    • Both: unregisterPush() on sign-out stops further pushes; graceful no-op when Firebase/PushKit is not configured.
0
likes
130
points
0
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

LiveKit-based WhatsApp-style 1:1 audio & video calling for Flutter — independent UTD-Stream kit.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

equatable, firebase_core, firebase_messaging, floating, flutter, flutter_callkit_incoming, flutter_screenutil, flutter_webrtc, livekit_client, proximity_sensor, utd_signaling, uuid

More

Packages that depend on utd_calls_kit