mugib_voice
New projects: use
mugib_sdkfor chat + voice. This package is a voice-only re-export — the voice API is unchanged.
Official Flutter SDK for Mugib real-time voice agents.
Add a voice call to your mobile app: the user talks to your Mugib AI agent by microphone, hears the reply, sees live transcripts, and can mute or hang up. You do not choose or configure a voice provider — Mugib does that on the server. Your app only needs a project API key and a voice agent id.
What you need (from Mugib dashboard)
| Item | Where |
|---|---|
| Project API key | Project → Settings → API Key |
| Voice agent id | Project → Voice Agents → numeric id |
| API base URL | https://api.mugib.com (default; use your self-hosted URL if applicable) |
Everything else — knowledge base, system/voice prompts, language, voice personality, and which voice engine runs the call — is configured in the Mugib project. No provider keys or assistant IDs in your app.
Install
dependencies:
mugib_voice: ^0.7.1 # voice only — re-exports from mugib_sdk
# mugib_sdk: ^1.0.4 # recommended: chat + voice
flutter pub get
Requirements: Flutter 3.24+ / Dart 3.5+ · iOS & Android (see permissions below).
Monorepo dev:
path: ../sdk/flutter/mugib_voice
Platform permissions
iOS — ios/Runner/Info.plist
<key>NSMicrophoneUsageDescription</key>
<string>We need the microphone for voice conversations with our support agent.</string>
ios/Podfile: platform :ios, '12.0'
Android — AndroidManifest.xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
minSdkVersion 23 or higher.
Quick start
import 'package:mugib_voice/mugib_voice.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await MugibVoice.initialize(); // once at startup
runApp(const MyApp());
}
// Inside your screen / button handler:
final call = MugibVoiceClient(
apiKey: 'YOUR_PROJECT_API_KEY',
agentId: 34,
);
call.state.listen((state) {
// idle → connecting → connected → listening / speaking → ended / error
});
call.transcripts.listen((line) {
// line.role: "user" | "assistant"
// line.text: live transcription
});
call.errors.listen((message) {
// show snackbar / dialog
});
await call.start(); // Mugib connects the call — you do nothing else
// … user talks …
call.mute();
call.unmute();
await call.end(); // hang up
await call.start(); // same client can call again
await call.dispose(); // when screen is destroyed
That is the full integration surface for most apps: apiKey + agentId +
start().
Optional: user context (payload)
Pass the same dynamic payload object supported by the chat REST API. Fully
optional — calls without options behave exactly as before.
await call.start(
options: MugibVoiceCallOptions(
userContext: {
'name': 'أحمد',
'plan': 'Pro',
'balance': '45 IQD',
'account_id': 'ACC-9921',
},
// Or reuse payload saved on a chat session (from POST /sessions):
// chatSessionId: 'chat_abc123',
),
);
The SDK sends a WebSocket context frame before audio (only when
userContext or chatSessionId is set) and appends chat_session to the
stream URL when reusing chat payload.
Optional: voice agent picker
If the project has multiple voice agents, list them and let the user pick
one. You only need id and display fields (name, language, …) — not the
provider.
final agents = await MugibVoice.listAgents(apiKey: 'YOUR_PROJECT_API_KEY');
// Example UI: show agent.name, agent.language
final selectedId = agents.first.id;
final call = MugibVoiceClient(apiKey: 'YOUR_PROJECT_API_KEY', agentId: selectedId);
await call.start();
The provider field on each agent is informational only (e.g. for an
admin/debug label). The SDK reads it from Mugib when connecting; your code
must not branch on it.
Call states
MugibCallState |
Meaning |
|---|---|
idle |
Not in a call |
connecting |
Opening the Mugib voice session |
connected |
Call is live |
listening |
Agent is waiting for the user |
speaking |
Agent is talking |
ended |
Call finished normally |
error |
Failed — see errors / errorEvents streams |
API reference
MugibVoice
| Method | Description |
|---|---|
initialize() |
Call once in main() before any voice call |
listAgents({apiKey, baseUrl}) |
Active voice agents for a project (picker UI) |
MugibVoiceClient
| Parameter | Description |
|---|---|
apiKey |
Project API key |
agentId |
Voice agent id from Mugib dashboard |
baseUrl |
Default https://api.mugib.com |
| Parameter (optional) | Description |
|---|---|
feedThresholdFrames |
Mugib Voice AI playback buffer in output samples (default 4000 ≈ 166ms). Lower = less latency, higher underrun risk |
maxReconnectAttempts |
Mugib Voice AI auto-reconnect attempts after a drop (default 3) |
reconnectBackoff |
Base delay between reconnect attempts, multiplied by attempt number (default 600ms) |
MugibVoiceCallOptions
Passed to MugibVoiceClient.start(options:). Both fields are optional.
| Field | Description |
|---|---|
userContext |
Inline map (name, plan, balance, …) injected into the agent prompt |
chatSessionId |
Reuse payload stored for a chat session (chat_… id from POST /sessions) |
MugibVoiceClient methods
| Method / property | Description |
|---|---|
start({options}) |
Start a voice call; optional MugibVoiceCallOptions for user context |
end() |
End the call |
mute() / unmute() / toggleMute() |
Microphone control |
dispose() |
Release resources; client cannot be reused |
state |
Stream of MugibCallState |
transcripts |
Stream of MugibTranscript |
errors |
Stream of error message strings |
errorEvents |
Stream of MugibVoiceError (with MugibVoiceErrorCode) |
metrics / currentMetrics |
Stream / snapshot of MugibCallMetrics (latency) |
callDuration / durationTicks |
Elapsed call time (snapshot / per-second stream) |
token |
Last session metadata from Mugib (optional; for debugging) |
Errors, metrics & call timer
// Branch on a machine-readable code instead of matching strings.
call.errorEvents.listen((e) {
switch (e.code) {
case MugibVoiceErrorCode.microphonePermissionDenied:
// prompt the user to enable mic
break;
case MugibVoiceErrorCode.quotaExceeded:
// show "upgrade plan"
break;
case MugibVoiceErrorCode.network:
// SDK auto-reconnects; show a "reconnecting…" hint
break;
default:
// e.message has a human-readable description
}
});
// Latency metrics (time-to-first-audio, per-response latency).
call.metrics.listen((m) {
print('first audio: ${m.timeToFirstAudio?.inMilliseconds} ms');
print('avg response: ${m.averageResponseLatency?.inMilliseconds} ms');
});
// Live call timer.
call.durationTicks.listen((d) => setState(() => _elapsed = d));
Error codes — MugibVoiceErrorCode
Every MugibVoiceError (and MugibVoiceException.code) carries one of these.
Branch on the code; show error.message for a human-readable description.
| Code | Meaning / typical fix |
|---|---|
microphonePermissionDenied |
User denied mic access — prompt to enable it in settings |
invalidApiKey |
API key missing/invalid (HTTP 401/403 or UNAUTHORIZED) |
agentNotFound |
agentId does not exist (HTTP 404 or NOT_FOUND) |
agentInactive |
Agent exists but is disabled — activate it in the dashboard |
notConfigured |
Provider/credentials not set on the server, or missing token fields |
quotaExceeded |
Monthly voice-minutes quota reached (HTTP 429) — upgrade plan |
network |
Transport failure / WebSocket dropped (auto-reconnect retried first) |
connectionTimeout |
Session did not become ready within the timeout |
alreadyInProgress |
start() called while a call is active — end() first |
disposed |
Client/session was disposed — create a new client |
providerError |
The voice provider reported an error mid-call |
unsupportedProvider |
Provider returned by Mugib is not supported by this SDK version |
unknown |
Cause could not be determined (e.g. malformed server response) |
Latency metrics — MugibCallMetrics
All fields are null until the relevant event occurs.
| Field | Meaning |
|---|---|
timeToFirstAudio |
start() → first audio frame (connection + greeting) |
lastResponseLatency |
User stopped speaking → first audio of the reply |
averageResponseLatency |
Rolling average of lastResponseLatency |
responseSamples |
Number of measured responses in the average |
Auto-reconnect & session resumption (Mugib Voice AI)
If the WebSocket drops unexpectedly mid-call, the SDK reconnects automatically
(state goes back to connecting, then connected) while the mic and player
keep running. Since v0.4.0 the SDK uses native session resumption —
the reconnected session continues with the same conversation context (no
repeated greeting, no lost history). The Mugib server relays a resumption handle
periodically; the SDK stores it in memory and sends it back on reconnect. If
the server signals an imminent disconnect (goAway), the SDK reconnects
proactively before the connection closes.
After maxReconnectAttempts failures it emits a network error. Tune with
maxReconnectAttempts and reconnectBackoff.
Latency tuning (Mugib Voice AI)
Mugib Voice AI has no client-side latency knob beyond the playback buffer. Lower it for snappier audio at the cost of underrun risk:
final call = MugibVoiceClient(
apiKey: 'YOUR_KEY',
agentId: 34,
feedThresholdFrames: 2400, // ≈100ms (default 4000 ≈ 166ms)
);
What Mugib handles for you
You do not configure these in the app:
- Knowledge base & MCP tool calls during the call
- System / voice prompts and agent personality
- Language, dialect, voice, greeting
- Voice transport and provider selection
- Barge-in (user interrupting the agent)
The SDK handles microphone capture, playback, transcripts, mute, and hang-up on the device. Mugib handles the AI and business logic on the server.
Architecture (for curious developers)
Your app Mugib cloud
──────── ───────────
apiKey + agentId → validates project & agent
→ loads KB, prompts, voice settings
→ starts the right voice backend (transparent)
← audio, transcripts, call state
Your integration code stays the same even if the project owner changes the voice backend in the Mugib dashboard.
Troubleshooting
| Symptom | Check |
|---|---|
Microphone permission denied |
iOS Info.plist / Android RECORD_AUDIO |
Invalid API Key |
API key from correct project |
Voice agent not found |
agentId exists and is active in Voice Agents |
Call already in progress |
Call end() before start() again, or use a new client after dispose() |
| No audio on emulator | Test on a real device; emulators have unreliable audio |
| No audio on the 2nd call (1st was fine) | Fixed in 0.4.1 — upgrade the package (flutter pub upgrade mugib_voice) |
Example app
See example/ in this package for a minimal call screen with agent picker.
Maintainers
cd sdk/flutter/mugib_voice
make help
make check
make release-patch # bump version + publish to pub.dev
Links
- Package: https://pub.dev/packages/mugib_voice
- Unified SDK (chat + voice): https://pub.dev/packages/mugib_sdk
- Integration guide: docs/flutter-sdk.md
- Voice guide: docs/voice-integration.md
- Mugib: https://mugib.com
License
MIT — see LICENSE.
Libraries
- mugib_voice
- Mugib Voice SDK — re-exports voice APIs from
mugib_sdk.