Skip to content

Architecture

OmniAvatar is split across two Go modules, following the same pattern as OmniVoice.

Core vs. Batteries-Included

Module Import when
omniavatar-core You accept avatars as an interface (library code, consumers like videoascode) — zero provider dependencies
omniavatar You construct providers — pulls in provider SDKs and LiveKit
omniavatar-core/              # Core interfaces (no provider deps)
├── live/                     # Real-time sessions
│   ├── provider.go           # live.Provider
│   ├── session.go            # live.Session + callbacks
│   ├── audio.go              # live.AudioDestination
│   └── errors.go
├── render/                   # Batch video generation
│   ├── provider.go           # render.Provider, render.AudioUploader
│   ├── request.go            # render.GenerateRequest
│   ├── job.go                # render.Job, JobStatus, render.Wait
│   └── errors.go
└── registry/                 # Shared config + factory types

omniavatar/                   # Provider implementations
├── registry.go               # Global live + render registries
├── token.go                  # LiveKit token generation
└── providers/
    ├── heygen/               # live + render
    ├── tavus/                # live + render
    ├── bithuman/             # live + render
    └── all/                  # convenience import

The Two Surfaces

The module names its packages after the mode, not the vendor concept:

  • live — session-oriented: Start, WaitForJoin, stream PCM frames, Close. The avatar joins a room and speaks in real time.
  • render — job-oriented: Generate, Status/Wait, Download. Audio in, MP4 out, minutes later.

avatar is deliberately not a package name: with two surfaces, live.Session and render.Job are self-describing at every call site, while avatar.X would not say which mode is involved.

Provider Registry

Providers self-register both surfaces via init() when imported:

// Import one provider
import _ "github.com/plexusone/omniavatar/providers/heygen"

// Or all providers
import _ "github.com/plexusone/omniavatar/providers/all"

Registration carries a priority so alternative implementations can coexist:

Priority Constant Meaning
0 PriorityThin Minimal (stdlib-only) implementations
10 PriorityThick Full SDK implementations

Higher priority wins for the same name. The registry API is symmetric across surfaces:

omniavatar.GetLiveProvider("heygen", opts...)     // live.Provider
omniavatar.GetRenderProvider("heygen", opts...)   // render.Provider
omniavatar.ListLiveProviders()                    // []string
omniavatar.ListRenderProviders()                  // []string

Capability Interfaces

Optional provider abilities are modeled as feature-detected interfaces rather than core methods, so the core interface stays at the lowest common denominator:

// Audio hosting (HeyGen, bitHuman — not Tavus)
if up, ok := provider.(render.AudioUploader); ok {
    url, err = up.UploadAudio(ctx, filename, r)
}

Providers that lack a capability simply don't implement the interface; render.ErrAudioUploadUnsupported is available for callers that need a typed error.

Design Documents

The full requirements and technical design for the render surface live in Specs.