@livo-tv/sdk 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
- import { VideoPlayer } from '@livo-tv/blocks';
2
- import { useState, useEffect, useMemo } from 'react';
3
- import { jsx } from 'react/jsx-runtime';
1
+ "use client";
2
+ import { VideoPlayer, createPlaybackBeaconClient, CurtainStage } from '@livo-tv/blocks';
3
+ import { useState, useRef, useEffect, useMemo } from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
5
 
5
6
  // src/player.tsx
6
7
 
@@ -16,17 +17,35 @@ function parsePublicStream(value) {
16
17
  if (typeof value.title !== "string" || typeof value.status !== "string") {
17
18
  return null;
18
19
  }
20
+ const organization = isRecord(value.organization) ? {
21
+ name: typeof value.organization.name === "string" ? value.organization.name : null,
22
+ image: typeof value.organization.image === "string" ? value.organization.image : null
23
+ } : { name: null, image: null };
19
24
  return {
20
25
  id: value.id,
21
26
  title: value.title,
22
27
  status: value.status,
23
28
  playbackUrl: typeof value.playbackUrl === "string" ? value.playbackUrl : null,
29
+ posterUrl: typeof value.posterUrl === "string" ? value.posterUrl : null,
24
30
  captionsUrl: typeof value.captionsUrl === "string" ? value.captionsUrl : null,
25
31
  viewers: typeof value.viewers === "number" ? value.viewers : 0,
26
- curtain: isRecord(value.curtain) && typeof value.curtain.url === "string" ? {
27
- kind: value.curtain.kind === "video" ? "video" : "image",
28
- url: value.curtain.url
29
- } : null
32
+ startsAt: typeof value.startsAt === "number" ? value.startsAt : null,
33
+ durationSeconds: typeof value.durationSeconds === "number" ? value.durationSeconds : null,
34
+ organization,
35
+ curtain: parsePublicCurtain(value.curtain)
36
+ };
37
+ }
38
+ function parsePublicCurtain(value) {
39
+ if (!isRecord(value) || typeof value.url !== "string") return null;
40
+ const kind = value.kind === "video" ? "video" : value.kind === "shader" ? "shader" : value.kind === "image" ? "image" : null;
41
+ if (!kind) return null;
42
+ return {
43
+ kind,
44
+ url: value.url,
45
+ hideBranding: value.hideBranding === true,
46
+ hideStatus: value.hideStatus === true,
47
+ config: isRecord(value.config) ? value.config : void 0,
48
+ envBasePath: typeof value.envBasePath === "string" ? value.envBasePath : void 0
30
49
  };
31
50
  }
32
51
  function parsePublicVod(value) {
@@ -40,10 +59,7 @@ function parsePublicVod(value) {
40
59
  status: value.status,
41
60
  playbackUrl: typeof value.playbackUrl === "string" ? value.playbackUrl : null,
42
61
  posterUrl: typeof value.posterUrl === "string" ? value.posterUrl : null,
43
- curtain: isRecord(value.curtain) && typeof value.curtain.url === "string" ? {
44
- kind: value.curtain.kind === "video" ? "video" : "image",
45
- url: value.curtain.url
46
- } : null
62
+ curtain: parsePublicCurtain(value.curtain)
47
63
  };
48
64
  }
49
65
  async function fetchPublicStream(apiUrl, streamId, fetchFn = fetch) {
@@ -133,6 +149,93 @@ function embedThemeCssVars(theme) {
133
149
  if (theme.fontFamily) vars["--font-sans"] = theme.fontFamily;
134
150
  return vars;
135
151
  }
152
+ function decodeEmbedTheme(raw) {
153
+ if (!raw) return null;
154
+ try {
155
+ const padded = raw.replace(/-/g, "+").replace(/_/g, "/");
156
+ const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
157
+ const binary = atob(padded + pad);
158
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
159
+ return sanitizeEmbedTheme(JSON.parse(new TextDecoder().decode(bytes)));
160
+ } catch {
161
+ return null;
162
+ }
163
+ }
164
+ function applyEmbedTheme(root, theme) {
165
+ if (!theme) return;
166
+ if (theme.mode === "dark") root.classList.add("dark");
167
+ if (theme.mode === "light") root.classList.remove("dark");
168
+ for (const [key, value] of Object.entries(embedThemeCssVars(theme))) {
169
+ root.style.setProperty(key, value);
170
+ }
171
+ if (theme.fontFamily) root.style.fontFamily = theme.fontFamily;
172
+ }
173
+ function LivoWaitingRoom({
174
+ title,
175
+ organization,
176
+ curtain,
177
+ primaryStatus,
178
+ hideBranding = false,
179
+ hideStatus = false,
180
+ labels
181
+ }) {
182
+ const waiting = labels?.waiting ?? "Waiting";
183
+ const statusText = [title, primaryStatus || waiting].filter(Boolean).join(". ");
184
+ return /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room", "data-livo-player": "waiting", children: [
185
+ curtain ? /* @__PURE__ */ jsx(
186
+ CurtainStage,
187
+ {
188
+ kind: curtain.kind,
189
+ url: curtain.url,
190
+ config: curtain.kind === "shader" ? curtain.config : void 0,
191
+ envBasePath: curtain.kind === "shader" ? curtain.envBasePath : void 0,
192
+ alt: "",
193
+ allowSound: true,
194
+ className: "livo-waiting-room__stage"
195
+ }
196
+ ) : /* @__PURE__ */ jsx("div", { className: "livo-waiting-room__fallback" }),
197
+ /* @__PURE__ */ jsx("p", { className: "livo-sr-only", role: "status", children: statusText }),
198
+ hideBranding && hideStatus ? null : /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room__overlay", children: [
199
+ hideBranding ? null : /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room__brand", children: [
200
+ organization.image ? /* @__PURE__ */ jsx("img", { src: organization.image, alt: "" }) : /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: (organization.name ?? title).slice(0, 1) }),
201
+ /* @__PURE__ */ jsxs("div", { children: [
202
+ /* @__PURE__ */ jsx("h1", { children: title }),
203
+ organization.name ? /* @__PURE__ */ jsx("p", { children: organization.name }) : null
204
+ ] })
205
+ ] }),
206
+ hideStatus ? null : /* @__PURE__ */ jsx("p", { children: primaryStatus || waiting })
207
+ ] })
208
+ ] });
209
+ }
210
+ function sdkSessionId() {
211
+ if (typeof sessionStorage === "undefined") return crypto.randomUUID();
212
+ const key = "livo-player-session";
213
+ const existing = sessionStorage.getItem(key);
214
+ if (existing) return existing;
215
+ const next = crypto.randomUUID();
216
+ sessionStorage.setItem(key, next);
217
+ return next;
218
+ }
219
+ function formatCountdown(ms) {
220
+ const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
221
+ const hours = Math.floor(totalSeconds / 3600);
222
+ const minutes = Math.floor(totalSeconds % 3600 / 60);
223
+ const seconds = totalSeconds % 60;
224
+ if (hours > 0) {
225
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
226
+ }
227
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
228
+ }
229
+ function createSdkBeacon(apiUrl, getContext) {
230
+ const endpoint = `${apiUrl.replace(/\/$/, "")}/public/playback/beacon`;
231
+ if (!endpoint.startsWith("http")) return null;
232
+ return createPlaybackBeaconClient({
233
+ endpoint,
234
+ sessionId: sdkSessionId(),
235
+ surface: "sdk",
236
+ getContext
237
+ });
238
+ }
136
239
  function LivoPlayer({
137
240
  streamId,
138
241
  vodId,
@@ -151,6 +254,20 @@ function LivoPlayer({
151
254
  const [stream, setStream] = useState(null);
152
255
  const [vod, setVod] = useState(null);
153
256
  const safeTheme = sanitizeEmbedTheme(theme ?? {});
257
+ const contextRef = useRef({ streamId, vodId });
258
+ contextRef.current = { streamId, vodId };
259
+ const [beacon, setBeacon] = useState(null);
260
+ useEffect(() => {
261
+ const client = createSdkBeacon(apiUrl, () => {
262
+ const ctx = contextRef.current;
263
+ return {
264
+ ...ctx.streamId ? { streamId: ctx.streamId } : {},
265
+ ...ctx.vodId ? { vodId: ctx.vodId } : {}
266
+ };
267
+ });
268
+ setBeacon(client);
269
+ return () => client?.destroy();
270
+ }, [apiUrl]);
154
271
  useEffect(() => {
155
272
  let cancelled = false;
156
273
  async function poll() {
@@ -212,16 +329,20 @@ function LivoPlayer({
212
329
  );
213
330
  const rootClass = ["livo-sdk", className].filter(Boolean).join(" ");
214
331
  if (!playbackUrl) {
215
- return /* @__PURE__ */ jsx(
216
- "div",
332
+ const startsAt = stream?.startsAt ?? null;
333
+ const remainingMs = startsAt != null ? startsAt - Date.now() : null;
334
+ const primaryStatus = remainingMs != null ? remainingMs > 0 ? `Starts in ${formatCountdown(remainingMs)}` : "Starting soon" : "Waiting";
335
+ return /* @__PURE__ */ jsx("div", { className: rootClass, style, "data-locale": locale, children: /* @__PURE__ */ jsx(
336
+ LivoWaitingRoom,
217
337
  {
218
- className: rootClass,
219
- style,
220
- "data-livo-player": "waiting",
221
- "data-locale": locale,
222
- children: stream?.title ?? vod?.title ?? "Livo"
338
+ title: stream?.title ?? vod?.title ?? "Livo",
339
+ organization: stream?.organization ?? { name: null, image: null },
340
+ curtain: stream?.curtain ?? vod?.curtain ?? null,
341
+ primaryStatus,
342
+ hideBranding: (stream?.curtain ?? vod?.curtain)?.hideBranding ?? false,
343
+ hideStatus: (stream?.curtain ?? vod?.curtain)?.hideStatus ?? false
223
344
  }
224
- );
345
+ ) });
225
346
  }
226
347
  return /* @__PURE__ */ jsx(
227
348
  "div",
@@ -240,7 +361,27 @@ function LivoPlayer({
240
361
  fit: "contain",
241
362
  labels,
242
363
  liveCaptionsBaseUrl: stream?.status === "public" ? stream.captionsUrl ?? void 0 : void 0,
243
- onError: () => onError?.({ code: "playback" })
364
+ onFirstPlay: () => beacon?.track({ type: "view" }),
365
+ onPlaybackProgress: (currentTime, duration) => {
366
+ beacon?.track({ type: "progress", currentTime, duration });
367
+ onProgress?.({
368
+ currentTime,
369
+ duration: Number.isFinite(duration) ? duration : null
370
+ });
371
+ },
372
+ onRebuffer: (seconds) => {
373
+ beacon?.track({ type: "rebuffer", rebufferSeconds: seconds });
374
+ },
375
+ onQualityChange: (quality) => {
376
+ beacon?.track({ type: "quality_change", quality });
377
+ },
378
+ onError: (error) => {
379
+ beacon?.track({
380
+ type: "error",
381
+ errorCode: error && typeof error === "object" && "kind" in error ? String(error.kind) : "playback"
382
+ });
383
+ onError?.({ code: "playback" });
384
+ }
244
385
  }
245
386
  )
246
387
  }
@@ -255,6 +396,7 @@ function playerUrl(streamId, options = {}) {
255
396
  const origin = trimSlash(options.playerOrigin ?? "https://player.livo.tv");
256
397
  const url = new URL(`${origin}/${streamId}`);
257
398
  if (options.locale) url.searchParams.set("lang", options.locale);
399
+ if (options.autoplay) url.searchParams.set("autoplay", "1");
258
400
  if (options.theme)
259
401
  url.searchParams.set("theme", encodeEmbedTheme(options.theme));
260
402
  return url.toString();
@@ -263,6 +405,7 @@ function vodPlayerUrl(vodId, options = {}) {
263
405
  const origin = trimSlash(options.playerOrigin ?? "https://player.livo.tv");
264
406
  const url = new URL(`${origin}/vod/${vodId}`);
265
407
  if (options.locale) url.searchParams.set("lang", options.locale);
408
+ if (options.autoplay) url.searchParams.set("autoplay", "1");
266
409
  if (options.theme)
267
410
  url.searchParams.set("theme", encodeEmbedTheme(options.theme));
268
411
  return url.toString();
@@ -338,6 +481,6 @@ function LivoStudio({
338
481
  );
339
482
  }
340
483
 
341
- export { LivoPlayer, LivoStudio, encodeEmbedTheme, playerUrl, sanitizeEmbedTheme, studioGuestUrl, studioHostUrl, vodPlayerUrl };
484
+ export { LivoPlayer, LivoStudio, applyEmbedTheme, decodeEmbedTheme, encodeEmbedTheme, playerUrl, sanitizeEmbedTheme, studioGuestUrl, studioHostUrl, vodPlayerUrl };
342
485
  //# sourceMappingURL=index.js.map
343
486
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/public-api.ts","../src/theme.ts","../src/player.tsx","../src/urls.ts","../src/studio.tsx"],"names":["useMemo","useEffect","jsx"],"mappings":"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","file":"index.js","sourcesContent":["export type PublicStream = {\n\tid: string;\n\ttitle: string;\n\tstatus: string;\n\tplaybackUrl: string | null;\n\tposterUrl?: string | null;\n\tcaptionsUrl: string | null;\n\tviewers: number;\n\tcurtain: { kind: \"image\" | \"video\"; url: string } | null;\n};\n\nexport type PublicVod = {\n\tid: string;\n\ttitle: string;\n\tstatus: string;\n\tplaybackUrl: string | null;\n\tposterUrl: string | null;\n\tcurtain: { kind: \"image\" | \"video\"; url: string } | null;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function defaultApiUrl(): string {\n\treturn \"https://media-svc.livo.tv\";\n}\n\nexport function parsePublicStream(value: unknown): PublicStream | null {\n\tif (!isRecord(value) || typeof value.id !== \"string\") return null;\n\tif (typeof value.title !== \"string\" || typeof value.status !== \"string\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tid: value.id,\n\t\ttitle: value.title,\n\t\tstatus: value.status,\n\t\tplaybackUrl:\n\t\t\ttypeof value.playbackUrl === \"string\" ? value.playbackUrl : null,\n\t\tcaptionsUrl:\n\t\t\ttypeof value.captionsUrl === \"string\" ? value.captionsUrl : null,\n\t\tviewers: typeof value.viewers === \"number\" ? value.viewers : 0,\n\t\tcurtain:\n\t\t\tisRecord(value.curtain) && typeof value.curtain.url === \"string\"\n\t\t\t\t? {\n\t\t\t\t\t\tkind: value.curtain.kind === \"video\" ? \"video\" : \"image\",\n\t\t\t\t\t\turl: value.curtain.url,\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t};\n}\n\nexport function parsePublicVod(value: unknown): PublicVod | null {\n\tif (!isRecord(value) || typeof value.id !== \"string\") return null;\n\tif (typeof value.title !== \"string\" || typeof value.status !== \"string\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tid: value.id,\n\t\ttitle: value.title,\n\t\tstatus: value.status,\n\t\tplaybackUrl:\n\t\t\ttypeof value.playbackUrl === \"string\" ? value.playbackUrl : null,\n\t\tposterUrl: typeof value.posterUrl === \"string\" ? value.posterUrl : null,\n\t\tcurtain:\n\t\t\tisRecord(value.curtain) && typeof value.curtain.url === \"string\"\n\t\t\t\t? {\n\t\t\t\t\t\tkind: value.curtain.kind === \"video\" ? \"video\" : \"image\",\n\t\t\t\t\t\turl: value.curtain.url,\n\t\t\t\t\t}\n\t\t\t\t: null,\n\t};\n}\n\nexport async function fetchPublicStream(\n\tapiUrl: string,\n\tstreamId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<PublicStream | null> {\n\tconst res = await fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/streams/${encodeURIComponent(streamId)}`,\n\t\t{ cache: \"no-store\" },\n\t);\n\tif (!res.ok) return null;\n\treturn parsePublicStream(await res.json());\n}\n\nexport async function fetchPublicVod(\n\tapiUrl: string,\n\tvodId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<PublicVod | null> {\n\tconst res = await fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/vods/${encodeURIComponent(vodId)}`,\n\t\t{ cache: \"no-store\" },\n\t);\n\tif (!res.ok) return null;\n\treturn parsePublicVod(await res.json());\n}\n\nexport async function heartbeatPresence(\n\tapiUrl: string,\n\tstreamId: string,\n\tsessionId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<void> {\n\tawait fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/streams/${encodeURIComponent(streamId)}/presence`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ sessionId }),\n\t\t},\n\t).catch(() => undefined);\n}\n","export type EmbedTheme = {\n\tprimary?: string;\n\tbackground?: string;\n\tforeground?: string;\n\tradius?: string;\n\tfontFamily?: string;\n\tmode?: \"light\" | \"dark\";\n};\n\nconst HEX =\n\t/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst COLOR_FN = /^(?:oklch|rgb|rgba|hsl|hsla)\\([0-9a-zA-Z.\\s%,./-]+\\)$/;\nconst RADIUS = /^\\d+(?:\\.\\d+)?(?:px|rem|em)$/;\nconst FONT = /^[a-zA-Z0-9\\s,\"'-]{1,120}$/;\n\nfunction sanitizeColor(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (HEX.test(trimmed) || COLOR_FN.test(trimmed)) return trimmed;\n\treturn undefined;\n}\n\nexport function sanitizeEmbedTheme(value: unknown): EmbedTheme | null {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn null;\n\t}\n\tconst row = value as Record<string, unknown>;\n\tconst theme: EmbedTheme = {};\n\tconst primary = sanitizeColor(row.primary);\n\tconst background = sanitizeColor(row.background);\n\tconst foreground = sanitizeColor(row.foreground);\n\tif (typeof row.radius === \"string\" && RADIUS.test(row.radius.trim())) {\n\t\ttheme.radius = row.radius.trim();\n\t}\n\tif (typeof row.fontFamily === \"string\" && FONT.test(row.fontFamily.trim())) {\n\t\ttheme.fontFamily = row.fontFamily.trim();\n\t}\n\tif (primary) theme.primary = primary;\n\tif (background) theme.background = background;\n\tif (foreground) theme.foreground = foreground;\n\tif (row.mode === \"light\" || row.mode === \"dark\") theme.mode = row.mode;\n\treturn Object.keys(theme).length > 0 ? theme : null;\n}\n\nexport function encodeEmbedTheme(theme: EmbedTheme): string {\n\tconst json = JSON.stringify(theme);\n\tconst bytes = new TextEncoder().encode(json);\n\tlet binary = \"\";\n\tfor (const byte of bytes) binary += String.fromCharCode(byte);\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n}\n\nexport function embedThemeCssVars(theme: EmbedTheme): Record<string, string> {\n\tconst vars: Record<string, string> = {};\n\tif (theme.primary) {\n\t\tvars[\"--livo-primary\"] = theme.primary;\n\t\tvars[\"--primary\"] = theme.primary;\n\t}\n\tif (theme.background) {\n\t\tvars[\"--livo-background\"] = theme.background;\n\t\tvars[\"--background\"] = theme.background;\n\t}\n\tif (theme.foreground) {\n\t\tvars[\"--livo-foreground\"] = theme.foreground;\n\t\tvars[\"--foreground\"] = theme.foreground;\n\t}\n\tif (theme.radius) {\n\t\tvars[\"--livo-radius\"] = theme.radius;\n\t\tvars[\"--radius\"] = theme.radius;\n\t}\n\tif (theme.fontFamily) vars[\"--font-sans\"] = theme.fontFamily;\n\treturn vars;\n}\n","import { VideoPlayer, type VideoPlayerLabels } from \"@livo-tv/blocks\";\nimport { useEffect, useMemo, useState, type CSSProperties } from \"react\";\nimport {\n\tdefaultApiUrl,\n\tfetchPublicStream,\n\tfetchPublicVod,\n\theartbeatPresence,\n\ttype PublicStream,\n\ttype PublicVod,\n} from \"./public-api\";\nimport {\n\tembedThemeCssVars,\n\tsanitizeEmbedTheme,\n\ttype EmbedTheme,\n} from \"./theme\";\n\nexport type LivoPlayerProps = {\n\tstreamId?: string;\n\tvodId?: string;\n\tapiUrl?: string;\n\ttheme?: EmbedTheme;\n\tlocale?: string;\n\tautoplay?: boolean;\n\tmuted?: boolean;\n\tlabels?: VideoPlayerLabels;\n\tclassName?: string;\n\tonStatusChange?: (status: string) => void;\n\tonProgress?: (payload: {\n\t\tcurrentTime: number;\n\t\tduration: number | null;\n\t}) => void;\n\tonEnded?: () => void;\n\tonError?: (error: { code: string }) => void;\n};\n\nexport function LivoPlayer({\n\tstreamId,\n\tvodId,\n\tapiUrl = defaultApiUrl(),\n\ttheme,\n\tlocale,\n\tautoplay = false,\n\tmuted,\n\tlabels,\n\tclassName,\n\tonStatusChange,\n\tonProgress,\n\tonEnded,\n\tonError,\n}: LivoPlayerProps) {\n\tconst [stream, setStream] = useState<PublicStream | null>(null);\n\tconst [vod, setVod] = useState<PublicVod | null>(null);\n\tconst safeTheme = sanitizeEmbedTheme(theme ?? {});\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tasync function poll() {\n\t\t\tif (streamId) {\n\t\t\t\tconst next = await fetchPublicStream(apiUrl, streamId);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!next) {\n\t\t\t\t\tonError?.({ code: \"not_found\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetStream(next);\n\t\t\t\tonStatusChange?.(next.status);\n\t\t\t\tif (next.status === \"ended\") onEnded?.();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (vodId) {\n\t\t\t\tconst next = await fetchPublicVod(apiUrl, vodId);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!next) {\n\t\t\t\t\tonError?.({ code: \"not_found\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetVod(next);\n\t\t\t\tonStatusChange?.(next.status);\n\t\t\t}\n\t\t}\n\t\tvoid poll();\n\t\tconst timer = window.setInterval(() => void poll(), 5000);\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [apiUrl, onEnded, onError, onStatusChange, streamId, vodId]);\n\n\tuseEffect(() => {\n\t\tif (!streamId) return;\n\t\tconst sessionId = crypto.randomUUID();\n\t\tconst beat = () => {\n\t\t\tvoid heartbeatPresence(apiUrl, streamId, sessionId);\n\t\t};\n\t\tbeat();\n\t\tconst timer = window.setInterval(beat, 15_000);\n\t\treturn () => window.clearInterval(timer);\n\t}, [apiUrl, streamId]);\n\n\tuseEffect(() => {\n\t\tconst timer = window.setInterval(() => {\n\t\t\tconst media = document.querySelector(\"video\");\n\t\t\tif (!media) return;\n\t\t\tonProgress?.({\n\t\t\t\tcurrentTime: media.currentTime,\n\t\t\t\tduration: Number.isFinite(media.duration) ? media.duration : null,\n\t\t\t});\n\t\t}, 5000);\n\t\treturn () => window.clearInterval(timer);\n\t}, [onProgress]);\n\n\tconst playbackUrl = stream?.playbackUrl ?? vod?.playbackUrl;\n\tconst style = useMemo(\n\t\t() =>\n\t\t\tsafeTheme ? (embedThemeCssVars(safeTheme) as CSSProperties) : undefined,\n\t\t[safeTheme],\n\t);\n\n\tconst rootClass = [\"livo-sdk\", className].filter(Boolean).join(\" \");\n\n\tif (!playbackUrl) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName={rootClass}\n\t\t\t\tstyle={style}\n\t\t\t\tdata-livo-player=\"waiting\"\n\t\t\t\tdata-locale={locale}\n\t\t\t>\n\t\t\t\t{stream?.title ?? vod?.title ?? \"Livo\"}\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName={rootClass}\n\t\t\tstyle={style}\n\t\t\tdata-livo-player=\"playing\"\n\t\t\tdata-locale={locale}\n\t\t>\n\t\t\t<VideoPlayer\n\t\t\t\tsrc={playbackUrl}\n\t\t\t\tmode={vod || stream?.status === \"ended\" ? \"vod\" : \"live\"}\n\t\t\t\tautoPlay={autoplay}\n\t\t\t\tmuted={muted ?? autoplay}\n\t\t\t\tfit=\"contain\"\n\t\t\t\tlabels={labels}\n\t\t\t\tliveCaptionsBaseUrl={\n\t\t\t\t\tstream?.status === \"public\"\n\t\t\t\t\t\t? (stream.captionsUrl ?? undefined)\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonError={() => onError?.({ code: \"playback\" })}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n","import { encodeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type UrlOrigins = {\n\tplayerOrigin?: string;\n\tappOrigin?: string;\n};\n\nfunction trimSlash(value: string): string {\n\treturn value.replace(/\\/$/, \"\");\n}\n\nexport function playerUrl(\n\tstreamId: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; locale?: string } = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/${streamId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function vodPlayerUrl(\n\tvodId: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; locale?: string } = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/vod/${vodId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioGuestUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/join/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioHostUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/host/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n","import { useEffect, useMemo, type CSSProperties } from \"react\";\nimport { studioGuestUrl, studioHostUrl } from \"./urls\";\nimport { sanitizeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type LivoStudioEvent = {\n\ttype: string;\n\tpayload?: unknown;\n};\n\nexport type LivoStudioProps = {\n\tguestToken?: string;\n\thostToken?: string;\n\tappOrigin?: string;\n\ttheme?: EmbedTheme;\n\tclassName?: string;\n\ttitle?: string;\n\tonEvent?: (event: LivoStudioEvent) => void;\n};\n\nexport function LivoStudio({\n\tguestToken,\n\thostToken,\n\tappOrigin,\n\ttheme,\n\tclassName,\n\ttitle = \"Livo studio\",\n\tonEvent,\n}: LivoStudioProps) {\n\tconst safeTheme = sanitizeEmbedTheme(theme ?? {});\n\tconst src = useMemo(() => {\n\t\tif (hostToken) {\n\t\t\treturn studioHostUrl(hostToken, {\n\t\t\t\tappOrigin,\n\t\t\t\ttheme: safeTheme ?? undefined,\n\t\t\t});\n\t\t}\n\t\tif (guestToken) {\n\t\t\treturn studioGuestUrl(guestToken, {\n\t\t\t\tappOrigin,\n\t\t\t\ttheme: safeTheme ?? undefined,\n\t\t\t});\n\t\t}\n\t\treturn null;\n\t}, [appOrigin, guestToken, hostToken, safeTheme]);\n\n\tuseEffect(() => {\n\t\tif (!onEvent) return;\n\t\tconst emit = onEvent;\n\t\tfunction onMessage(event: MessageEvent) {\n\t\t\tconst data = event.data;\n\t\t\tif (\n\t\t\t\ttypeof data !== \"object\" ||\n\t\t\t\tdata === null ||\n\t\t\t\t(data as { source?: string }).source !== \"livo\"\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\temit({\n\t\t\t\ttype: String((data as { type: string }).type),\n\t\t\t\tpayload: (data as { payload?: unknown }).payload,\n\t\t\t});\n\t\t}\n\t\twindow.addEventListener(\"message\", onMessage);\n\t\treturn () => window.removeEventListener(\"message\", onMessage);\n\t}, [onEvent]);\n\n\tif (!src) return null;\n\treturn (\n\t\t<iframe\n\t\t\tsrc={src}\n\t\t\ttitle={title}\n\t\t\tclassName={className}\n\t\t\tstyle={\n\t\t\t\t{ border: 0, width: \"100%\", minHeight: 480 } satisfies CSSProperties\n\t\t\t}\n\t\t\tallow=\"camera; microphone; display-capture; autoplay; fullscreen\"\n\t\t\tallowFullScreen\n\t\t/>\n\t);\n}\n"]}
1
+ {"version":3,"sources":["../src/public-api.ts","../src/theme.ts","../src/waiting-room.tsx","../src/player.tsx","../src/urls.ts","../src/iframe-studio.tsx"],"names":["jsx","useMemo","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["export type PublicStream = {\n\tid: string;\n\ttitle: string;\n\tstatus: string;\n\tplaybackUrl: string | null;\n\tposterUrl?: string | null;\n\tcaptionsUrl: string | null;\n\tviewers: number;\n\tstartsAt: number | null;\n\tdurationSeconds: number | null;\n\torganization: { name: string | null; image: string | null };\n\tcurtain: PublicCurtain | null;\n};\n\nexport type PublicCurtain = {\n\tkind: \"image\" | \"video\" | \"shader\";\n\turl: string;\n\thideBranding: boolean;\n\thideStatus: boolean;\n\tconfig?: Record<string, unknown>;\n\tenvBasePath?: string;\n};\n\nexport type PublicVod = {\n\tid: string;\n\ttitle: string;\n\tstatus: string;\n\tplaybackUrl: string | null;\n\tposterUrl: string | null;\n\tcurtain: PublicCurtain | null;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function defaultApiUrl(): string {\n\treturn \"https://media-svc.livo.tv\";\n}\n\nexport function parsePublicStream(value: unknown): PublicStream | null {\n\tif (!isRecord(value) || typeof value.id !== \"string\") return null;\n\tif (typeof value.title !== \"string\" || typeof value.status !== \"string\") {\n\t\treturn null;\n\t}\n\tconst organization = isRecord(value.organization)\n\t\t? {\n\t\t\t\tname:\n\t\t\t\t\ttypeof value.organization.name === \"string\"\n\t\t\t\t\t\t? value.organization.name\n\t\t\t\t\t\t: null,\n\t\t\t\timage:\n\t\t\t\t\ttypeof value.organization.image === \"string\"\n\t\t\t\t\t\t? value.organization.image\n\t\t\t\t\t\t: null,\n\t\t\t}\n\t\t: { name: null, image: null };\n\treturn {\n\t\tid: value.id,\n\t\ttitle: value.title,\n\t\tstatus: value.status,\n\t\tplaybackUrl:\n\t\t\ttypeof value.playbackUrl === \"string\" ? value.playbackUrl : null,\n\t\tposterUrl: typeof value.posterUrl === \"string\" ? value.posterUrl : null,\n\t\tcaptionsUrl:\n\t\t\ttypeof value.captionsUrl === \"string\" ? value.captionsUrl : null,\n\t\tviewers: typeof value.viewers === \"number\" ? value.viewers : 0,\n\t\tstartsAt: typeof value.startsAt === \"number\" ? value.startsAt : null,\n\t\tdurationSeconds:\n\t\t\ttypeof value.durationSeconds === \"number\" ? value.durationSeconds : null,\n\t\torganization,\n\t\tcurtain: parsePublicCurtain(value.curtain),\n\t};\n}\n\nfunction parsePublicCurtain(value: unknown): PublicCurtain | null {\n\tif (!isRecord(value) || typeof value.url !== \"string\") return null;\n\tconst kind =\n\t\tvalue.kind === \"video\"\n\t\t\t? \"video\"\n\t\t\t: value.kind === \"shader\"\n\t\t\t\t? \"shader\"\n\t\t\t\t: value.kind === \"image\"\n\t\t\t\t\t? \"image\"\n\t\t\t\t\t: null;\n\tif (!kind) return null;\n\treturn {\n\t\tkind,\n\t\turl: value.url,\n\t\thideBranding: value.hideBranding === true,\n\t\thideStatus: value.hideStatus === true,\n\t\tconfig: isRecord(value.config) ? value.config : undefined,\n\t\tenvBasePath:\n\t\t\ttypeof value.envBasePath === \"string\" ? value.envBasePath : undefined,\n\t};\n}\n\nexport function parsePublicVod(value: unknown): PublicVod | null {\n\tif (!isRecord(value) || typeof value.id !== \"string\") return null;\n\tif (typeof value.title !== \"string\" || typeof value.status !== \"string\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tid: value.id,\n\t\ttitle: value.title,\n\t\tstatus: value.status,\n\t\tplaybackUrl:\n\t\t\ttypeof value.playbackUrl === \"string\" ? value.playbackUrl : null,\n\t\tposterUrl: typeof value.posterUrl === \"string\" ? value.posterUrl : null,\n\t\tcurtain: parsePublicCurtain(value.curtain),\n\t};\n}\n\nexport async function fetchPublicStream(\n\tapiUrl: string,\n\tstreamId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<PublicStream | null> {\n\tconst res = await fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/streams/${encodeURIComponent(streamId)}`,\n\t\t{ cache: \"no-store\" },\n\t);\n\tif (!res.ok) return null;\n\treturn parsePublicStream(await res.json());\n}\n\nexport async function fetchPublicVod(\n\tapiUrl: string,\n\tvodId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<PublicVod | null> {\n\tconst res = await fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/vods/${encodeURIComponent(vodId)}`,\n\t\t{ cache: \"no-store\" },\n\t);\n\tif (!res.ok) return null;\n\treturn parsePublicVod(await res.json());\n}\n\nexport async function heartbeatPresence(\n\tapiUrl: string,\n\tstreamId: string,\n\tsessionId: string,\n\tfetchFn: typeof fetch = fetch,\n): Promise<void> {\n\tawait fetchFn(\n\t\t`${apiUrl.replace(/\\/$/, \"\")}/public/streams/${encodeURIComponent(streamId)}/presence`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"content-type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ sessionId }),\n\t\t},\n\t).catch(() => undefined);\n}\n","export type EmbedTheme = {\n\tprimary?: string;\n\tbackground?: string;\n\tforeground?: string;\n\tradius?: string;\n\tfontFamily?: string;\n\tmode?: \"light\" | \"dark\";\n};\n\nconst HEX =\n\t/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst COLOR_FN = /^(?:oklch|rgb|rgba|hsl|hsla)\\([0-9a-zA-Z.\\s%,./-]+\\)$/;\nconst RADIUS = /^\\d+(?:\\.\\d+)?(?:px|rem|em)$/;\nconst FONT = /^[a-zA-Z0-9\\s,\"'-]{1,120}$/;\n\nfunction sanitizeColor(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (HEX.test(trimmed) || COLOR_FN.test(trimmed)) return trimmed;\n\treturn undefined;\n}\n\nexport function sanitizeEmbedTheme(value: unknown): EmbedTheme | null {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn null;\n\t}\n\tconst row = value as Record<string, unknown>;\n\tconst theme: EmbedTheme = {};\n\tconst primary = sanitizeColor(row.primary);\n\tconst background = sanitizeColor(row.background);\n\tconst foreground = sanitizeColor(row.foreground);\n\tif (typeof row.radius === \"string\" && RADIUS.test(row.radius.trim())) {\n\t\ttheme.radius = row.radius.trim();\n\t}\n\tif (typeof row.fontFamily === \"string\" && FONT.test(row.fontFamily.trim())) {\n\t\ttheme.fontFamily = row.fontFamily.trim();\n\t}\n\tif (primary) theme.primary = primary;\n\tif (background) theme.background = background;\n\tif (foreground) theme.foreground = foreground;\n\tif (row.mode === \"light\" || row.mode === \"dark\") theme.mode = row.mode;\n\treturn Object.keys(theme).length > 0 ? theme : null;\n}\n\nexport function encodeEmbedTheme(theme: EmbedTheme): string {\n\tconst json = JSON.stringify(theme);\n\tconst bytes = new TextEncoder().encode(json);\n\tlet binary = \"\";\n\tfor (const byte of bytes) binary += String.fromCharCode(byte);\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n}\n\nexport function embedThemeCssVars(theme: EmbedTheme): Record<string, string> {\n\tconst vars: Record<string, string> = {};\n\tif (theme.primary) {\n\t\tvars[\"--livo-primary\"] = theme.primary;\n\t\tvars[\"--primary\"] = theme.primary;\n\t}\n\tif (theme.background) {\n\t\tvars[\"--livo-background\"] = theme.background;\n\t\tvars[\"--background\"] = theme.background;\n\t}\n\tif (theme.foreground) {\n\t\tvars[\"--livo-foreground\"] = theme.foreground;\n\t\tvars[\"--foreground\"] = theme.foreground;\n\t}\n\tif (theme.radius) {\n\t\tvars[\"--livo-radius\"] = theme.radius;\n\t\tvars[\"--radius\"] = theme.radius;\n\t}\n\tif (theme.fontFamily) vars[\"--font-sans\"] = theme.fontFamily;\n\treturn vars;\n}\n\nexport function decodeEmbedTheme(\n\traw: string | null | undefined,\n): EmbedTheme | null {\n\tif (!raw) return null;\n\ttry {\n\t\tconst padded = raw.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\t\tconst pad =\n\t\t\tpadded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n\t\tconst binary = atob(padded + pad);\n\t\tconst bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n\t\treturn sanitizeEmbedTheme(JSON.parse(new TextDecoder().decode(bytes)));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function applyEmbedTheme(\n\troot: HTMLElement,\n\ttheme: EmbedTheme | null,\n): void {\n\tif (!theme) return;\n\tif (theme.mode === \"dark\") root.classList.add(\"dark\");\n\tif (theme.mode === \"light\") root.classList.remove(\"dark\");\n\tfor (const [key, value] of Object.entries(embedThemeCssVars(theme))) {\n\t\troot.style.setProperty(key, value);\n\t}\n\tif (theme.fontFamily) root.style.fontFamily = theme.fontFamily;\n}\n","import { CurtainStage, type ShaderCurtainConfig } from \"@livo-tv/blocks\";\nimport type { PublicCurtain } from \"./public-api\";\n\nexport type WaitingRoomLabels = {\n\twaiting?: string;\n\tstartingSoon?: string;\n};\n\nexport function LivoWaitingRoom({\n\ttitle,\n\torganization,\n\tcurtain,\n\tprimaryStatus,\n\thideBranding = false,\n\thideStatus = false,\n\tlabels,\n}: {\n\ttitle: string;\n\torganization: { name: string | null; image: string | null };\n\tcurtain: PublicCurtain | null;\n\tprimaryStatus: string;\n\thideBranding?: boolean;\n\thideStatus?: boolean;\n\tlabels?: WaitingRoomLabels;\n}) {\n\tconst waiting = labels?.waiting ?? \"Waiting\";\n\tconst statusText = [title, primaryStatus || waiting]\n\t\t.filter(Boolean)\n\t\t.join(\". \");\n\n\treturn (\n\t\t<div className=\"livo-waiting-room\" data-livo-player=\"waiting\">\n\t\t\t{curtain ? (\n\t\t\t\t<CurtainStage\n\t\t\t\t\tkind={curtain.kind}\n\t\t\t\t\turl={curtain.url}\n\t\t\t\t\tconfig={\n\t\t\t\t\t\tcurtain.kind === \"shader\"\n\t\t\t\t\t\t\t? (curtain.config as ShaderCurtainConfig | undefined)\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t}\n\t\t\t\t\tenvBasePath={\n\t\t\t\t\t\tcurtain.kind === \"shader\" ? curtain.envBasePath : undefined\n\t\t\t\t\t}\n\t\t\t\t\talt=\"\"\n\t\t\t\t\tallowSound\n\t\t\t\t\tclassName=\"livo-waiting-room__stage\"\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<div className=\"livo-waiting-room__fallback\" />\n\t\t\t)}\n\t\t\t<p className=\"livo-sr-only\" role=\"status\">\n\t\t\t\t{statusText}\n\t\t\t</p>\n\t\t\t{hideBranding && hideStatus ? null : (\n\t\t\t\t<div className=\"livo-waiting-room__overlay\">\n\t\t\t\t\t{hideBranding ? null : (\n\t\t\t\t\t\t<div className=\"livo-waiting-room__brand\">\n\t\t\t\t\t\t\t{organization.image ? (\n\t\t\t\t\t\t\t\t<img src={organization.image} alt=\"\" />\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t<span aria-hidden>\n\t\t\t\t\t\t\t\t\t{(organization.name ?? title).slice(0, 1)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t<h1>{title}</h1>\n\t\t\t\t\t\t\t\t{organization.name ? <p>{organization.name}</p> : null}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t)}\n\t\t\t\t\t{hideStatus ? null : <p>{primaryStatus || waiting}</p>}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n","import {\n\tVideoPlayer,\n\tcreatePlaybackBeaconClient,\n\ttype PlaybackBeaconClient,\n\ttype VideoPlayerLabels,\n} from \"@livo-tv/blocks\";\nimport {\n\tuseEffect,\n\tuseMemo,\n\tuseRef,\n\tuseState,\n\ttype CSSProperties,\n} from \"react\";\nimport {\n\tdefaultApiUrl,\n\tfetchPublicStream,\n\tfetchPublicVod,\n\theartbeatPresence,\n\ttype PublicStream,\n\ttype PublicVod,\n} from \"./public-api\";\nimport {\n\tembedThemeCssVars,\n\tsanitizeEmbedTheme,\n\ttype EmbedTheme,\n} from \"./theme\";\nimport { LivoWaitingRoom } from \"./waiting-room\";\n\nexport type LivoPlayerProps = {\n\tstreamId?: string;\n\tvodId?: string;\n\tapiUrl?: string;\n\ttheme?: EmbedTheme;\n\tlocale?: string;\n\tautoplay?: boolean;\n\tmuted?: boolean;\n\tlabels?: VideoPlayerLabels;\n\tclassName?: string;\n\tonStatusChange?: (status: string) => void;\n\tonProgress?: (payload: {\n\t\tcurrentTime: number;\n\t\tduration: number | null;\n\t}) => void;\n\tonEnded?: () => void;\n\tonError?: (error: { code: string }) => void;\n};\n\nfunction sdkSessionId(): string {\n\tif (typeof sessionStorage === \"undefined\") return crypto.randomUUID();\n\tconst key = \"livo-player-session\";\n\tconst existing = sessionStorage.getItem(key);\n\tif (existing) return existing;\n\tconst next = crypto.randomUUID();\n\tsessionStorage.setItem(key, next);\n\treturn next;\n}\n\nfunction formatCountdown(ms: number): string {\n\tconst totalSeconds = Math.max(0, Math.ceil(ms / 1000));\n\tconst hours = Math.floor(totalSeconds / 3600);\n\tconst minutes = Math.floor((totalSeconds % 3600) / 60);\n\tconst seconds = totalSeconds % 60;\n\tif (hours > 0) {\n\t\treturn `${hours}h ${String(minutes).padStart(2, \"0\")}m`;\n\t}\n\treturn `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\nfunction createSdkBeacon(\n\tapiUrl: string,\n\tgetContext: () => Record<string, unknown>,\n): PlaybackBeaconClient | null {\n\tconst endpoint = `${apiUrl.replace(/\\/$/, \"\")}/public/playback/beacon`;\n\tif (!endpoint.startsWith(\"http\")) return null;\n\treturn createPlaybackBeaconClient({\n\t\tendpoint,\n\t\tsessionId: sdkSessionId(),\n\t\tsurface: \"sdk\",\n\t\tgetContext,\n\t});\n}\n\nexport function LivoPlayer({\n\tstreamId,\n\tvodId,\n\tapiUrl = defaultApiUrl(),\n\ttheme,\n\tlocale,\n\tautoplay = false,\n\tmuted,\n\tlabels,\n\tclassName,\n\tonStatusChange,\n\tonProgress,\n\tonEnded,\n\tonError,\n}: LivoPlayerProps) {\n\tconst [stream, setStream] = useState<PublicStream | null>(null);\n\tconst [vod, setVod] = useState<PublicVod | null>(null);\n\tconst safeTheme = sanitizeEmbedTheme(theme ?? {});\n\tconst contextRef = useRef({ streamId, vodId });\n\tcontextRef.current = { streamId, vodId };\n\tconst [beacon, setBeacon] = useState<PlaybackBeaconClient | null>(null);\n\n\tuseEffect(() => {\n\t\tconst client = createSdkBeacon(apiUrl, () => {\n\t\t\tconst ctx = contextRef.current;\n\t\t\treturn {\n\t\t\t\t...(ctx.streamId ? { streamId: ctx.streamId } : {}),\n\t\t\t\t...(ctx.vodId ? { vodId: ctx.vodId } : {}),\n\t\t\t};\n\t\t});\n\t\tsetBeacon(client);\n\t\treturn () => client?.destroy();\n\t}, [apiUrl]);\n\n\tuseEffect(() => {\n\t\tlet cancelled = false;\n\t\tasync function poll() {\n\t\t\tif (streamId) {\n\t\t\t\tconst next = await fetchPublicStream(apiUrl, streamId);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!next) {\n\t\t\t\t\tonError?.({ code: \"not_found\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetStream(next);\n\t\t\t\tonStatusChange?.(next.status);\n\t\t\t\tif (next.status === \"ended\") onEnded?.();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (vodId) {\n\t\t\t\tconst next = await fetchPublicVod(apiUrl, vodId);\n\t\t\t\tif (cancelled) return;\n\t\t\t\tif (!next) {\n\t\t\t\t\tonError?.({ code: \"not_found\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsetVod(next);\n\t\t\t\tonStatusChange?.(next.status);\n\t\t\t}\n\t\t}\n\t\tvoid poll();\n\t\tconst timer = window.setInterval(() => void poll(), 5000);\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\twindow.clearInterval(timer);\n\t\t};\n\t}, [apiUrl, onEnded, onError, onStatusChange, streamId, vodId]);\n\n\tuseEffect(() => {\n\t\tif (!streamId) return;\n\t\tconst sessionId = crypto.randomUUID();\n\t\tconst beat = () => {\n\t\t\tvoid heartbeatPresence(apiUrl, streamId, sessionId);\n\t\t};\n\t\tbeat();\n\t\tconst timer = window.setInterval(beat, 15_000);\n\t\treturn () => window.clearInterval(timer);\n\t}, [apiUrl, streamId]);\n\n\tuseEffect(() => {\n\t\tconst timer = window.setInterval(() => {\n\t\t\tconst media = document.querySelector(\"video\");\n\t\t\tif (!media) return;\n\t\t\tonProgress?.({\n\t\t\t\tcurrentTime: media.currentTime,\n\t\t\t\tduration: Number.isFinite(media.duration) ? media.duration : null,\n\t\t\t});\n\t\t}, 5000);\n\t\treturn () => window.clearInterval(timer);\n\t}, [onProgress]);\n\n\tconst playbackUrl = stream?.playbackUrl ?? vod?.playbackUrl;\n\tconst style = useMemo(\n\t\t() =>\n\t\t\tsafeTheme ? (embedThemeCssVars(safeTheme) as CSSProperties) : undefined,\n\t\t[safeTheme],\n\t);\n\n\tconst rootClass = [\"livo-sdk\", className].filter(Boolean).join(\" \");\n\n\tif (!playbackUrl) {\n\t\tconst startsAt = stream?.startsAt ?? null;\n\t\tconst remainingMs = startsAt != null ? startsAt - Date.now() : null;\n\t\tconst primaryStatus =\n\t\t\tremainingMs != null\n\t\t\t\t? remainingMs > 0\n\t\t\t\t\t? `Starts in ${formatCountdown(remainingMs)}`\n\t\t\t\t\t: \"Starting soon\"\n\t\t\t\t: \"Waiting\";\n\t\treturn (\n\t\t\t<div className={rootClass} style={style} data-locale={locale}>\n\t\t\t\t<LivoWaitingRoom\n\t\t\t\t\ttitle={stream?.title ?? vod?.title ?? \"Livo\"}\n\t\t\t\t\torganization={stream?.organization ?? { name: null, image: null }}\n\t\t\t\t\tcurtain={stream?.curtain ?? vod?.curtain ?? null}\n\t\t\t\t\tprimaryStatus={primaryStatus}\n\t\t\t\t\thideBranding={\n\t\t\t\t\t\t(stream?.curtain ?? vod?.curtain)?.hideBranding ?? false\n\t\t\t\t\t}\n\t\t\t\t\thideStatus={(stream?.curtain ?? vod?.curtain)?.hideStatus ?? false}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t);\n\t}\n\n\treturn (\n\t\t<div\n\t\t\tclassName={rootClass}\n\t\t\tstyle={style}\n\t\t\tdata-livo-player=\"playing\"\n\t\t\tdata-locale={locale}\n\t\t>\n\t\t\t<VideoPlayer\n\t\t\t\tsrc={playbackUrl}\n\t\t\t\tmode={vod || stream?.status === \"ended\" ? \"vod\" : \"live\"}\n\t\t\t\tautoPlay={autoplay}\n\t\t\t\tmuted={muted ?? autoplay}\n\t\t\t\tfit=\"contain\"\n\t\t\t\tlabels={labels}\n\t\t\t\tliveCaptionsBaseUrl={\n\t\t\t\t\tstream?.status === \"public\"\n\t\t\t\t\t\t? (stream.captionsUrl ?? undefined)\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonFirstPlay={() => beacon?.track({ type: \"view\" })}\n\t\t\t\tonPlaybackProgress={(currentTime, duration) => {\n\t\t\t\t\tbeacon?.track({ type: \"progress\", currentTime, duration });\n\t\t\t\t\tonProgress?.({\n\t\t\t\t\t\tcurrentTime,\n\t\t\t\t\t\tduration: Number.isFinite(duration) ? duration : null,\n\t\t\t\t\t});\n\t\t\t\t}}\n\t\t\t\tonRebuffer={(seconds) => {\n\t\t\t\t\tbeacon?.track({ type: \"rebuffer\", rebufferSeconds: seconds });\n\t\t\t\t}}\n\t\t\t\tonQualityChange={(quality) => {\n\t\t\t\t\tbeacon?.track({ type: \"quality_change\", quality });\n\t\t\t\t}}\n\t\t\t\tonError={(error) => {\n\t\t\t\t\tbeacon?.track({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\terrorCode:\n\t\t\t\t\t\t\terror && typeof error === \"object\" && \"kind\" in error\n\t\t\t\t\t\t\t\t? String(error.kind)\n\t\t\t\t\t\t\t\t: \"playback\",\n\t\t\t\t\t});\n\t\t\t\t\tonError?.({ code: \"playback\" });\n\t\t\t\t}}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n","import { encodeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type UrlOrigins = {\n\tplayerOrigin?: string;\n\tappOrigin?: string;\n};\n\nfunction trimSlash(value: string): string {\n\treturn value.replace(/\\/$/, \"\");\n}\n\nexport function playerUrl(\n\tstreamId: string,\n\toptions: UrlOrigins & {\n\t\ttheme?: EmbedTheme;\n\t\tlocale?: string;\n\t\tautoplay?: boolean;\n\t} = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/${streamId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.autoplay) url.searchParams.set(\"autoplay\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function vodPlayerUrl(\n\tvodId: string,\n\toptions: UrlOrigins & {\n\t\ttheme?: EmbedTheme;\n\t\tlocale?: string;\n\t\tautoplay?: boolean;\n\t} = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/vod/${vodId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.autoplay) url.searchParams.set(\"autoplay\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioGuestUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/join/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioHostUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/host/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n","import { useEffect, useMemo, type CSSProperties } from \"react\";\nimport { studioGuestUrl, studioHostUrl } from \"./urls\";\nimport { sanitizeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type LivoStudioEvent = {\n\ttype: string;\n\tpayload?: unknown;\n};\n\nexport type LivoStudioProps = {\n\tguestToken?: string;\n\thostToken?: string;\n\tappOrigin?: string;\n\ttheme?: EmbedTheme;\n\tclassName?: string;\n\ttitle?: string;\n\tonEvent?: (event: LivoStudioEvent) => void;\n};\n\nexport function LivoStudio({\n\tguestToken,\n\thostToken,\n\tappOrigin,\n\ttheme,\n\tclassName,\n\ttitle = \"Livo studio\",\n\tonEvent,\n}: LivoStudioProps) {\n\tconst safeTheme = sanitizeEmbedTheme(theme ?? {});\n\tconst src = useMemo(() => {\n\t\tif (hostToken) {\n\t\t\treturn studioHostUrl(hostToken, {\n\t\t\t\tappOrigin,\n\t\t\t\ttheme: safeTheme ?? undefined,\n\t\t\t});\n\t\t}\n\t\tif (guestToken) {\n\t\t\treturn studioGuestUrl(guestToken, {\n\t\t\t\tappOrigin,\n\t\t\t\ttheme: safeTheme ?? undefined,\n\t\t\t});\n\t\t}\n\t\treturn null;\n\t}, [appOrigin, guestToken, hostToken, safeTheme]);\n\n\tuseEffect(() => {\n\t\tif (!onEvent) return;\n\t\tconst emit = onEvent;\n\t\tfunction onMessage(event: MessageEvent) {\n\t\t\tconst data = event.data;\n\t\t\tif (\n\t\t\t\ttypeof data !== \"object\" ||\n\t\t\t\tdata === null ||\n\t\t\t\t(data as { source?: string }).source !== \"livo\"\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\temit({\n\t\t\t\ttype: String((data as { type: string }).type),\n\t\t\t\tpayload: (data as { payload?: unknown }).payload,\n\t\t\t});\n\t\t}\n\t\twindow.addEventListener(\"message\", onMessage);\n\t\treturn () => window.removeEventListener(\"message\", onMessage);\n\t}, [onEvent]);\n\n\tif (!src) return null;\n\treturn (\n\t\t<iframe\n\t\t\tsrc={src}\n\t\t\ttitle={title}\n\t\t\tclassName={className}\n\t\t\tstyle={\n\t\t\t\t{ border: 0, width: \"100%\", minHeight: 480 } satisfies CSSProperties\n\t\t\t}\n\t\t\tallow=\"camera; microphone; display-capture; autoplay; fullscreen\"\n\t\t\tallowFullScreen\n\t\t/>\n\t);\n}\n"]}
package/dist/server.cjs CHANGED
@@ -66,6 +66,22 @@ var LivoServerClient = class {
66
66
  `/streams/${encodeURIComponent(id)}`
67
67
  );
68
68
  }
69
+ startStream(id) {
70
+ return this.request(`/streams/${encodeURIComponent(id)}/start`, {
71
+ method: "POST"
72
+ });
73
+ }
74
+ publishStream(id) {
75
+ return this.request(`/streams/${encodeURIComponent(id)}/publish`, {
76
+ method: "POST"
77
+ });
78
+ }
79
+ patchStream(id, input) {
80
+ return this.request(`/streams/${encodeURIComponent(id)}`, {
81
+ method: "PATCH",
82
+ body: JSON.stringify(input)
83
+ });
84
+ }
69
85
  stopStream(id) {
70
86
  return this.request(`/streams/${encodeURIComponent(id)}/stop`, {
71
87
  method: "POST"
@@ -76,6 +92,56 @@ var LivoServerClient = class {
76
92
  "/vods"
77
93
  );
78
94
  }
95
+ createVod(input) {
96
+ return this.request("/vods", {
97
+ method: "POST",
98
+ body: JSON.stringify(input)
99
+ });
100
+ }
101
+ requestVodPartUrl(vodId, partNumber) {
102
+ return this.request(
103
+ `/vods/${encodeURIComponent(vodId)}/upload-part`,
104
+ {
105
+ method: "POST",
106
+ body: JSON.stringify({ partNumber })
107
+ }
108
+ );
109
+ }
110
+ completeVodUpload(vodId) {
111
+ return this.request(`/vods/${encodeURIComponent(vodId)}/complete-upload`, {
112
+ method: "POST"
113
+ });
114
+ }
115
+ transcodeVod(vodId) {
116
+ return this.request(`/vods/${encodeURIComponent(vodId)}/transcode`, {
117
+ method: "POST"
118
+ });
119
+ }
120
+ enableVodCaptions(vodId) {
121
+ return this.request(`/vods/${encodeURIComponent(vodId)}/captions`, {
122
+ method: "POST"
123
+ });
124
+ }
125
+ listCurtains() {
126
+ return this.request(
127
+ "/curtains"
128
+ );
129
+ }
130
+ createCurtain(input) {
131
+ return this.request("/curtains", {
132
+ method: "POST",
133
+ body: JSON.stringify(input)
134
+ });
135
+ }
136
+ getOrganizationSettings() {
137
+ return this.request("/organization/settings");
138
+ }
139
+ putOrganizationSettings(input) {
140
+ return this.request("/organization/settings", {
141
+ method: "PUT",
142
+ body: JSON.stringify(input)
143
+ });
144
+ }
79
145
  mintHostSession(streamId, input) {
80
146
  return this.request(`/streams/${encodeURIComponent(streamId)}/studio/host-session`, {
81
147
  method: "POST",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/theme.ts","../src/urls.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.cjs","sourcesContent":["export type EmbedTheme = {\n\tprimary?: string;\n\tbackground?: string;\n\tforeground?: string;\n\tradius?: string;\n\tfontFamily?: string;\n\tmode?: \"light\" | \"dark\";\n};\n\nconst HEX =\n\t/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst COLOR_FN = /^(?:oklch|rgb|rgba|hsl|hsla)\\([0-9a-zA-Z.\\s%,./-]+\\)$/;\nconst RADIUS = /^\\d+(?:\\.\\d+)?(?:px|rem|em)$/;\nconst FONT = /^[a-zA-Z0-9\\s,\"'-]{1,120}$/;\n\nfunction sanitizeColor(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (HEX.test(trimmed) || COLOR_FN.test(trimmed)) return trimmed;\n\treturn undefined;\n}\n\nexport function sanitizeEmbedTheme(value: unknown): EmbedTheme | null {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn null;\n\t}\n\tconst row = value as Record<string, unknown>;\n\tconst theme: EmbedTheme = {};\n\tconst primary = sanitizeColor(row.primary);\n\tconst background = sanitizeColor(row.background);\n\tconst foreground = sanitizeColor(row.foreground);\n\tif (typeof row.radius === \"string\" && RADIUS.test(row.radius.trim())) {\n\t\ttheme.radius = row.radius.trim();\n\t}\n\tif (typeof row.fontFamily === \"string\" && FONT.test(row.fontFamily.trim())) {\n\t\ttheme.fontFamily = row.fontFamily.trim();\n\t}\n\tif (primary) theme.primary = primary;\n\tif (background) theme.background = background;\n\tif (foreground) theme.foreground = foreground;\n\tif (row.mode === \"light\" || row.mode === \"dark\") theme.mode = row.mode;\n\treturn Object.keys(theme).length > 0 ? theme : null;\n}\n\nexport function encodeEmbedTheme(theme: EmbedTheme): string {\n\tconst json = JSON.stringify(theme);\n\tconst bytes = new TextEncoder().encode(json);\n\tlet binary = \"\";\n\tfor (const byte of bytes) binary += String.fromCharCode(byte);\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n}\n\nexport function embedThemeCssVars(theme: EmbedTheme): Record<string, string> {\n\tconst vars: Record<string, string> = {};\n\tif (theme.primary) {\n\t\tvars[\"--livo-primary\"] = theme.primary;\n\t\tvars[\"--primary\"] = theme.primary;\n\t}\n\tif (theme.background) {\n\t\tvars[\"--livo-background\"] = theme.background;\n\t\tvars[\"--background\"] = theme.background;\n\t}\n\tif (theme.foreground) {\n\t\tvars[\"--livo-foreground\"] = theme.foreground;\n\t\tvars[\"--foreground\"] = theme.foreground;\n\t}\n\tif (theme.radius) {\n\t\tvars[\"--livo-radius\"] = theme.radius;\n\t\tvars[\"--radius\"] = theme.radius;\n\t}\n\tif (theme.fontFamily) vars[\"--font-sans\"] = theme.fontFamily;\n\treturn vars;\n}\n","import { encodeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type UrlOrigins = {\n\tplayerOrigin?: string;\n\tappOrigin?: string;\n};\n\nfunction trimSlash(value: string): string {\n\treturn value.replace(/\\/$/, \"\");\n}\n\nexport function playerUrl(\n\tstreamId: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; locale?: string } = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/${streamId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function vodPlayerUrl(\n\tvodId: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; locale?: string } = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/vod/${vodId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioGuestUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/join/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioHostUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/host/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n","export type LivoServerClientOptions = {\n\tapiUrl?: string;\n\tapiKey: string;\n\tfetchFn?: typeof fetch;\n};\n\nexport class LivoServerClient {\n\tprivate readonly apiUrl: string;\n\tprivate readonly apiKey: string;\n\tprivate readonly fetchFn: typeof fetch;\n\n\tconstructor(options: LivoServerClientOptions) {\n\t\tthis.apiUrl = (options.apiUrl ?? \"https://media-svc.livo.tv\").replace(\n\t\t\t/\\/$/,\n\t\t\t\"\",\n\t\t);\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t}\n\n\tprivate async request<T>(path: string, init: RequestInit = {}): Promise<T> {\n\t\tconst headers = new Headers(init.headers);\n\t\theaders.set(\"X-Api-Key\", this.apiKey);\n\t\theaders.set(\"accept\", \"application/json\");\n\t\tif (init.body && !headers.has(\"content-type\")) {\n\t\t\theaders.set(\"content-type\", \"application/json\");\n\t\t}\n\t\tconst res = await this.fetchFn(`${this.apiUrl}${path}`, {\n\t\t\t...init,\n\t\t\theaders,\n\t\t});\n\t\tconst body: unknown = await res.json().catch(() => ({}));\n\t\tif (!res.ok) {\n\t\t\tconst error = new Error(`Livo API ${res.status}`);\n\t\t\t(error as Error & { status: number; body: unknown }).status = res.status;\n\t\t\t(error as Error & { status: number; body: unknown }).body = body;\n\t\t\tthrow error;\n\t\t}\n\t\treturn body as T;\n\t}\n\n\tcreateStream(input: { title: string; mode?: \"instant\" | \"scheduled\" }) {\n\t\treturn this.request<{ stream: { id: string } }>(\"/streams\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\tgetStream(id: string) {\n\t\treturn this.request<{ stream: { id: string; status: string } }>(\n\t\t\t`/streams/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tstopStream(id: string) {\n\t\treturn this.request(`/streams/${encodeURIComponent(id)}/stop`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tlistVods() {\n\t\treturn this.request<{ vods: Array<{ id: string; title: string }> }>(\n\t\t\t\"/vods\",\n\t\t);\n\t}\n\n\tmintHostSession(\n\t\tstreamId: string,\n\t\tinput: { displayName: string; picture?: string },\n\t) {\n\t\treturn this.request<{\n\t\t\thostUrl: string;\n\t\t\tguestUrl: string;\n\t\t\texpiresAt: number;\n\t\t}>(`/streams/${encodeURIComponent(streamId)}/studio/host-session`, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n}\n\nexport function createLivoServerClient(options: LivoServerClientOptions) {\n\treturn new LivoServerClient(options);\n}\n\nexport { studioGuestUrl as getGuestLink } from \"./urls\";\n"]}
1
+ {"version":3,"sources":["../src/theme.ts","../src/urls.ts","../src/server.ts"],"names":[],"mappings":"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","file":"server.cjs","sourcesContent":["export type EmbedTheme = {\n\tprimary?: string;\n\tbackground?: string;\n\tforeground?: string;\n\tradius?: string;\n\tfontFamily?: string;\n\tmode?: \"light\" | \"dark\";\n};\n\nconst HEX =\n\t/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\nconst COLOR_FN = /^(?:oklch|rgb|rgba|hsl|hsla)\\([0-9a-zA-Z.\\s%,./-]+\\)$/;\nconst RADIUS = /^\\d+(?:\\.\\d+)?(?:px|rem|em)$/;\nconst FONT = /^[a-zA-Z0-9\\s,\"'-]{1,120}$/;\n\nfunction sanitizeColor(value: unknown): string | undefined {\n\tif (typeof value !== \"string\") return undefined;\n\tconst trimmed = value.trim();\n\tif (HEX.test(trimmed) || COLOR_FN.test(trimmed)) return trimmed;\n\treturn undefined;\n}\n\nexport function sanitizeEmbedTheme(value: unknown): EmbedTheme | null {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\treturn null;\n\t}\n\tconst row = value as Record<string, unknown>;\n\tconst theme: EmbedTheme = {};\n\tconst primary = sanitizeColor(row.primary);\n\tconst background = sanitizeColor(row.background);\n\tconst foreground = sanitizeColor(row.foreground);\n\tif (typeof row.radius === \"string\" && RADIUS.test(row.radius.trim())) {\n\t\ttheme.radius = row.radius.trim();\n\t}\n\tif (typeof row.fontFamily === \"string\" && FONT.test(row.fontFamily.trim())) {\n\t\ttheme.fontFamily = row.fontFamily.trim();\n\t}\n\tif (primary) theme.primary = primary;\n\tif (background) theme.background = background;\n\tif (foreground) theme.foreground = foreground;\n\tif (row.mode === \"light\" || row.mode === \"dark\") theme.mode = row.mode;\n\treturn Object.keys(theme).length > 0 ? theme : null;\n}\n\nexport function encodeEmbedTheme(theme: EmbedTheme): string {\n\tconst json = JSON.stringify(theme);\n\tconst bytes = new TextEncoder().encode(json);\n\tlet binary = \"\";\n\tfor (const byte of bytes) binary += String.fromCharCode(byte);\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=+$/, \"\");\n}\n\nexport function embedThemeCssVars(theme: EmbedTheme): Record<string, string> {\n\tconst vars: Record<string, string> = {};\n\tif (theme.primary) {\n\t\tvars[\"--livo-primary\"] = theme.primary;\n\t\tvars[\"--primary\"] = theme.primary;\n\t}\n\tif (theme.background) {\n\t\tvars[\"--livo-background\"] = theme.background;\n\t\tvars[\"--background\"] = theme.background;\n\t}\n\tif (theme.foreground) {\n\t\tvars[\"--livo-foreground\"] = theme.foreground;\n\t\tvars[\"--foreground\"] = theme.foreground;\n\t}\n\tif (theme.radius) {\n\t\tvars[\"--livo-radius\"] = theme.radius;\n\t\tvars[\"--radius\"] = theme.radius;\n\t}\n\tif (theme.fontFamily) vars[\"--font-sans\"] = theme.fontFamily;\n\treturn vars;\n}\n\nexport function decodeEmbedTheme(\n\traw: string | null | undefined,\n): EmbedTheme | null {\n\tif (!raw) return null;\n\ttry {\n\t\tconst padded = raw.replace(/-/g, \"+\").replace(/_/g, \"/\");\n\t\tconst pad =\n\t\t\tpadded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n\t\tconst binary = atob(padded + pad);\n\t\tconst bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));\n\t\treturn sanitizeEmbedTheme(JSON.parse(new TextDecoder().decode(bytes)));\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nexport function applyEmbedTheme(\n\troot: HTMLElement,\n\ttheme: EmbedTheme | null,\n): void {\n\tif (!theme) return;\n\tif (theme.mode === \"dark\") root.classList.add(\"dark\");\n\tif (theme.mode === \"light\") root.classList.remove(\"dark\");\n\tfor (const [key, value] of Object.entries(embedThemeCssVars(theme))) {\n\t\troot.style.setProperty(key, value);\n\t}\n\tif (theme.fontFamily) root.style.fontFamily = theme.fontFamily;\n}\n","import { encodeEmbedTheme, type EmbedTheme } from \"./theme\";\n\nexport type UrlOrigins = {\n\tplayerOrigin?: string;\n\tappOrigin?: string;\n};\n\nfunction trimSlash(value: string): string {\n\treturn value.replace(/\\/$/, \"\");\n}\n\nexport function playerUrl(\n\tstreamId: string,\n\toptions: UrlOrigins & {\n\t\ttheme?: EmbedTheme;\n\t\tlocale?: string;\n\t\tautoplay?: boolean;\n\t} = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/${streamId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.autoplay) url.searchParams.set(\"autoplay\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function vodPlayerUrl(\n\tvodId: string,\n\toptions: UrlOrigins & {\n\t\ttheme?: EmbedTheme;\n\t\tlocale?: string;\n\t\tautoplay?: boolean;\n\t} = {},\n): string {\n\tconst origin = trimSlash(options.playerOrigin ?? \"https://player.livo.tv\");\n\tconst url = new URL(`${origin}/vod/${vodId}`);\n\tif (options.locale) url.searchParams.set(\"lang\", options.locale);\n\tif (options.autoplay) url.searchParams.set(\"autoplay\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioGuestUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/join/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n\nexport function studioHostUrl(\n\ttoken: string,\n\toptions: UrlOrigins & { theme?: EmbedTheme; embed?: boolean } = {},\n): string {\n\tconst origin = trimSlash(options.appOrigin ?? \"https://app.livo.tv\");\n\tconst url = new URL(`${origin}/studio/host/${token}`);\n\tif (options.embed !== false) url.searchParams.set(\"embed\", \"1\");\n\tif (options.theme)\n\t\turl.searchParams.set(\"theme\", encodeEmbedTheme(options.theme));\n\treturn url.toString();\n}\n","export type LivoServerClientOptions = {\n\tapiUrl?: string;\n\tapiKey: string;\n\tfetchFn?: typeof fetch;\n};\n\nexport class LivoServerClient {\n\tprivate readonly apiUrl: string;\n\tprivate readonly apiKey: string;\n\tprivate readonly fetchFn: typeof fetch;\n\n\tconstructor(options: LivoServerClientOptions) {\n\t\tthis.apiUrl = (options.apiUrl ?? \"https://media-svc.livo.tv\").replace(\n\t\t\t/\\/$/,\n\t\t\t\"\",\n\t\t);\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.fetchFn = options.fetchFn ?? fetch;\n\t}\n\n\tprivate async request<T>(path: string, init: RequestInit = {}): Promise<T> {\n\t\tconst headers = new Headers(init.headers);\n\t\theaders.set(\"X-Api-Key\", this.apiKey);\n\t\theaders.set(\"accept\", \"application/json\");\n\t\tif (init.body && !headers.has(\"content-type\")) {\n\t\t\theaders.set(\"content-type\", \"application/json\");\n\t\t}\n\t\tconst res = await this.fetchFn(`${this.apiUrl}${path}`, {\n\t\t\t...init,\n\t\t\theaders,\n\t\t});\n\t\tconst body: unknown = await res.json().catch(() => ({}));\n\t\tif (!res.ok) {\n\t\t\tconst error = new Error(`Livo API ${res.status}`);\n\t\t\t(error as Error & { status: number; body: unknown }).status = res.status;\n\t\t\t(error as Error & { status: number; body: unknown }).body = body;\n\t\t\tthrow error;\n\t\t}\n\t\treturn body as T;\n\t}\n\n\tcreateStream(input: {\n\t\ttitle: string;\n\t\tmode?: \"instant\" | \"scheduled\";\n\t\tsource?: \"rtmp\" | \"studio\";\n\t\tstartsAt?: number;\n\t\tdurationSeconds?: number;\n\t\tcurtainId?: string;\n\t\tcaptionsEnabled?: boolean;\n\t}) {\n\t\treturn this.request<{ stream: { id: string } }>(\"/streams\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\tgetStream(id: string) {\n\t\treturn this.request<{ stream: { id: string; status: string } }>(\n\t\t\t`/streams/${encodeURIComponent(id)}`,\n\t\t);\n\t}\n\n\tstartStream(id: string) {\n\t\treturn this.request(`/streams/${encodeURIComponent(id)}/start`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tpublishStream(id: string) {\n\t\treturn this.request(`/streams/${encodeURIComponent(id)}/publish`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tpatchStream(\n\t\tid: string,\n\t\tinput: {\n\t\t\ttitle?: string;\n\t\t\tstartsAt?: number | null;\n\t\t\tdurationSeconds?: number | null;\n\t\t\tcurtainId?: string | null;\n\t\t\tcaptionsEnabled?: boolean;\n\t\t},\n\t) {\n\t\treturn this.request(`/streams/${encodeURIComponent(id)}`, {\n\t\t\tmethod: \"PATCH\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\tstopStream(id: string) {\n\t\treturn this.request(`/streams/${encodeURIComponent(id)}/stop`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tlistVods() {\n\t\treturn this.request<{ vods: Array<{ id: string; title: string }> }>(\n\t\t\t\"/vods\",\n\t\t);\n\t}\n\n\tcreateVod(input: {\n\t\tfilename: string;\n\t\tcontentType: string;\n\t\tbyteSize: number;\n\t\tsha256: string;\n\t\ttitle?: string;\n\t\treleaseAt?: number | null;\n\t\tcurtainId?: string | null;\n\t\tcaptionsEnabled?: boolean;\n\t}) {\n\t\treturn this.request<{\n\t\t\tvodId: string;\n\t\t\tuploadId: string;\n\t\t\tpartSize: number;\n\t\t}>(\"/vods\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\trequestVodPartUrl(vodId: string, partNumber: number) {\n\t\treturn this.request<{ uploadUrl: string }>(\n\t\t\t`/vods/${encodeURIComponent(vodId)}/upload-part`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: JSON.stringify({ partNumber }),\n\t\t\t},\n\t\t);\n\t}\n\n\tcompleteVodUpload(vodId: string) {\n\t\treturn this.request(`/vods/${encodeURIComponent(vodId)}/complete-upload`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\ttranscodeVod(vodId: string) {\n\t\treturn this.request(`/vods/${encodeURIComponent(vodId)}/transcode`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tenableVodCaptions(vodId: string) {\n\t\treturn this.request(`/vods/${encodeURIComponent(vodId)}/captions`, {\n\t\t\tmethod: \"POST\",\n\t\t});\n\t}\n\n\tlistCurtains() {\n\t\treturn this.request<{ curtains: Array<{ id: string; title: string }> }>(\n\t\t\t\"/curtains\",\n\t\t);\n\t}\n\n\tcreateCurtain(input: Record<string, unknown>) {\n\t\treturn this.request<{\n\t\t\tcurtain: { id: string };\n\t\t\tuploadId: string;\n\t\t\tpartSize: number;\n\t\t}>(\"/curtains\", {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\tgetOrganizationSettings() {\n\t\treturn this.request<{\n\t\t\tdefaultCurtainId: string | null;\n\t\t\twebhookUrl: string | null;\n\t\t}>(\"/organization/settings\");\n\t}\n\n\tputOrganizationSettings(input: {\n\t\tdefaultCurtainId?: string | null;\n\t\twebhookUrl?: string | null;\n\t\twebhookSecret?: string | null;\n\t}) {\n\t\treturn this.request(\"/organization/settings\", {\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n\n\tmintHostSession(\n\t\tstreamId: string,\n\t\tinput: { displayName: string; picture?: string },\n\t) {\n\t\treturn this.request<{\n\t\t\thostUrl: string;\n\t\t\tguestUrl: string;\n\t\t\texpiresAt: number;\n\t\t}>(`/streams/${encodeURIComponent(streamId)}/studio/host-session`, {\n\t\t\tmethod: \"POST\",\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t}\n}\n\nexport function createLivoServerClient(options: LivoServerClientOptions) {\n\treturn new LivoServerClient(options);\n}\n\nexport { studioGuestUrl as getGuestLink } from \"./urls\";\n"]}