{"_id":"@lemonsliceai/avatar","name":"@lemonsliceai/avatar","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@lemonsliceai/avatar","type":"module","version":"0.1.0","description":"Browser helpers for integrating LemonSlice avatars","license":"MIT","author":{"name":"LemonSlice"},"homepage":"https://github.com/lemonsliceai/avatar-js#readme","repository":{"type":"git","url":"git+https://github.com/lemonsliceai/avatar-js.git"},"bugs":{"url":"https://github.com/lemonsliceai/avatar-js/issues"},"keywords":["lemonslice","avatar","livekit","webrtc","react","video","mediastreamtrack"],"sideEffects":false,"publishConfig":{"access":"public"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js"},"./livekit-react":{"types":"./dist/livekit-react.d.ts","import":"./dist/livekit-react.js"},"./package.json":"./package.json"},"peerDependencies":{"@livekit/components-react":">=2.0.0","livekit-client":">=2.0.0","react":">=18.0.0"},"peerDependenciesMeta":{"@livekit/components-react":{"optional":true},"livekit-client":{"optional":true},"react":{"optional":true}},"engines":{"node":">=18"},"scripts":{"build":"tsdown","dev":"tsdown --watch","test":"vitest","typecheck":"tsc --noEmit","release":"bumpp","prepublishOnly":"pnpm run build"},"devDependencies":{"@livekit/components-react":"^2.9.21","@types/node":"^25.6.2","@types/react":"^19.2.17","@typescript/native-preview":"7.0.0-dev.20260509.2","bumpp":"^11.1.0","livekit-client":"^2.20.0","tsdown":"^0.22.0","typescript":"^6.0.3","vitest":"^4.1.5"},"_id":"@lemonsliceai/avatar@0.1.0","gitHead":"b34e005588b3bd368df90f2b6340a1e53fd9f8b3","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-huJkUHAm+qdWrxTsRWA5waCcn4B5/deb5VsWYVKBK2dpfUps8XJvTD48MNeosyCOwKnY6MyGbYTZ+TP5sWUjMQ==","shasum":"e93a417d78c5bd7453a594657609d80671190bf8","tarball":"https://registry.npmjs.org/@lemonsliceai/avatar/-/avatar-0.1.0.tgz","fileCount":17,"unpackedSize":28843,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEWev8qFVuTJEgqwFZ/04Ub7lkc7p8SzGZ/JmMjduU8HAiAXf1D+I2KCV6//tsLsI3ChG/I4JYDXRecsi3u5A/TM7Q=="}]},"_npmUser":{"name":"jp-lemon","email":"josh@lemonslice.com"},"directories":{},"maintainers":[{"name":"aweitz-lemon","email":"andrew@lemonslice.com"},{"name":"jp-lemon","email":"josh@lemonslice.com"},{"name":"bryceatlemonslice","email":"bryce@lemonslice.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/avatar_0.1.0_1782944650964_0.9405937357052223"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T22:24:10.774Z","0.1.0":"2026-07-01T22:24:11.112Z","modified":"2026-07-01T22:24:11.355Z"},"maintainers":[{"name":"aweitz-lemon","email":"andrew@lemonslice.com"},{"name":"jp-lemon","email":"josh@lemonslice.com"},{"name":"bryceatlemonslice","email":"bryce@lemonslice.com"}],"description":"Browser helpers for integrating LemonSlice avatars","homepage":"https://github.com/lemonsliceai/avatar-js#readme","keywords":["lemonslice","avatar","livekit","webrtc","react","video","mediastreamtrack"],"repository":{"type":"git","url":"git+https://github.com/lemonsliceai/avatar-js.git"},"author":{"name":"LemonSlice"},"bugs":{"url":"https://github.com/lemonsliceai/avatar-js/issues"},"license":"MIT","readme":"# @lemonsliceai/avatar\n\nBrowser helpers for integrating [LemonSlice](https://lemonslice.com/) avatars.\n\nThe package centers on **avatar readiness** — knowing the exact moment an avatar\nhas rendered its first frame and is ready to display, so you can hide loaders,\nfade the avatar in, or kick off the rest of your UX at the right time.\n\nIt ships three entry points so you can pick the one that matches your stack:\n\n| Import | Use when |\n| --- | --- |\n| `@lemonsliceai/avatar/livekit-react` | You use LiveKit + React (recommended) |\n| `@lemonsliceai/avatar/react` | You use React with a `MediaStreamTrack` from any source |\n| `@lemonsliceai/avatar` | Plain JavaScript / TypeScript, no framework |\n\n## Installation\n\n```bash\nnpm install @lemonsliceai/avatar\n```\n\n`react`, `@livekit/components-react`, and `livekit-client` are **optional peer\ndependencies** — install only the ones your chosen entry point needs. The plain\nJS entry (`@lemonsliceai/avatar`) has no peer dependencies.\n\n---\n\n## LiveKit + React (recommended)\n\n`<LiveKitAvatarReadyWatcher>` is a renderless component that you drop inside your\n`<LiveKitRoom>` tree. It automatically finds the LemonSlice avatar's video track\nand fires `onReady` when the avatar is streaming A/V:\n\n```tsx\nimport { useState } from \"react\";\nimport { LiveKitRoom } from \"@livekit/components-react\";\nimport { LiveKitAvatarReadyWatcher } from \"@lemonsliceai/avatar/livekit-react\";\n\nfunction AvatarStage({ serverUrl, token }: { serverUrl: string; token: string }) {\n  const [ready, setReady] = useState(false);\n\n  return (\n    <LiveKitRoom serverUrl={serverUrl} token={token} connect>\n      <LiveKitAvatarReadyWatcher onReady={() => setReady(true)} />\n\n      {!ready && <LoadingSpinner />}\n      <div style={{ opacity: ready ? 1 : 0, transition: \"opacity 200ms\" }}>\n        {/* your <VideoTrack /> / avatar UI */}\n      </div>\n    </LiveKitRoom>\n  );\n}\n```\n\n### Props\n\n| Prop | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onReady` | `() => void` | — | Called exactly once when the avatar is ready. |\n| `frameThreshold` | `number` | `1` | Number of decoded frames to wait for. |\n| `botReadyFallbackDelayMs` | `number` | `5000` | Delay after a `bot_ready` RPC before firing as a fallback. |\n| `avatarParticipantIdentity` | `string` | `\"lemonslice-avatar-agent\"` | LiveKit identity that publishes the avatar track. |\n\n---\n\n## React (any track source)\n\nIf you already have the avatar's `MediaStreamTrack` (from Daily, WebRTC, or\nanywhere else), `useAvatarReady` is a hook that fires `onReady` once the track\nhas rendered its first frame(s). It tears down automatically on unmount, when\nthe track changes, or when `enabled` is `false`.\n\n```tsx\nimport { useState } from \"react\";\nimport { useAvatarReady } from \"@lemonsliceai/avatar/react\";\n\nfunction Avatar({ track }: { track: MediaStreamTrack | null }) {\n  const [ready, setReady] = useState(false);\n\n  useAvatarReady(track, {\n    onReady: () => setReady(true),\n  });\n\n  return ready ? <AvatarVideo track={track} /> : <LoadingSpinner />;\n}\n```\n\nPass `null` (or `enabled: false`) while you don't yet have a track — the hook\nsimply stays idle until a track is available.\n\n### Options\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onReady` | `() => void` | — | Called once when the avatar is ready. |\n| `frameThreshold` | `number` | `1` | Number of decoded frames to wait for. |\n| `enabled` | `boolean` | `true` | Set `false` to pause watching without unmounting. |\n\n---\n\n## Plain JavaScript / TypeScript\n\n`waitForAvatarReady` is the framework-agnostic core. Give it a video\n`MediaStreamTrack` and it calls `onReady` once the first frame(s) decode. It\nreturns a `dispose` function for cleanup.\n\n```ts\nimport { waitForAvatarReady } from \"@lemonsliceai/avatar\";\n\nconst stop = waitForAvatarReady(videoTrack, {\n  onReady: () => {\n    console.log(\"avatar is ready — first frame rendered\");\n    showAvatar();\n  },\n});\n\n// Cancel early if you navigate away before it fires:\nstop();\n```\n\nYou can also observe individual frames as they arrive:\n\n```ts\nconst stop = waitForAvatarReady(videoTrack, {\n  frameThreshold: 3, // wait for 3 decoded frames\n  onFrame: (frameIndex) => console.log(`frame ${frameIndex} decoded`),\n  onReady: () => showAvatar(),\n});\n```\n\nIt is safe to call in non-browser environments (e.g. SSR): when `document` is\nunavailable it returns a no-op `dispose` and does nothing.\n\n### Options\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `onReady` | `() => void` | — | Called once when `frameThreshold` frames have decoded. |\n| `frameThreshold` | `number` | `1` | Number of decoded frames to wait for. |\n| `onFrame` | `(frameIndex: number) => void` | — | Optional per-frame hook (`frameIndex` starts at 1). |\n\n---\n\n## Browser support\n\nReadiness detection relies on the browser's\n[`HTMLVideoElement.requestVideoFrameCallback()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestVideoFrameCallback#specifications)\nAPI to observe decoded video frames. This is supported across all modern\nbrowsers ([Baseline](https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement/requestVideoFrameCallback#specifications)\nsince 2024) but may be unavailable in older browsers. Where it is\nunsupported, the frame-based signal will not fire. In that case, fall back to\nthe LemonSlice `bot_ready` WebRTC message, as done by `LiveKitAvatarReadyWatcher`.\n","readmeFilename":"README.md","_rev":"1-42089cc6257c2f590891c8c05c9f12c6"}