Build a SIP or PSTN calling workspace
Telephony applications have two connected lifecycles: a SIP/PSTN call managed by a telephony service and a MediaSFU room that gives a browser or mobile operator a realtime audio surface. The MediaSFU VOIP repository contains React, Flutter, React Native, and Expo clients plus a room proxy. Platform-specific dialer references can help with application layout, but they do not establish MediaSFU SDK or carrier-runtime parity.
This is a blueprint, not a copy-and-paste tutorial. Start with the secure backend proxy guide, then use the package-specific room and lifecycle guide for your client.
Build this: compare the runnable starter and its platform evidence. View source: MediaSFU VOIP.
The user journey
- Enter an E.164 number or select a contact.
- An authenticated backend authorizes the action, creates or joins the MediaSFU room, and starts or joins the telephony leg.
- Show dialing, ringing, connected, held, failed, and ended as distinct states.
- Let the operator mute, change audio route, inspect participants, and—when policy allows—switch between an automated agent and a human.
- Hang up the telephony leg, leave the room, and confirm cleanup before clearing the active-call UI.
The SDK supplies room media and participant state. Telephony routing, number normalization, trunk configuration, call history, billing, recording policy, and agent rules belong to your backend and application.
Package paths in the repository
| Client | MediaSFU package declaration | Repository behavior |
|---|---|---|
| React web | mediasfu-reactjs 4.3.0 | Headless MediasfuGeneric, backend create/join callbacks, participant/audio display, and call controls |
| Flutter | mediasfu_sdk 2.3.0 | MediasfuGeneric, MediasfuRoomDisplay, microphone/audio controls, SIP state, and agent/operator controls |
| React Native CLI | mediasfu-reactnative 2.4.0 | Native room handler/display and call-service integration |
| Expo | mediasfu-reactnative-expo 2.5.0 | Expo room handler/display and web/mobile shell |
| Angular | no working client; planned placeholder | Do not present Angular as an available VOIP client from this repository |
| Kotlin Compose sample | com.mediasfu:mediasoup-client:1.0.0; mediasfu-sdk is commented | Dialer, contacts, recent calls, permission request, mute/speaker UI; no wired MediaSFU room controller |
The React sample describes an audio-first build and does not implement video, screen sharing, transfer, or server-side recording. The Flutter sample likewise focuses on audio and does not claim video, push notifications, or background calling. These are application limits even where an SDK offers more features.
Secure create and join
The repository quick start includes a Node room proxy on port 3002. It exposes POST /api/rooms/create and POST /api/rooms/join, validates public fields, supplies the server-side authorization header, and lets clients use headless room callbacks.
const response = await fetch(VOIP_API_URL + '/api/rooms/create', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ action: 'create', userName, duration: 30 }),
});
if (!response.ok) throw new Error('Room creation failed');
const room = await response.json();
Use an authenticated application session around this route in a real product. The sample proxy secures room create/join; SIP configuration, call placement, agent controls, and call history also need an authenticated backend before release. Never put a reusable MediaSFU credential in a browser, mobile bundle, or committed environment file.
For an exact retry of one create or join request, send one opaque Idempotency-Key (8–128 visible ASCII characters) and reuse it only for that same logical request. Use a new key for another call or room.
Separate telephony and room state
Model the two state machines explicitly:
type CallPhase =
| 'idle' | 'dialing' | 'ringing' | 'connected'
| 'held' | 'failed' | 'ended';
type RoomPhase = 'not-created' | 'joining' | 'connected' | 'left' | 'ended';
function finishCall() {
// End the SIP/PSTN call through your authenticated call service.
// Leave the MediaSFU room through the installed SDK controller.
// Stop polling, clear listeners, and confirm backend cleanup.
}
On the web, keep the current SDK parameter read pure and render every prepared remote audio element independently of the selected participant tile. In Flutter, keep the MediasfuParameters snapshot in the room widget and dispose listeners and timers. Native clients must use their measured media container.
Agent and human takeover
The VOIP clients contain application controls for starting/stopping an agent, switching the active source to a human, and choosing whether bot audio is caller-only or played to all room participants. These controls call telephony or application endpoints; they are not general MediaSFU SDK operations.
- Ask the backend whether an agent is configured for the destination.
- Start it only after the room and telephony call are connected.
- On human takeover, stop or pause agent playback, identify the operator, switch the application source, and keep room audio mounted.
- On return to the agent, re-authorize the transition and prevent stale audio.
The operation concepts are telephony.sip_pstn, room.create.secure, room.join, media.produce.audio, media.consume, media.track.control, ai_agent.session_handoff, agent.operator_takeover, room.end_state.distinguish, and room.cleanup.
Cloud and self-hosted deployment
With MediaSFU Cloud, your backend talks to the managed room and telephony service. With MediaSFU Open, your team runs the media server and points the backend at its existing room endpoint. A URL or localLink does not install or start MediaSFU Open. SIP trunks, PSTN numbers, and telephony credentials remain separate provider assets protected by your backend.
Recovery and cleanup
- Show permission and route failures instead of treating a silent call as connected.
- Keep dialing and room status separate: a connected room does not prove that the PSTN leg answered.
- After reconnect, reread the current snapshot before enabling controls and do not publish shared parameters during render or build.
- Treat host end, participant leave, and telephony hang-up as distinct actions. Stop polling, remove audio sources, dispose room listeners, and confirm backend cleanup.
- Configure recording consent, storage, retention, retrieval, and export in the authenticated backend that owns the call.
Before release
- Run the proxy and client checks from the repository quick start, then test a real two-party call on every target device family.
- Test dialing, answer, mute, speaker/Bluetooth route, hold/resume, agent start and stop, human takeover, hang-up, reconnect, and cleanup.
- Require an authenticated backend session for SIP configuration and agent actions.
- Never ship or log room credentials, telephony secrets, cleanup capabilities, raw authorization headers, or full upstream responses.