How to Stream an AI Avatar in Flutter Using FastAPI and WebRTC

Learn how to stream a realtime AI avatar in Flutter with FastAPI and WebRTC, including session setup, signaling, and audio-video sync.
Introduction
Streaming an AI avatar in Flutter is mostly a systems-integration problem: you need low-latency audio, synchronized video, and a transport that works well on mobile networks. WebRTC is the right tool for that job because it gives you real-time media delivery, jitter handling, congestion control, and NAT traversal without forcing you to build a custom media stack.
This post shows the practical shape of the solution: how a Flutter client connects to a realtime avatar session, what the server side needs to do in FastAPI, and where WebRTC constraints show up in the architecture. By the end, you should have a clear implementation plan for wiring a voice or conversational agent to a lip-synced video face, without confusing media signaling, session orchestration, and app state.
If you are building on Protoface, the same basic flow applies whether you are exposing avatars through your own app, a voice agent backend, or a browser embed. The details differ, but the transport and lifecycle concerns are the same.
What “streaming an avatar” actually means
There are three separate streams in a realtime avatar experience:
Audio from the user to your agent or backend.
Agent output audio back to the client.
Avatar video that is synchronized to the agent’s speech.
In a WebRTC setup, the client does not “download a video file.” It subscribes to a live media track that is being produced in realtime. The avatar is usually driven by TTS and phoneme/viseme timing so the mouth motion matches the generated speech closely enough to feel natural.
That distinction matters because latency budgets are tight. If your client waits on a full request/response cycle before playback, the avatar will feel sluggish. The correct approach is to establish a session, negotiate media transport, and keep the connection open while the conversation runs.
Recommended architecture with Flutter and FastAPI
The simplest production architecture looks like this:
Flutter app authenticates the user with your backend.
FastAPI creates or looks up a realtime avatar session.
Backend returns session credentials or connection metadata to the app.
Flutter connects over WebRTC and renders the remote avatar video track.
User audio is captured in Flutter and sent into the session if your agent needs it.
Keep the server as the source of truth for session creation and authorization. Do not mint long-lived media credentials in the client. On mobile, the client should hold only short-lived tokens or ephemeral session parameters that are safe to expose.
In practice, your FastAPI layer is responsible for:
creating sessions for a given user or conversation,
attaching avatar configuration and prompt/instructions,
issuing a token or join payload for the client,
recording session state, timeouts, and usage.
FastAPI: create a session endpoint
A good first cut is an endpoint that returns the information Flutter needs to join a live avatar session. The exact fields depend on the avatar and session model in the docs, but the shape is usually something like this:
The important part is not the exact JSON shape; it is that the backend owns the secret API key and the browser or Flutter app only receives what it needs to connect.
If you want to create or inspect sessions directly from Python, the SDK is useful for orchestration jobs, admin tools, and tests. See the Python SDK repo for examples: https://github.com/protoface-ai/protoface-sdk-python.
Flutter: connect to the media session and render video
On the Flutter side, your job is mostly media plumbing. You obtain the session payload from FastAPI, initialize your WebRTC client, and attach the remote video track to a widget.
At a high level:
request camera/microphone permissions if you are capturing user audio,
create a peer connection,
exchange SDP/ICE information via your signaling layer,
subscribe to the remote avatar track,
render that track in a
RTCVideoRendereror equivalent widget.
Here is a simplified sketch of the client flow:
In a real implementation you also need ICE candidate exchange and reconnection handling. Those are not optional; they are the difference between a demo and something that survives mobile network transitions.
Two practical gotchas:
Do not block the UI thread. Video render setup, permissions, and session fetches should be asynchronous.
Assume reconnects happen. If the app backgrounds, network conditions change, or the session times out, you need a clean teardown and rejoin path.
Audio sync and avatar realism
The avatar only feels good if the audio pipeline is stable. WebRTC helps with transport, but it does not solve the timing of the underlying speech generation. The agent should produce audio in small, low-latency chunks, and the avatar renderer should use the same speech timing metadata that drives the mouth animation.
That means you should avoid unnecessary buffering between TTS output and media send. If you insert a large queue in your backend, the video may still play smoothly, but the conversational latency will feel off. A realistic target is “fast enough to overlap turns,” not “fully generated before playback starts.”
If you are mixing user speech detection with avatar speech, make sure your turn-taking policy is explicit. Common mistakes include:
starting avatar speech while user audio is still active,
dropping late audio frames and causing visible desync,
treating the avatar as a static video stream instead of a live, stateful participant.
How Protoface fits this pattern
This is exactly the kind of integration Protoface is built for: you keep your Flutter app focused on media rendering and session lifecycle, while the platform handles the avatar-side realtime session. The REST API is useful when your FastAPI backend needs to create or manage sessions programmatically, and the Python SDK is convenient for server-side orchestration or admin tooling. If you are using a LiveKit-based voice agent, the plugin path is even simpler because the avatar can be dropped into the existing agent pipeline without rebuilding your media stack from scratch.
For implementation details, the public docs are the right reference point: https://docs.protoface.com. If you are integrating with a LiveKit voice agent, the plugin repo and examples are here: https://github.com/protoface-ai/protoface-quickstart-openai-realtime and the broader quickstart collection is linked from the project README. For a lower-level SDK-driven backend, the Python SDK repository is the place to start.
Operational details you should not skip
Realtime avatars are operationally closer to voice infrastructure than to traditional frontend rendering. A few things are worth planning for up front:
Session expiry. Decide how long a session can remain idle before you tear it down.
Rate limiting. Protect your backend from session creation bursts and repeated reconnect loops.
Telemetry. Measure join time, first audio time, first video frame time, and reconnect frequency.
Fallback UX. If the video track fails, the conversation should still continue as audio-only rather than hard-failing.
Also remember that on mobile, permissions and backgrounding are first-class concerns. If the app goes into the background, the WebRTC session may survive, but the UX may not. Design explicit resume behavior instead of assuming the media stack will recover itself.
Conclusion
The core pattern is straightforward: FastAPI handles authenticated session creation, Flutter handles WebRTC media rendering, and the avatar service handles realtime speech-to-video generation. Once you separate those responsibilities, the integration becomes mostly about signaling, reconnection, and keeping latency under control.
If you want to implement this for real, start with a minimal backend endpoint, verify you can join and render one remote video track in Flutter, and then layer in turn-taking, reconnects, and usage tracking. The docs at https://docs.protoface.com and the quickstart repos are the fastest path from architecture to working prototype.
