Vanira Flutter SDK (vanira_sdk)

Production-oriented Flutter client for Vanira voice sessions over WebRTC. It aligns with the TypeScript SDK’s signaling, DataChannel protocol, and session flow—implemented with flutter_webrtc.

Features

  • WebRTC voice: microphone, RTCPeerConnection, control DataChannel, SDP offer/answer via HTTP signaling.
  • createCall + connect: pass agentId and apiKey only; optional serverUrl / callId if you create calls yourself.
  • ICE: fetches ephemeral servers from Vanira when apiKey is set; normalizes username / credential for Android (avoids native username == null crashes).
  • Layered layout: protocol, transport, observability, reconnect (mirrors the TS SDK structure conceptually).

Requirements

  • Dart ^3.11.1
  • Flutter ( mobile: Android / iOS )
  • Microphone permission (see below)

Install

Path (monorepo / local dev):

dependencies:
  vanira_sdk:
    path: ../vanira-sdk/flutter-sdk   # adjust to your layout

Published (when available on pub.flutter-io.cn):

flutter pub add vanira_sdk

Transitive deps used by the package: flutter_webrtc, http, permission_handler (versions are defined in this package’s pubspec.yaml).

App setup

VaniraSession.connect() calls ensureFlutterWebRtcInitialized() first (same Android audio options as below), so a minimal main() that only uses the SDK for WebRTC can omit this block. Keep an explicit WebRTC.initialize in main() if you use flutter_webrtc elsewhere before connecting, or you want initialization as early as possible.

Call WebRTC.initialize once before creating a session—especially on Android—to reduce native edge cases:

import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'dart:io' show Platform;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final options = <String, dynamic>{};
  if (Platform.isAndroid) {
    options['androidAudioConfiguration'] =
        AndroidAudioConfiguration.communication.toMap();
    options['bypassVoiceProcessing'] = true; // optional; helps some devices
  }
  await WebRTC.initialize(options: options);

  runApp(const MyApp());
}

2. Microphone permission

The SDK requests mic access via permission_handler (MediaPipeline). Ensure Android RECORD_AUDIO and iOS NSMicrophoneUsageDescription are configured.

Usage

Minimal: API key + agent ID

You do not need serverUrl if apiKey is set—the SDK calls /calls/create, then connects to the returned worker URL.

import 'package:vanira_sdk/vanira_sdk.dart';

final session = VaniraSession(
  VaniraSessionConfig(
    agentId: 'your-agent-id',
    apiKey: 'your-api-key',
    // backendUrl defaults to https://api.vanira.io
    onConnected: () => print('connected'),
    onDisconnected: () => print('disconnected'),
    onError: (e) => print('error: $e'),
    onTranscription: (text, isFinal) => print('$text (final=$isFinal)'),
    onClientToolCall: (tool) => print('tool: $tool'),
  ),
);

await session.connect();

Explicit createCall

Same as above flow-wise; useful if you want a clear two-step API:

await session.createCall(); // sets worker URL + call id, then connects

Bring your own serverUrl

If you already have a worker URL and call id:

await session.connect(
  serverUrl: 'https://worker.example.com/...',
  callId: 'call_…',
);

Disconnect

await session.disconnect();

Optional: logger, reconnect, ICE override

  • logger: VaniraLogger — (level, message, [context]) to integrate with your logging.
  • reconnect: ReconnectConfig — exponential backoff reconnection; null disables.
  • iceServers: static ICE list; if set, replaces backend ICE fetch for that session.

Package layout

lib/
  vanira_sdk.dart              # export entrypoint
  src/
    vanira_session.dart        # VaniraSession, VaniraSessionConfig, ReconnectConfig
    protocol/                  # events, state machine, kProtocolVersion
    transport/                 # ICE, signaling, media pipeline
    observability/             # logger
    reconnect/                 # reconnect manager

Protocol overview

Outbound HTTP signaling includes protocol_version (see kProtocolVersion in code, aligned with TS PROTOCOL_VERSION). The SignalingClient builds the same JSON shape as the npm SDK’s buildSignalingPayload.

Server → client (DataChannel)

event Notes
clearAudio No-op client-side; native stack handles audio.
transcription onTranscription(text, isFinal).
mark Logged; extend as needed.
client_tool_call Immediate client_tool_ack when tool_call_id is present; then onClientToolCall.

Client → server

API Event
sendToolResult(callId, result) client_tool_result
sendToolAck(toolCallId) client_tool_ack
sendContextUpdate(map) client_context_update
sendActionTrigger(name, [data]) client_action_trigger
triggerActionInterrupt() action_interrupt
sendEvent(name, [data]) custom / generic

TypeScript ↔ Dart WebRTC mapping (mental model)

TS / browser Dart (flutter_webrtc)
new RTCPeerConnection(config) await createPeerConnection(config, {})
getUserMedia navigator.mediaDevices.getUserMedia(...)
pc.createDataChannel(...) await _pc.createDataChannel(..., RTCDataChannelInit())
createOffer / setLocalDescription / setRemoteDescription same async methods on RTCPeerConnection
pc.onconnectionstatechange = … _pc.onConnectionState = (state) { ... } (setter + callback, not Stream.listen unless the plugin exposes streams in your version)
fetch package:http (SignalingClient inside the SDK)

Differences vs browser / React Native SDK

Topic Browser / RN Flutter
Remote audio <audio> / RN native Routed by flutter_webrtc (no Audio widget)
playedStream Browser can send after playback ends Not sent (no onended equivalent; backend should not block on it)
Screen share Browser / N/A on RN Not supported here
registerGlobals() RN / react-native-webrtc Not used; use WebRTC.initialize for Flutter
Distribution npm / CDN pub.flutter-io.cn or path / git dependency

Security

  • Treat apiKey as a secret: avoid committing it (use --dart-define, CI secrets, or a short-lived token from your backend for production).
  • All Vanira HTTP calls use TLS against backendUrl (default https://api.vanira.io).

Development

cd flutter-sdk
dart pub get
dart analyze lib

To run the in-repo smoke app, use vanira_flutter_test in this monorepo (path dependency on vanira_sdk).

License

See LICENSE.

Libraries

vanira_sdk