@lemonsliceai/avatar
v0.1.0
Published
Browser helpers for integrating LemonSlice avatars
Readme
@lemonsliceai/avatar
Browser helpers for integrating LemonSlice avatars.
The package centers on avatar readiness — knowing the exact moment an avatar has rendered its first frame and is ready to display, so you can hide loaders, fade the avatar in, or kick off the rest of your UX at the right time.
It ships three entry points so you can pick the one that matches your stack:
| Import | Use when |
| --- | --- |
| @lemonsliceai/avatar/livekit-react | You use LiveKit + React (recommended) |
| @lemonsliceai/avatar/react | You use React with a MediaStreamTrack from any source |
| @lemonsliceai/avatar | Plain JavaScript / TypeScript, no framework |
Installation
npm install @lemonsliceai/avatarreact, @livekit/components-react, and livekit-client are optional peer
dependencies — install only the ones your chosen entry point needs. The plain
JS entry (@lemonsliceai/avatar) has no peer dependencies.
LiveKit + React (recommended)
<LiveKitAvatarReadyWatcher> is a renderless component that you drop inside your
<LiveKitRoom> tree. It automatically finds the LemonSlice avatar's video track
and fires onReady when the avatar is streaming A/V:
import { useState } from "react";
import { LiveKitRoom } from "@livekit/components-react";
import { LiveKitAvatarReadyWatcher } from "@lemonsliceai/avatar/livekit-react";
function AvatarStage({ serverUrl, token }: { serverUrl: string; token: string }) {
const [ready, setReady] = useState(false);
return (
<LiveKitRoom serverUrl={serverUrl} token={token} connect>
<LiveKitAvatarReadyWatcher onReady={() => setReady(true)} />
{!ready && <LoadingSpinner />}
<div style={{ opacity: ready ? 1 : 0, transition: "opacity 200ms" }}>
{/* your <VideoTrack /> / avatar UI */}
</div>
</LiveKitRoom>
);
}Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| onReady | () => void | — | Called exactly once when the avatar is ready. |
| frameThreshold | number | 1 | Number of decoded frames to wait for. |
| botReadyFallbackDelayMs | number | 5000 | Delay after a bot_ready RPC before firing as a fallback. |
| avatarParticipantIdentity | string | "lemonslice-avatar-agent" | LiveKit identity that publishes the avatar track. |
React (any track source)
If you already have the avatar's MediaStreamTrack (from Daily, WebRTC, or
anywhere else), useAvatarReady is a hook that fires onReady once the track
has rendered its first frame(s). It tears down automatically on unmount, when
the track changes, or when enabled is false.
import { useState } from "react";
import { useAvatarReady } from "@lemonsliceai/avatar/react";
function Avatar({ track }: { track: MediaStreamTrack | null }) {
const [ready, setReady] = useState(false);
useAvatarReady(track, {
onReady: () => setReady(true),
});
return ready ? <AvatarVideo track={track} /> : <LoadingSpinner />;
}Pass null (or enabled: false) while you don't yet have a track — the hook
simply stays idle until a track is available.
Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| onReady | () => void | — | Called once when the avatar is ready. |
| frameThreshold | number | 1 | Number of decoded frames to wait for. |
| enabled | boolean | true | Set false to pause watching without unmounting. |
Plain JavaScript / TypeScript
waitForAvatarReady is the framework-agnostic core. Give it a video
MediaStreamTrack and it calls onReady once the first frame(s) decode. It
returns a dispose function for cleanup.
import { waitForAvatarReady } from "@lemonsliceai/avatar";
const stop = waitForAvatarReady(videoTrack, {
onReady: () => {
console.log("avatar is ready — first frame rendered");
showAvatar();
},
});
// Cancel early if you navigate away before it fires:
stop();You can also observe individual frames as they arrive:
const stop = waitForAvatarReady(videoTrack, {
frameThreshold: 3, // wait for 3 decoded frames
onFrame: (frameIndex) => console.log(`frame ${frameIndex} decoded`),
onReady: () => showAvatar(),
});It is safe to call in non-browser environments (e.g. SSR): when document is
unavailable it returns a no-op dispose and does nothing.
Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| onReady | () => void | — | Called once when frameThreshold frames have decoded. |
| frameThreshold | number | 1 | Number of decoded frames to wait for. |
| onFrame | (frameIndex: number) => void | — | Optional per-frame hook (frameIndex starts at 1). |
Browser support
Readiness detection relies on the browser's
HTMLVideoElement.requestVideoFrameCallback()
API to observe decoded video frames. This is supported across all modern
browsers (Baseline
since 2024) but may be unavailable in older browsers. Where it is
unsupported, the frame-based signal will not fire. In that case, fall back to
the LemonSlice bot_ready WebRTC message, as done by LiveKitAvatarReadyWatcher.
