The offline path is the SDK’s most powerful pattern: run inference once, persist the result as JSON, and replay it forever without touching the model, the network, or a license refresh. It is the right tool for prefetching, queued playback, deterministic video export, and any case where the same audio is animated more than once. The artifact is theDocumentation Index
Fetch the complete documentation index at: https://docs.mascot.bot/llms.txt
Use this file to discover all available pages before exploring further.
VisemeTimeline — plain,
versioned JSON.
Generate → persist → replay
Generate once
Run
processAudio (vanilla) or useProcessAudio (React). result.timeline
is the artifact.React
Vanilla
Assembling a timeline yourself
If you already have per-frame viseme ids (e.g. from your own batch job), build a timeline with the pure converters instead of running inference:Prefetching & queues
Because a timeline is detached from the model, you can compute many ahead of time and play them instantly later:- Prefetch on idle — generate timelines for likely-next utterances during idle time; play from cache the moment they are needed (no inference latency at play time).
- Queue playback — store a list of
{ audioUrl, timeline }pairs; for each, start the audio andplayback.setTimeline(timeline)in lockstep. - Server-side precompute — generate timelines in a build step or backend job, ship the JSON with your assets, and the client never runs inference for that content at all.
speechMs rides inside the timeline, so cached replay never re-meters and
never re-infers.
Deterministic video export
For frame-accurate rendering (recording the avatar to video), a timeline gives you a fixed, inspectable script: the same JSON produces the same mouth frames every run. Driveplayback.seek(ms) to a render clock instead of wall-clock
playback, capture the canvas per frame, and mux against the original audio.
Because there is no live inference in the loop, export is reproducible and as
fast as your renderer.
Versioning & the trust boundary
parseTimeline validates untrusted/persisted JSON and throws a LipsyncError
with .code === "bad_timeline" on a version or shape mismatch — so a stale
stored timeline fails loudly instead of animating garbage:
parseTimeline, never JSON.parse
alone. VISEME_TIMELINE_VERSION bumps on breaking changes; old artifacts are
rejected deterministically.
Next
Visemes & the timeline
The timeline format in detail.
Core client
processAudio and helpers.Error codes
bad_timeline and the rest.