Header Logo

How to Integrate STT, TTS, and Lip-Sync for a Flutter Concierge Avatar

How to Integrate STT, TTS, and Lip-Sync for a Flutter Concierge Avatar

Integrate STT, TTS, and lip-sync in Flutter for a realtime concierge avatar with low-latency, synced audio/video transport.

Introduction


If you want a Flutter concierge avatar that feels responsive instead of “chatty in a box,” you need three pieces working together: speech-to-text (STT) to understand the user, text-to-speech (TTS) to answer them, and lip-sync to keep the avatar visually aligned with the generated speech. The hard part is not any one subsystem; it is coordinating latency, turn-taking, and media transport so the interaction feels continuous.


In practice, the pipeline looks like this:


  • Capture microphone audio from the Flutter app.

  • Stream audio into your voice agent or backend.

  • Run STT, then your dialogue logic or LLM, then TTS.

  • Send the synthesized audio to a video avatar that can render synchronized mouth motion.

  • Play the audio and video back in the app with low enough end-to-end latency that the user never notices the plumbing.


By the end of this post, you should have a clear integration model for building that loop in Flutter, understand where the latency and synchronization risks are, and know where Protoface fits without turning your app into a media stack project.


Start with the interaction model, not the avatar


The biggest mistake is treating lip-sync as a visual effect layered on after the fact. It is better to think of the avatar as part of the voice agent’s output channel. The avatar should be driven by the same synthesized speech stream the user hears, not by a separate animation timer guessed from text length or phonemes reconstructed in the client.


That gives you a few important properties:


  • Audio is authoritative. If the TTS engine emits a 2.4-second utterance, the avatar mouth motion should track that exact timing.

  • Turn boundaries matter. Users need a clean start and stop when they interrupt, barge in, or ask a follow-up.

  • Streaming reduces perceived latency. You do not need the full response before you start animating or playing audio.


For a concierge use case, this usually means a realtime session rather than a request/response API. The agent listens, transcribes, reasons, speaks, and animates in a single ongoing session. On the Flutter side, your job is mostly to connect media streams, manage lifecycle, and surface state transitions cleanly.


Architect the Flutter side around realtime media


Flutter is a good fit for the UI and session control, but you should keep the media path simple. A common architecture is:


  1. The app authenticates the user with your backend.

  2. Your backend creates or authorizes a realtime session.

  3. Flutter joins the session and publishes microphone audio.

  4. The agent returns synthesized audio plus avatar video over the same realtime channel or a tightly coupled media channel.

  5. Flutter renders the remote video and plays the audio in sync.


The exact transport depends on your stack, but the constraints are the same whether you use WebRTC, a realtime SDK, or a hosted session layer: keep one clock as the source of truth, avoid independent buffering for audio and video, and monitor round-trip latency. If audio starts drifting ahead of the lip motion, the usual causes are:


  • Too much jitter buffering on the video path.

  • Audio playback starting before the avatar stream is ready.

  • Client-side rendering work delaying frame presentation.

  • Speech chunks being sent to the avatar later than the audio chunks that drive playback.


For mobile apps, it is worth testing on realistic network conditions early. A prototype that feels fine on localhost can become awkward once you introduce cellular jitter and device-specific audio latencies.


STT and TTS boundaries: where to process and where not to


For a concierge avatar, the cleanest design is usually: let the agent platform own STT/TTS and keep Flutter focused on transport, state, and presentation. That keeps the client thin and makes the interaction easier to reason about.


There are two practical variations:


  • Client microphone to backend: Flutter streams raw or encoded mic audio to a realtime agent. The agent handles STT, generates text, calls TTS, and returns audio/video.

  • Push-to-talk with server orchestration: Flutter sends discrete utterances. This is simpler to build, but it tends to feel less conversational because you lose barge-in and interruption handling unless you recreate it yourself.


If you are handling STT yourself, pay attention to partial transcripts. They are useful for responsiveness, but they are not stable enough to drive final actions. The safest pattern is:


  • Use partial STT for live captions or UI hints.

  • Use finalized STT for agent decisions.

  • Use streamed TTS for speech playback and lip-sync.


The same goes for TTS: streaming synthesis is usually preferable to waiting for a complete utterance. It shortens the time to first audio and gives the avatar something to animate immediately. If your TTS provider only returns a full audio blob, the avatar will always feel a beat behind.


Keep the lip-sync aligned with the audio stream


Lip-sync quality is mostly about timing fidelity, not visual realism. Even a strong face render can feel wrong if visemes or mouth motion lag the audio by a few hundred milliseconds. The avatar should be driven by the same speech timeline that feeds playback.


Three rules help a lot:


  1. Do not regenerate mouth motion client-side from text. Text is too far removed from the actual audio timing.

  2. Do not resample or rebuffer independently. If your audio player and avatar renderer drift apart, the user will notice immediately.

  3. Treat interruptions as first-class events. When the user speaks over the agent, stop the outgoing speech and reset the avatar quickly instead of letting the old mouth motion finish.


A good test is to deliberately introduce latency and see how the system behaves. For example, if you add 200 ms of network delay, does the avatar still start moving when the user expects? If not, you likely have a timing dependency hidden in the wrong layer.


Minimal implementation shape in Flutter


At a high level, the Flutter app should manage three objects: the local microphone stream, the remote session connection, and the avatar/video renderer. The code below is intentionally schematic; the exact calls will depend on the session and media SDKs you use.


// Pseudocode: Flutter app controls session lifecycle and media plumbing.

}
// Pseudocode: Flutter app controls session lifecycle and media plumbing.

}
// Pseudocode: Flutter app controls session lifecycle and media plumbing.

}


The important design point is that the app should not try to “synchronize” lip movement itself. It should only receive the already synchronized media and render it efficiently. If you need avatar state in the UI, derive it from session events such as listening, thinking, speaking, or interrupted.


Where Protoface fits in a Flutter concierge stack


Protoface is useful when you want the avatar layer handled as a realtime service instead of building and maintaining your own mouth-motion pipeline. For Flutter teams, the practical integration point is usually your backend or voice-agent layer: create a session, attach an avatar, and let the service keep the speech-to-video synchronization aligned while your app renders the resulting media.


If you are already using a voice-agent framework, the integration is even cleaner because the avatar becomes a drop-in output for the agent. For example, the LiveKit Agents plugin livekit-plugins-protoface lets a voice agent gain a synchronized talking face without you writing custom video timing logic. For teams that want to work directly against a service API, the REST API at api.protoface.com supports avatar and session management with standard bearer auth, while the Python SDK covers programmatic orchestration from backend services.


import requests

resp.raise_for_status()
import requests

resp.raise_for_status()
import requests

resp.raise_for_status()


If you prefer Python for orchestration, the SDK follows the same model: create or load an avatar, create a session, and hand the session information to the part of your system that joins the realtime media path. Refer to the docs for the exact request and response shapes, since those details matter and will change less often than blog examples.


There is also an iframe embed option when the requirement is “put an interactive avatar on a webpage with no backend and no API key in the browser.” That is not the Flutter path, but it is worth knowing if you are building the same concierge experience across mobile and web.


Operational gotchas worth planning for


Once the basic integration works, the rest of the work is usually operational:


  • Latency budgets: track microphone capture, STT, LLM, TTS, and render time separately so you know where the delay comes from.

  • Interruption handling: stop speaking immediately when the user starts a new turn.

  • Network loss: reconnect gracefully and reset media state so the avatar does not keep “talking” after the session has dropped.

  • Rate and cost control: avatar quality tiers affect billing, so tie quality selection to the actual experience you need.


Also make sure your backend owns session authorization. Do not ship long-lived API keys in Flutter. The client should receive only the minimum session token or embed URL needed for the live connection.


Conclusion


A good Flutter concierge avatar is mostly a realtime systems problem: capture audio, keep the agent and avatar on the same timeline, and render the returned media with minimal buffering. STT, TTS, and lip-sync are each straightforward in isolation; the value comes from treating them as one coordinated pipeline.


If you want the avatar layer to be reliable without becoming your team’s next media subsystem, start by wiring the agent and session flow on the backend, then let Flutter focus on joining the realtime session and rendering media. For implementation details, examples, and supported integration surfaces, start with the documentation and the relevant GitHub examples, then validate the latency and interruption behavior on real devices before you ship.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.