# @livo-tv/sdk

Embed Livo **in-DOM** on your origin. Native React is the partner path: `LivoPlayer`, `LivoHostStudio`, and `LivoGuestStudio`. Do not iframe `player.livo.tv`.

```tsx
import { LivoPlayer, applyEmbedTheme, type EmbedTheme } from "@livo-tv/sdk";
import {
	LivoHostStudio,
	LivoGuestStudio,
	StudioWindowGate,
	studioRoomLabels,
} from "@livo-tv/sdk/studio";
import "@livo-tv/sdk/styles.css";

const theme: EmbedTheme = {
	accent: "#0F6FF5",
	background: "#0b1220",
	foreground: "#f8fafc",
	radius: "8px",
	fontFamily: "Inter, sans-serif",
	mode: "dark",
};

<LivoPlayer
	streamId="s1"
	theme={theme}
	onPlaybackState={(state) => {
		/* loading | waiting | playing | paused | ended | error */
	}}
	onError={(error) => {
		/* not_found | playback */
	}}
/>;
```

`@livo-tv/sdk`, `@livo-tv/sdk/studio`, and `@livo-tv/sdk/condo` (`LivoCommunity`) are client entries (the published bundles start with `"use client"`). Import them from `"use client"` files in Next.js App Router — do not import those entries from a Server Component page. Do **not** install `@livo-tv/blocks` or add `transpilePackages` — the SDK bundles its UI internals. Tailwind hosts that restyle the embed can `@source` `node_modules/@livo-tv/sdk/dist/*.{js,cjs,mjs}`.

Root `<LivoStudio>` still iframes `app.livo.tv` and is **not** the partner contract. Native studio has no runtime dependency on `player.livo.tv` (no `postMessage` bridge, no hard-coded player origin). The hosted-player URL helpers (`playerUrl`) stay available for Livo's own player app.

## Native studio (RealtimeKit)

Install:

```text
pnpm add @livo-tv/sdk
# studio only:
pnpm add @cloudflare/realtimekit
```

Peers:

- `react` / `react-dom` (required)
- `@cloudflare/realtimekit` (optional, lazy-imported; studio only)
- `hls.js` (optional, for `<LivoPlayer>` on browsers without native HLS)

Do not add `@livo-tv/blocks` as a host dependency. The published player and studio bundles inline it (and the UI kit). Next.js does not need `transpilePackages`.

Send this header on the route that mounts the studio (camera, mic, and screen share):

```
Permissions-Policy: camera=(self), microphone=(self), display-capture=(self)
```

Wrap the studio in `StudioWindowGate` with a **stable** `lockKey` (stream id or host token), not the partner pathname. The lock uses `BroadcastChannel` and is origin-scoped, so `/dashboard/studio` and `/events/123/studio` on the same origin still see each other.

```tsx
<StudioWindowGate lockKey={`host:${streamId}`} labels={gateLabels}>
	<LivoHostStudio
		token={hostToken}
		apiUrl="https://api.livo.tv"
		labels={studioRoomLabels((key) => key)}
		embed
		theme={theme}
	/>
</StudioWindowGate>
```

`theme` is an `EmbedTheme` object (or a hosted encoded string). `applyEmbedTheme` writes CSS variables onto **the studio container**, not `document.documentElement`, so a partner shell keeps its own chrome.

Omit `playerOrigin` in native embeds. The watch-share control only appears when you pass a player origin; native partners should share their own watch URL.

`endedHref` is the link on the host's ended-session action. When it is omitted or blank, that action goes to `/streams/:id`. Optional `endedExtra` renders under that action. Omit it and the card stays the same.

Regression page: `pnpm example:embed` serves `examples/partner-embed` on `http://localhost:3003`.

Community (condo) — comments on every video, live Q&A only while a stream or webinar is `preview` / `public`. After the event, Q&A stays closed. Comments stay closed unless the organizer sets `commentsAfterEnd`.

**In-player drawers:** native `<LivoPlayer showCommunity>` shows Comments and Q&A as overlay drawers, hidden until the top-right glass buttons. A count badge appears when a feed has more than one item. Hosted iframes are theater-only by default; pass `?community=1` (or `playerUrl(id, { community: true })`) to enable the same drawers. `?community=0` remains a valid hide. Drawers take the full theater when the player is compact, or a right-hand fraction when it is large. Hide closes the whole drawer.

**Custom layout:** leave `showCommunity` off (and omit `?community=1`) so in-player buttons stay hidden, then mount `<LivoCommunity>` (optional `type="comment"` / `type="question"`) beside the theater, or call `createCommunityClient` / the public HTTP API. Pass a public `apiUrl` for viewers (`mode="viewer"`, polls). Pass JWT / `X-Api-Key` headers and `mode="control"` for settings, pin/hide/delete, and Q&A highlight/dismiss/answer. Unregistered posters send `displayName`, optional `picture` URL, and `guestId`. Supply `displayName` (and optional `picture`) to skip the name field; otherwise the composer asks once and stores `livo-community-identity`. Hosted iframes use `?name=` / `?avatar=` when the built-in drawers are on.

```tsx
import { LivoPlayer } from "@livo-tv/sdk";
import { LivoCommunity } from "@livo-tv/sdk/condo";
import "@livo-tv/sdk/styles.css";

<LivoPlayer streamId={stream.id} />

<LivoCommunity
	apiUrl="https://api.livo.tv"
	targetKind="stream"
	targetId={stream.id}
	flags={stream.community}
	type="comment"
	mode="viewer"
	displayName="Ada"
	picture="https://cdn.example/ada.jpg"
	guestId="guest-ada"
/>;
```

Headless client (same public routes, your own UI):

```ts
import { createCommunityClient } from "@livo-tv/sdk/condo";

const client = createCommunityClient({
	apiUrl: "https://api.livo.tv",
	targetKind: "stream",
	targetId: stream.id,
	mode: "viewer",
});
await client.list({ type: "comment" });
```

Server (Node / Workers):

```ts
import { createLivoServerClient } from "@livo-tv/sdk/server";

const livo = createLivoServerClient({ apiKey: process.env.LIVO_API_KEY! });
const { vodId, uploadId, partSize } = await livo.createVod({
	filename: "talk.mp4",
	contentType: "video/mp4",
	byteSize: 12_000_000,
	sha256: "64-lowercase-hex",
});
await livo.completeVodUpload(vodId, {
	parts: [{ partNumber: 1, etag: '"etag-from-put"' }],
});
const { hostToken, guestToken } = await livo.mintHostSession(streamId, {
	displayName: "Host",
});
```

Browser part `PUT`s go to the presigned R2 URL. Bucket CORS is `PUT *` with `ETag` exposed; mint the URL with `lk_` on your server.

GitHub `livo-tv/sdk` + npm OIDC publish are a human handoff (same as `@livo-tv/blocks`).
