Skip to main content
A viseme is the visual shape of the mouth for a sound — the visual counterpart of a phoneme. The SDK emits one of 22 viseme ids (021). Internally, ready-made Rive mascots map those onto number inputs 100118 via the exported VISEMES_MAP; you rarely touch the raw ids directly.

The model output

Inference produces one viseme id per 10 ms frame. client.processAudio() returns that as a VisemeTimeline, not a raw array:
processAudio() returns the timeline directly. It is run-length-encoded, ~10× smaller than a per-frame array, and is exactly the change-event model the playback engine consumes, so there is no second representation to keep in sync.

The VisemeTimeline shape

It is plain JSON. Persist it anywhere — localStorage, your database, a CDN, a file — and replay it later without touching the model, the network, or a license refresh.

Helpers

All three are pure functions on the package root.

parseTimeline is the trust boundary

Persisted JSON outlives SDK versions. parseTimeline is the single gate for loading a timeline back: it validates version, frameMs, monotonic cue offsets, the leading t: 0, and viseme-id ranges. On any mismatch it throws a LipsyncError whose .code === "bad_timeline":
VISEME_TIMELINE_VERSION is bumped on any breaking shape or semantics change, so an old stored timeline fails loudly instead of animating garbage. Treat bad_timeline as “regenerate”, never as a license or network condition. It is documented in the error-code reference.

Next

Offline lip sync

Generate → persist → replay in practice.

Rive co-existence

How visemes reach the avatar.

Core client

processAudio, streaming sessions.