### Install Speech SDK
Source: https://developer.openhuman.ai/tts-lipsync-integration
Install the Microsoft Cognitive Services Speech SDK for Node.js.
```bash
npm install microsoft-cognitiveservices-speech-sdk
```
--------------------------------
### Example Animation Clip Structure
Source: https://developer.openhuman.ai/loading-custom-characters
Illustrates the expected directory structure for supplying animation clips. Each .glb file should contain a single animation track.
```bash
animations/
├── idle.glb
├── talk_neutral.glb
├── talk_happy.glb
├── gesture_wave.glb
└── blink.glb
```
--------------------------------
### Install OpenHuman SDK
Source: https://developer.openhuman.ai/
Install the OpenHuman SDK package using npm. This package includes all necessary components for the engine.
```bash
npm install @openhuman/sdk
```
--------------------------------
### Full Example: Azure TTS End-to-End Lipsync
Source: https://developer.openhuman.ai/tts-lipsync-integration
This snippet shows the complete setup for Azure TTS and OpenHuman SDK integration. It initializes the character, configures Azure TTS, and provides a 'speak' function that handles audio playback and viseme scheduling for lip-sync.
```typescript
import * as sdk from "microsoft-cognitiveservices-speech-sdk"
import { OpenHuman } from "@openhuman/sdk"
async function setup() {
// 1. Init character
const human = new OpenHuman({ canvas: document.getElementById("canvas") })
await human.loadCharacter("./character.ohb")
human.setParam("isTalking", false)
human.setBlink({ enabled: true })
// 2. Azure TTS setup
const speechConfig = sdk.SpeechConfig.fromSubscription(KEY, REGION)
speechConfig.speechSynthesisVoiceName = "en-US-JennyNeural"
const synthesizer = new sdk.SpeechSynthesizer(speechConfig, null)
// 3. Speak function
async function speak(text) {
return new Promise((resolve, reject) => {
const visemes = []
const audioCtx = new AudioContext()
synthesizer.visemeReceived = (s, e) => {
visemes.push({
audioOffsetMs: e.audioOffset / 10000,
visemeId: e.visemeId,
})
}
synthesizer.speakTextAsync(
text,
async (result) => {
human.setParam("isTalking", true)
const buffer = await audioCtx.decodeAudioData(result.audioData)
const source = audioCtx.createBufferSource()
source.buffer = buffer
source.connect(audioCtx.destination)
const startTime = audioCtx.currentTime + 0.1 // 100ms lead time
visemes.forEach(({ audioOffsetMs, visemeId }) => {
human.scheduleViseme({
visemeId,
triggerAt: startTime + audioOffsetMs / 1000,
audioContext: audioCtx,
})
})
source.start(startTime)
source.onended = () => {
human.setParam("isTalking", false)
resolve()
}
},
reject
)
})
}
return { human, speak }
}
// Usage
const { human, speak } = await setup()
await speak("Hello! I am Sarah, your AI assistant.")
await speak("How can I help you today?")
```
--------------------------------
### Create and Play a Simple Animation Graph
Source: https://developer.openhuman.ai/animation-graph
This snippet demonstrates the basic setup of an Animation Graph with two states and parameter-driven transitions. It shows how to add states, define transitions, attach the graph to a character, and trigger transitions at runtime.
```javascript
import { OpenHuman, AnimationGraph } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
// 1. Create the graph
const graph = new AnimationGraph()
// 2. Add states
graph.addState("idle", human.animation.getClip("idle"))
graph.addState("talk", human.animation.getClip("talk_neutral"))
// 3. Add transitions
graph.addTransition("idle", "talk", { bool: "isTalking", value: true, duration: 0.3 })
graph.addTransition("talk", "idle", { bool: "isTalking", value: false, duration: 0.5 })
// 4. Attach to the character and start
human.animation.setGraph(graph)
human.animation.play("idle")
// 5. Trigger transitions at runtime
document.getElementById("btn-talk").addEventListener("click", () => {
graph.setBool("isTalking", true)
})
```
--------------------------------
### Complete Expressive Conversational Avatar Example
Source: https://developer.openhuman.ai/facial-blendshapes
A comprehensive example demonstrating autonomous blinking via an Animation Graph, emotion state management using morph presets, phoneme-driven lip sync, and reactive micro-expressions. This snippet shows the integration of various facial animation techniques.
```javascript
import { OpenHuman, VisemeMapper } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
const mapper = new VisemeMapper()
// ── Autonomous blink (via Animation Graph layer) ──────────────
human.animation.setLayer("blink", human.animation.getClip("blink_idle"), {
additive: true,
mask: "facial",
loop: true,
})
// ── Emotion state ─────────────────────────────────────────────
let currentEmotion = "neutral"
async function setEmotion(name, intensity = 1.0) {
currentEmotion = name
await human.morph.applyPreset(name, intensity, { fadeDuration: 0.35 })
}
// ── Lip sync (phoneme-driven) ─────────────────────────────────
let lipSyncActive = false
function startSpeech(emotion = "happy", intensity = 0.6) {
lipSyncActive = true
setEmotion(emotion, intensity)
}
function onPhoneme(phoneme, intensity) {
if (!lipSyncActive) return
const weights = mapper.toFACS(phoneme, intensity)
human.morph.setMany(weights)
}
function stopSpeech() {
lipSyncActive = false
// Fade mouth back to emotion preset, keep brows / cheeks
human.morph.animateTo({ jawOpen: 0, mouthFunnel: 0, mouthPucker: 0, tongueOut: 0 }, { duration: 0.15 })
}
// ── Reactive micro-expressions ────────────────────────────────
function raiseBrow(side = "both", amount = 0.4) {
if (side === "left" || side === "both") human.morph.set("browOuterUpLeft", amount)
if (side === "right" || side === "both") human.morph.set("browOuterUpRight", amount)
setTimeout(() => {
human.morph.animateTo({ browOuterUpLeft: 0, browOuterUpRight: 0 }, { duration: 0.4 })
}, 600)
}
// ── Usage ─────────────────────────────────────────────────────
await setEmotion("neutral")
// Simulate a TTS speech event
startSpeech("happy", 0.7)
onPhoneme("HH", 0.5)
onPhoneme("EH", 0.9)
onPhoneme("L", 0.8)
onPhoneme("OW", 0.7)
stopSpeech()
// React to a user statement
raiseBrow("left", 0.5)
```
--------------------------------
### HTML Canvas Setup
Source: https://developer.openhuman.ai/
Add a canvas element to your HTML file where the OpenHuman engine will render. This example includes basic styling for the canvas.
```html
OpenHuman Demo
```
--------------------------------
### Complete Conversational Avatar Animation Graph
Source: https://developer.openhuman.ai/animation-graph
This example demonstrates setting up a full animation graph for a conversational AI avatar, including idle, talking, gestures, and additive layers for blinking and gazing. It shows how to define states, transitions, and control them at runtime.
```javascript
import { OpenHuman, AnimationGraph, BlendTree1D, BlendTree2D } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
const graph = new AnimationGraph()
// ── Base layer ────────────────────────────────────────────────
// Idle state
graph.addState("idle", human.animation.getClip("idle"), { loop: true })
// Talk state - 1D blend across emotion
const talkTree = new BlendTree1D("emotionHappy")
talkTree.addClip(0.0, human.animation.getClip("talk_neutral"))
talkTree.addClip(1.0, human.animation.getClip("talk_happy"))
graph.addState("talk", talkTree, { loop: true })
// Gesture states (one-shot)
graph.addState("wave", human.animation.getClip("gesture_wave"), { loop: false, exitTime: 2.1 })
graph.addState("nod", human.animation.getClip("gesture_nod"), { loop: false, exitTime: 1.2 })
// ── Transitions ────────────────────────────────────────────────
graph.addTransition("idle", "talk", { bool: "isTalking", value: true, duration: 0.3 })
graph.addTransition("talk", "idle", { bool: "isTalking", value: false, duration: 0.5 })
graph.addTransition("idle", "wave", { trigger: "doWave", duration: 0.2 })
graph.addTransition("talk", "nod", { trigger: "doNod", duration: 0.15 })
graph.addTransition("wave", "idle", { onEnd: true, duration: 0.3 })
graph.addTransition("nod", "talk", { onEnd: true, duration: 0.2 })
// ── Additive layers ────────────────────────────────────────────
// Blink - runs continuously, independent of body state
human.animation.setLayer("blink", human.animation.getClip("blink_idle"), {
additive: true,
mask: "facial",
loop: true,
})
// Gaze - 2D blend tree driven by lookX / lookY
const gazeTree = new BlendTree2D("lookX", "lookY")
gazeTree.addClip([0, 0], human.animation.getClip("look_center"))
gazeTree.addClip([-1, 0], human.animation.getClip("look_left"))
gazeTree.addClip([1, 0], human.animation.getClip("look_right"))
gazeTree.addClip([0, 1], human.animation.getClip("look_up"))
gazeTree.addClip([0, -1], human.animation.getClip("look_down"))
human.animation.setLayer("gaze", gazeTree, {
additive: true,
mask: "facial",
weight: 0.6,
})
// ── Start ──────────────────────────────────────────────────────
human.animation.setGraph(graph)
human.animation.play("idle")
// ── Runtime control ────────────────────────────────────────────
// When AI starts speaking
function onSpeechStart(emotion = 0.5) {
graph.setBool("isTalking", true)
graph.setFloat("emotionHappy", emotion)
}
// When AI stops speaking
function onSpeechEnd() {
graph.setBool("isTalking", false)
}
// Trigger a gesture mid-conversation
function triggerNod() {
graph.trigger("doNod")
}
function triggerWave() {
graph.trigger("doWave")
}
// Animate gaze toward mouse position (normalised -1..1)
canvas.addEventListener("mousemove", (e) => {
const x = (e.clientX / canvas.clientWidth - 0.5) * 2
const y = -(e.clientY / canvas.clientHeight - 0.5) * 2
graph.setFloat("lookX", x * 0.6)
graph.setFloat("lookY", y * 0.4)
})
```
--------------------------------
### Additive Blending Example
Source: https://developer.openhuman.ai/facial-blendshapes
Demonstrates how to use additive blending for animations driven by the Animation Graph, allowing independent control of morph targets via the `human.morph` API.
```APIDOC
## Blending with the Animation Graph
Morph weights set via `human.morph` compose **additively** with weights driven by the Animation Graph's facial layer. The final weight for each target is calculated as:
```
finalWeight = clamp(graphWeight + morphAPIWeight, 0.0, 1.0)
```
This allows for simultaneous control, such as procedural blinks from the graph and lip sync from the morph API.
### Example Usage
```javascript
// Graph drives blink loop autonomously
human.animation.setLayer("blink", blinkClip, {
additive: true,
mask: "facial",
loop: true,
})
// Morph API drives lip sync independently
function onFacsFrame(weights) {
human.morph.setFromArray(weights) // blink weights from graph still apply on top
}
// To exclude specific targets from the facial layer mask:
// graph.excludeFromMask('facial', ['jawOpen'])
```
```
--------------------------------
### Basic iframe Setup
Source: https://developer.openhuman.ai/embed-api-reference
Embed the OpenHuman character component in an iframe. Configure the source URL with the desired character asset and quality settings.
```html
```
--------------------------------
### Get a Reference to the Open Human Element
Source: https://developer.openhuman.ai/embed-api-reference
Select the element and wait for its engine to be fully initialized before interacting with it.
```javascript
const el = document.querySelector("open-human")
// Wait for the engine to be fully initialised
await el.ready // Promise - resolves when character is loaded and first frame is rendered
```
--------------------------------
### Additive Animation Layer Example
Source: https://developer.openhuman.ai/facial-blendshapes
Demonstrates how to run a procedural animation (like a blink) via an Animation Graph layer while simultaneously driving lip sync using the morph API. The graph drives the blink autonomously, and the morph API drives lip sync independently.
```javascript
human.animation.setLayer("blink", blinkClip, {
additive: true,
mask: "facial",
loop: true,
})
function onFacsFrame(weights) {
human.morph.setFromArray(weights) // blink weights from graph still apply on top
}
```
--------------------------------
### AI TTS Avatar Integration Example
Source: https://developer.openhuman.ai/streaming-protocol
Connects an AI speech backend to a live avatar for real-time lip-sync and animation. Requires loading a character, initializing a StreamingClient, and wiring UI events to trigger speech synthesis.
```javascript
import { OpenHuman, StreamingClient } from "@openhuman/sdk"
// 1. Load character
const human = await OpenHuman.load("character.ohb", canvas)
human.animation.play("idle")
// 2. Create lip sync streaming client
const lipsync = new StreamingClient({
transport: "websocket",
url: "wss://tts-backend.example.com/lipsync",
mode: "facs",
jitterBuffer: 80,
smoothing: 0.7,
reconnect: true,
})
lipsync.attach(human)
// 3. Wire UI → TTS → stream
const input = document.getElementById("user-input")
const btn = document.getElementById("send-btn")
btn.addEventListener("click", async () => {
const text = input.value.trim()
if (!text) return
// Transition to talking state
human.animation.crossFadeTo("talk", 0.3)
human.morph.applyPreset("neutral")
// POST to TTS backend - it begins streaming FACS over WebSocket
await fetch("https://tts-backend.example.com/speak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
})
})
// 4. Return to idle when speech ends
lipsync.on("speechEnd", () => {
human.animation.crossFadeTo("idle", 0.5)
human.morph.animateTo({}, { duration: 0.3 })
})
// 5. Diagnostics
lipsync.on("bufferUnderrun", () => {
console.warn("Lip sync buffer underrun - consider increasing jitterBuffer")
})
setInterval(() => {
const s = lipsync.getStats()
console.log(`Buffer: ${s.bufferDepth}ms | Latency: ${s.estimatedLatency}ms | FPS: ${s.fps}`)
}, 2000)
await lipsync.connect()
```
--------------------------------
### Initialize OpenHuman Engine
Source: https://developer.openhuman.ai/
Initialize the OpenHuman engine in your main JavaScript file, providing the canvas element and desired quality settings. Load a character bundle and start the render loop.
```javascript
import { OpenHuman } from "@openhuman/sdk"
const canvas = document.getElementById("oh-canvas")
const human = new OpenHuman({
canvas,
quality: "high", // 'high' | 'medium' | 'low'
fps: 60,
})
// Load a digital human character bundle (.ohb)
await human.loadCharacter("./characters/default.ohb")
// Start the render loop
human.play("idle")
```
--------------------------------
### Handle GPU Out of Memory Errors
Source: https://developer.openhuman.ai/error-code-reference
This example shows how to handle `ASSET_GPU_OOM` errors by destroying a lower-priority character instance and retrying the load. It utilizes the `human.on('error', ...)` event listener.
```typescript
human.on("error", async (err) => {
if (err.code === "ASSET_GPU_OOM") {
// Destroy lowest-priority character and retry
await lowPriorityHuman.destroy()
await human.loadCharacter("./character.ohb")
}
})
```
--------------------------------
### NDJSON Animation Frame Example
Source: https://developer.openhuman.ai/streaming-protocol
Example of newline-delimited JSON (NDJSON) format for animation frames. Each chunk is a UTF-8 JSON object terminated by a newline.
```json
{"t":1.033,"facs":[0,0,0,0,0,0,0,0,0.41,0,0,0,0,0,0,0,0,0,0,0,0,0,0.61,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
{"t":1.066,"facs":[0,0,0,0,0,0,0,0,0.55,0,0,0,0,0,0,0,0,0,0,0,0,0,0.71,0,0.3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
```
--------------------------------
### Configure 3-Point Lighting
Source: https://developer.openhuman.ai/render-pipeline
Sets up a fixed 3-point lighting model (key, fill, rim) using SDK configuration. Each light contributes diffuse and specular components.
```javascript
human.setLighting({
key: { direction: [-1, -1, -1], color: "#fff5e6", intensity: 1.0 },
fill: { direction: [1, -0.5, -1], color: "#e6f0ff", intensity: 0.4 },
rim: { direction: [0, 0, 1], color: "#ffffff", intensity: 0.6 },
})
```
--------------------------------
### Configure One-Shot Gestures with Auto-Return
Source: https://developer.openhuman.ai/animation-graph
Shows how to set up one-shot gestures like 'wave' that automatically transition back to a previous state upon completion. This is achieved by setting `loop: false` and `exitTime` to the clip's duration.
```javascript
// One-shot gesture that auto-returns to idle
graph.addState("wave", human.animation.getClip("gesture_wave"), {
loop: false,
exitTime: 2.1, // clip is 2.1s long
})
graph.addTransition("idle", "wave", { trigger: "doWave", duration: 0.2 })
graph.addTransition("wave", "idle", { onEnd: true, duration: 0.3 })
```
--------------------------------
### Configure Loading and Environment Attributes
Source: https://developer.openhuman.ai/embed-api-reference
Set the poster image, loading behavior, background color, and environment preset for the component.
```html
```
```html
```
--------------------------------
### Configure Real-time Streaming
Source: https://developer.openhuman.ai/embed-api-reference
Set up the WebSocket or HTTP stream URL for real-time lip-sync or FACS data. Adjust smoothing and buffer settings for optimal performance.
```html
```
--------------------------------
### Set Eyes for Looking Left
Source: https://developer.openhuman.ai/facial-blendshapes
Adjusts eye look blendshapes to simulate the character looking to the left. This example requires the 'human' object to be loaded.
```javascript
// Look left
human.morph.setMany({
eyeLookOutLeft: 0.6,
eyeLookInRight: 0.6,
})
```
--------------------------------
### Basic Streaming Client Configuration
Source: https://developer.openhuman.ai/streaming-protocol
Configure the StreamingClient with transport, URL, and buffer settings. Adjust jitterBuffer for network latency and maxBuffer to prevent memory growth.
```javascript
const client = new StreamingClient({
transport: "websocket",
url: "wss://...",
jitterBuffer: 80, // target buffer depth in ms (default: 80)
maxBuffer: 300, // drop frames older than this ms (default: 300)
extrapolate: true, // predict pose on buffer underrun (default: true)
})
```
--------------------------------
### Initialize OpenHuman SDK
Source: https://developer.openhuman.ai/
Instantiate the OpenHuman SDK with various configuration options. Ensure a valid HTMLCanvasElement is provided.
```javascript
new OpenHuman({
canvas,
quality,
fps,
shadows,
postProcess,
sss,
})
```
--------------------------------
### Initialize OpenHuman and Speech Synthesizer
Source: https://developer.openhuman.ai/tts-lipsync-integration
Initialize the OpenHuman SDK and configure the Azure Speech Synthesizer with your subscription key and region.
```typescript
import * as sdk from "microsoft-cognitiveservices-speech-sdk"
import { OpenHuman } from "@openhuman/sdk"
const human = new OpenHuman({ canvas })
await human.loadCharacter("./character.ohb")
const speechConfig = sdk.SpeechConfig.fromSubscription("YOUR_AZURE_KEY", "YOUR_AZURE_REGION")
speechConfig.speechSynthesisVoiceName = "en-US-JennyNeural"
const synthesizer = new sdk.SpeechSynthesizer(speechConfig)
```
--------------------------------
### Embed OpenHuman as a Web Component
Source: https://developer.openhuman.ai/
Embed the OpenHuman engine into your HTML using the `` custom element for a zero-configuration setup. This component supports various attributes for customization.
```html
```
--------------------------------
### Listen to AnimationGraph Events
Source: https://developer.openhuman.ai/animation-graph
Attach event listeners to the `human` instance to react to animation graph lifecycle events such as state changes, transition starts, and animation clip endings.
```javascript
human.on("stateEnter", ({ state }) => console.log("Entered state:", state))
human.on("stateExit", ({ state }) => console.log("Exited state:", state))
human.on("animationEnd", ({ clip }) => console.log("Clip ended:", clip))
human.on("transitionStart", ({ from, to, duration }) => {
console.log(`Crossfading ${from} → ${to} over ${duration}s`)
})
```
--------------------------------
### Configure Playback and Animation
Source: https://developer.openhuman.ai/embed-api-reference
Set the default animation, enable/disable autoplay and looping, and adjust playback speed.
```html
```
--------------------------------
### ParrotSpeech IPA Phoneme Timestamps Integration
Source: https://developer.openhuman.ai/tts-lipsync-integration
Integrate ParrotSpeech to obtain native IPA phoneme timestamps for high-accuracy lip sync. Requires ParrotSpeech API key and client setup.
```typescript
import { ParrotSpeechClient } from "@parrotspeech/client"
const parrot = new ParrotSpeechClient({ apiKey: "YOUR_KEY" })
const result = await parrot.synthesize({
text: "Hello, how can I help you today?",
voice: "nova_en_f",
format: "wav",
phonemes: true, // request IPA phoneme timestamps
})
// result.phonemes: [{ ipa: 'h', startMs: 0, endMs: 60 }, ...]
// result.audioBuffer: ArrayBuffer
result.phonemes.forEach(({ ipa, startMs, endMs }) => {
human.scheduleVisemeByIPA({
ipa,
startMs,
endMs,
audioContext: ctx,
audioStartTime: ctx.currentTime,
})
})
playAudio(ctx, result.audioBuffer)
```
--------------------------------
### Add Animation States with Options
Source: https://developer.openhuman.ai/animation-graph
Demonstrates how to register animation clips as states, including options for looping, playback speed, and start/exit times. Useful for configuring different animation behaviors.
```javascript
graph.addState("idle", human.animation.getClip("idle"))
graph.addState("talk", human.animation.getClip("talk_neutral"))
graph.addState("wave", human.animation.getClip("gesture_wave"))
graph.addState("blink", human.animation.getClip("blink"), { loop: false })
graph.addState("lookLeft", human.animation.getClip("look_left"), { loop: true, speed: 0.8 })
```
--------------------------------
### VisemeScheduler API: scheduleVisemeByIPA
Source: https://developer.openhuman.ai/tts-lipsync-integration
Schedule a viseme event using an IPA phoneme string. This is the most accurate method when working with IPA data from TTS services. Requires an AudioContext and audio start time.
```javascript
human.scheduleVisemeByIPA({
ipa: string, // IPA phoneme string
startMs: number, // ms from audio start
endMs: number, // ms from audio start
audioContext: AudioContext,
audioStartTime: number, // AudioContext time when audio started
})
```
--------------------------------
### Verify WebGL 2.0 Support and Handle Errors
Source: https://developer.openhuman.ai/
Listen for the 'ready' event to confirm WebGL version and extensions. Implement error handling for unsupported browsers, such as falling back to a lower quality setting.
```javascript
human.on("ready", ({ webglVersion, extensions }) => {
console.log(`Running on WebGL ${webglVersion}`)
console.log("Loaded extensions:", extensions)
})
human.on("error", (err) => {
if (err.code === "WEBGL2_NOT_SUPPORTED") {
// Show fallback UI or downgrade to WebGL 1.0 reduced-quality mode
console.warn("WebGL 2.0 not available, falling back to reduced quality.")
human.setQuality("low")
}
})
```
--------------------------------
### StreamingClient Options
Source: https://developer.openhuman.ai/streaming-protocol
Configuration options for initializing the StreamingClient to control transport, buffering, smoothing, and reconnection.
```APIDOC
## `StreamingClient` options
### Description
Configuration options for initializing the `StreamingClient` to control transport, buffering, smoothing, and reconnection.
### Parameters
#### `transport`
* Type: `'websocket' | 'http'`
* Default: `'websocket'`
* Description: Network transport protocol.
#### `url`
* Type: `string`
* Description: The `wss://` or `https://` endpoint for the streaming service. This is a required parameter.
#### `format`
* Type: `'binary' | 'ndjson'`
* Default: `'binary'` for WebSocket, `'ndjson'` for HTTP.
* Description: Specifies the frame encoding format.
#### `mode`
* Type: `'full' | 'facs'`
* Default: `'full'`
* Description: Sets the processing mode. `'facs'` can be used for lip-sync-only streams, skipping joint processing.
#### `jitterBuffer`
* Type: `number`
* Default: `80`
* Description: The depth of the jitter buffer in milliseconds. Controls how much latency is introduced to smooth out network jitter.
#### `maxBuffer`
* Type: `number`
* Default: `300`
* Description: The maximum age of a frame in milliseconds before it is discarded.
#### `extrapolate`
* Type: `boolean`
* Default: `true`
* Description: Enables pose prediction when the buffer underruns.
#### `smoothing`
* Type: `number`
* Default: `0.7`
* Description: Exponential Moving Average (EMA) alpha for smoothing FACS weights. `0` means no smoothing, `1` means frozen.
#### `reconnect`
* Type: `boolean`
* Default: `true`
* Description: Automatically attempts to reconnect if the connection is lost.
#### `reconnectDelay`
* Type: `number`
* Default: `1000`
* Description: The delay in milliseconds between reconnection attempts.
```
--------------------------------
### Asset Loading Data Flow
Source: https://developer.openhuman.ai/architecture-overview
Illustrates the process of parsing .ohb files, uploading assets to the GPU, and preparing a character instance for rendering.
```mermaid
flowchart TD
A[".ohb file on disk / CDN"] --> B["BundleParser"]
B --> B1["Header
version, chunk count, flags"]
B --> B2["Chunk[0]
glTF mesh (binary)"]
B --> B3["Chunk[1]
KTX2 textures
(albedo, normal, ORM, emissive)"]
B --> B4["Chunk[2]
Skeleton
(joint hierarchy + bind pose)"]
B --> B5["Chunk[3]
Morph targets
(52 FACS delta buffers)"]
B --> B6["Chunk[4]
Animation clips
(idle, talk, blink, ...)"]
B --> C["GPU Upload
(async, chunked to avoid frame drops)"]
C --> C1["WebGLBuffer
vertex / index data"]
C --> C2["WebGLTexture
KTX2 compressed textures"]
C --> C3["Float32Array
morph target deltas
(kept in JS heap)"]
C --> D["CharacterInstance
(ready to render)"]
```
--------------------------------
### Create and Configure 1D Blend Tree
Source: https://developer.openhuman.ai/animation-graph
Create a 1D blend tree using `BlendTree1D` to smoothly blend between multiple clips based on a single float parameter. Add clips with their corresponding parameter values.
```javascript
import { BlendTree1D } from "@openhuman/sdk"
const emotionTree = new BlendTree1D("emotionHappy")
emotionTree.addClip(0.0, human.animation.getClip("talk_neutral"))
emotionTree.addClip(0.5, human.animation.getClip("talk_happy_light"))
emotionTree.addClip(1.0, human.animation.getClip("talk_happy_full"))
graph.addState("talk", emotionTree)
// At runtime: smoothly blend across emotion intensity
graph.setFloat("emotionHappy", 0.75)
```
--------------------------------
### Streaming Client Event Handlers
Source: https://developer.openhuman.ai/streaming-protocol
Set up event handlers for connection status, frame reception, and buffer events. These callbacks allow you to react to stream lifecycle changes and data availability.
```javascript
client.on("connected", () => console.log("Stream connected"))
client.on("disconnected", ({ code, reason }) => console.warn("Stream dropped:", reason))
client.on("reconnecting", ({ attempt }) => console.log("Reconnect attempt", attempt))
client.on("frame", (pose) => {
// pose: { timestamp, joints?: Float32Array, facs?: Float32Array }
// Fired after jitter buffer output - before GPU upload
})
client.on("bufferUnderrun", () => console.warn("Jitter buffer ran dry"))
client.on("bufferOverflow", () => console.warn("Buffer full - frames dropped"))
```
--------------------------------
### Manual Pose Application with Pre-processing
Source: https://developer.openhuman.ai/streaming-protocol
Handle pose data manually by skipping client.attach() and processing frames within the 'frame' event. This allows for pre-processing like retargeting or blending before applying to the human model.
```javascript
// Do NOT call client.attach(human)
client.on("frame", ({ joints, facs }) => {
if (joints) {
// Retarget from a different skeleton before applying
const retargeted = myRetargeter.apply(joints)
human.skeleton.setPose(retargeted)
}
if (facs) {
// Blend 50/50 with a local procedural expression
const blended = facs.map((w, i) => w * 0.5 + localFacs[i] * 0.5)
human.morph.setFromArray(blended)
}
})
await client.connect()
```
--------------------------------
### Listening for SDK Errors
Source: https://developer.openhuman.ai/error-code-reference
Demonstrates how to globally handle errors using `human.on('error')` and how to catch asynchronous errors from SDK methods like `loadCharacter`.
```typescript
// Global error handler
human.on("error", (err: OpenHumanError) => {
console.error(`[${err.category}] ${err.code}: ${err.message}`)
if (!err.recoverable) {
// show fallback UI
}
})
// Async errors from loadCharacter, connectStream, etc.
try {
await human.loadCharacter("./character.ohb")
} catch (err) {
if (err instanceof OpenHumanError) {
handleError(err)
}
}
```
--------------------------------
### Create and Configure 2D Blend Tree
Source: https://developer.openhuman.ai/animation-graph
Create a 2D blend tree using `BlendTree2D` to blend between clips based on two float parameters. Add clips with their corresponding 2D coordinate values.
```javascript
import { BlendTree2D } from "@openhuman/sdk"
const gazeTree = new BlendTree2D("lookX", "lookY")
gazeTree.addClip([0, 0], human.animation.getClip("look_center"))
gazeTree.addClip([-1, 0], human.animation.getClip("look_left"))
gazeTree.addClip([1, 0], human.animation.getClip("look_right"))
gazeTree.addClip([0, 1], human.animation.getClip("look_up"))
gazeTree.addClip([0, -1], human.animation.getClip("look_down"))
human.animation.setLayer("gaze", gazeTree, { additive: true, mask: "facial" })
// Animate gaze toward a target
graph.setFloat("lookX", 0.4)
graph.setFloat("lookY", -0.2)
```
--------------------------------
### Check for WebGL 2.0 Support Before Initialization
Source: https://developer.openhuman.ai/error-code-reference
This code snippet checks if the browser supports WebGL 2.0 by attempting to create a context. It's crucial for preventing `WEBGL2_NOT_SUPPORTED` errors.
```javascript
// Check before initializing
if (!document.createElement("canvas").getContext("webgl2")) {
showFallbackUI("Your browser does not support WebGL 2.0.")
return
}
```
--------------------------------
### Connect to HTTP Chunked Stream with OpenHuman SDK
Source: https://developer.openhuman.ai/streaming-protocol
Connects to an HTTP chunked stream using the OpenHuman SDK. Ensure the 'character.ohb' file is loaded and the server URL is correct.
```javascript
import { OpenHuman, StreamingClient } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
const client = new StreamingClient({
transport: "http",
url: "https://your-server.example.com/animation-stream",
format: "ndjson", // 'ndjson' (default) or 'binary'
})
client.attach(human)
await client.connect()
```
--------------------------------
### Set Rendering Quality and Features
Source: https://developer.openhuman.ai/embed-api-reference
Adjust rendering quality presets, target frame rate, and toggle specific graphical features like shadows and post-processing.
```html
```
--------------------------------
### Enable Streaming Animation
Source: https://developer.openhuman.ai/
Connect to a streaming animation server via WebSocket to apply real-time animation data, such as lip sync or mocap. The `StreamingClient` handles the connection and frame processing.
```javascript
import { OpenHuman, StreamingClient } from "@openhuman/sdk"
const human = new OpenHuman({ canvas })
await human.loadCharacter("./characters/default.ohb")
// Connect to a streaming animation server
const stream = new StreamingClient({
url: "wss://your-server.example.com/animation-stream",
jitterBuffer: 80, // ms - smooths latency spikes
})
stream.on("frame", (pose) => {
human.applyPose(pose)
})
stream.connect()
```
--------------------------------
### Load Character and Set Morph Weights
Source: https://developer.openhuman.ai/facial-blendshapes
Loads a character and demonstrates setting individual and multiple morph target weights. Use this for basic facial animation control.
```javascript
import { OpenHuman } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
// Set a single morph target weight (0.0 – 1.0)
human.morph.set("mouthSmileLeft", 0.7)
human.morph.set("mouthSmileRight", 0.7)
// Set multiple targets in one call
human.morph.setMany({
jawOpen: 0.4,
mouthFunnel: 0.3,
browInnerUp: 0.5,
})
// Read current weight
const w = human.morph.get("jawOpen") // → 0.4
// Reset all targets to 0
human.morph.reset()
```
--------------------------------
### Control Camera Settings
Source: https://developer.openhuman.ai/embed-api-reference
Adjust the camera's position, target, field of view, and enable runtime orbit control.
```javascript
// Set camera position and target
el.setCameraPosition([0, 1.65, 0.8])
el.setCameraTarget([0, 1.55, 0])
el.setFOV(35)
// Enable orbit control at runtime
el.enableOrbit(true)
```
--------------------------------
### Enable Procedural Head Motion
Source: https://developer.openhuman.ai/tts-lipsync-integration
Enables a built-in system for subtle head motion synchronized with speech audio envelope to improve realism.
```javascript
human.setHeadMotion({
enabled: true,
intensity: 0.4, // 0.0–1.0 - scale of motion
noddingRate: 0.3, // nods per second during speech
swayAmplitude: 0.02, // meters - lateral sway
tiltAmplitude: 1.5, // degrees - head tilt range
})
```
--------------------------------
### Node.js WebSocket Server for Pose Streaming
Source: https://developer.openhuman.ai/streaming-protocol
A minimal WebSocket server that streams pose data at 30 fps. Replace placeholder joint and FACS data with actual pose source.
```javascript
import { WebSocketServer } from "ws"
const wss = new WebSocketServer({ port: 8080 })
const JOINT_COUNT = 256
const FACS_COUNT = 52
const FRAME_BYTES = 8 + JOINT_COUNT * 32 + FACS_COUNT * 2
wss.on("connection", (ws) => {
console.log("Client connected")
const interval = setInterval(() => {
const buf = new ArrayBuffer(FRAME_BYTES)
const view = new DataView(buf)
let offset = 0
// Header
view.setFloat32(offset, performance.now() / 1000, true)
offset += 4
view.setUint16(offset, JOINT_COUNT, true)
offset += 2
view.setUint16(offset, FACS_COUNT, true)
offset += 2
// Joint data - replace with your actual pose source
for (let i = 0; i < JOINT_COUNT; i++) {
view.setFloat32(offset, 0, true) // position.x
view.setFloat32(offset + 4, 0, true) // position.y
view.setFloat32(offset + 8, 0, true) // position.z
view.setFloat32(offset + 12, 0, true) // rotation.x
view.setFloat32(offset + 16, 0, true) // rotation.y
view.setFloat32(offset + 20, 0, true) // rotation.z
view.setFloat32(offset + 24, 1, true) // rotation.w (identity)
view.setFloat32(offset + 28, 1, true) // scale
offset += 32
}
// FACS weights - replace with your TTS / phoneme output
for (let i = 0; i < FACS_COUNT; i++) {
const weight = 0.0 // float in range 0.0–1.0
view.setInt16(offset, Math.round(weight * 32767), true)
offset += 2
}
ws.send(buf)
}, 1000 / 30) // 30 fps
ws.on("close", () => clearInterval(interval))
})
console.log("Streaming server on ws://localhost:8080")
```
--------------------------------
### Connect to WebSocket Stream
Source: https://developer.openhuman.ai/streaming-protocol
Establishes a connection to the WebSocket streaming server and attaches it to a loaded OpenHuman character. The client automatically applies incoming poses.
```javascript
import { OpenHuman, StreamingClient } from "@openhuman/sdk"
const human = await OpenHuman.load("character.ohb", canvas)
const client = new StreamingClient({
transport: "websocket",
url: "wss://your-server.example.com/animation-stream",
jitterBuffer: 80, // ms - smooths latency spikes (default: 80)
reconnect: true, // auto-reconnect on drop (default: true)
reconnectDelay: 1000, // ms between reconnect attempts (default: 1000)
})
// Attach to the character - poses are applied automatically each frame
client.attach(human)
// Open the connection
await client.connect()
```
--------------------------------
### Informational Warning: WASM_SIMD_UNSUPPORTED
Source: https://developer.openhuman.ai/error-code-reference
This warning is emitted when the WASM SIMD build is loaded on an unsupported browser. The SDK automatically falls back to the scalar build, resulting in a performance degradation.
```javascript
WASM_SIMD_UNSUPPORTED
```
--------------------------------
### Creating a 1D Blend Tree
Source: https://developer.openhuman.ai/animation-graph
Creates a 1D blend tree that smoothly blends between animation clips based on a single float parameter.
```APIDOC
## 1D Blend Tree - emotion intensity
### Description
Creates and configures a 1D blend tree, driven by a float parameter, to blend between multiple animation clips.
### Method
`BlendTree1D` constructor, `addClip`, `graph.addState`
### Parameters
#### `BlendTree1D(parameterName)`
- **parameterName** (string) - Required - The name of the float parameter that drives this blend tree.
#### `addClip(parameterValue, clip)`
- **parameterValue** (number) - Required - The value of the parameter at which this clip should be fully active.
- **clip** (AnimationClip) - Required - The animation clip to add.
#### `graph.addState(stateName, blendTree)`
- **stateName** (string) - Required - The name of the state to add the blend tree to.
- **blendTree** (BlendTree1D) - Required - The configured 1D blend tree instance.
### Request Example
```javascript
import { BlendTree1D } from "@openhuman/sdk"
const emotionTree = new BlendTree1D("emotionHappy")
emotionTree.addClip(0.0, human.animation.getClip("talk_neutral"))
emotionTree.addClip(0.5, human.animation.getClip("talk_happy_light"))
emotionTree.addClip(1.0, human.animation.getClip("talk_happy_full"))
graph.addState("talk", emotionTree)
// At runtime: smoothly blend across emotion intensity
graph.setFloat("emotionHappy", 0.75)
```
```
--------------------------------
### Listen for Open Human Events
Source: https://developer.openhuman.ai/embed-api-reference
Add event listeners to the element to react to various lifecycle, animation, streaming, and user interaction events.
```javascript
const el = document.querySelector("open-human")
// Engine lifecycle
el.addEventListener("oh:ready", (e) => console.log("Character ready"))
el.addEventListener("oh:error", (e) => console.error("Error:", e.detail.message))
el.addEventListener("oh:loadprogress", (e) => console.log(`${e.detail.stage}: ${e.detail.percent}%`))
// Animation
el.addEventListener("oh:animationstart", (e) => console.log("Started:", e.detail.state))
el.addEventListener("oh:animationend", (e) => console.log("Ended:", e.detail.clip))
el.addEventListener("oh:transition", (e) => console.log(`${e.detail.from} → ${e.detail.to}`))
// Streaming
el.addEventListener("oh:streamconnected", () => console.log("Stream connected"))
el.addEventListener("oh:streamdisconnected", (e) => console.warn("Stream dropped:", e.detail.reason))
el.addEventListener("oh:streamframe", (e) => {
// e.detail: { timestamp, joints?, facs? }
})
// User interaction (requires orbit="true")
el.addEventListener("oh:orbitstart", () => console.log("User started rotating"))
el.addEventListener("oh:orbitend", () => console.log("User stopped rotating"))
```
--------------------------------
### Quick Embed with Script and Component
Source: https://developer.openhuman.ai/embed-api-reference
The minimum required HTML to load the embed script and render a character. The component handles asset loading, rendering, and animation automatically.
```html
```
--------------------------------
### Manage Streaming Connections
Source: https://developer.openhuman.ai/embed-api-reference
Connect to a streaming endpoint for real-time data, disconnect the stream, and retrieve stream statistics.
```javascript
// Connect a stream
await el.connectStream("wss://tts-backend.example.com/lipsync", { mode: "facs" })
// Disconnect
el.disconnectStream()
// Stream stats
const stats = el.getStreamStats()
console.log(stats.bufferDepth, stats.estimatedLatency, stats.fps)
```