@livo-tv/sdk 1.0.0 → 1.1.0
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/README.md +4 -1
- package/dist/index.cjs +161 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +163 -21
- package/dist/index.js.map +1 -1
- package/dist/server.cjs +66 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +58 -1
- package/dist/server.d.ts +58 -1
- package/dist/server.js +66 -0
- package/dist/server.js.map +1 -1
- package/dist/studio/index.cjs +5534 -0
- package/dist/studio/index.cjs.map +1 -0
- package/dist/studio/index.d.cts +496 -0
- package/dist/studio/index.d.ts +496 -0
- package/dist/studio/index.js +5489 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/styles.css +73 -0
- package/dist/theme-CjyIlZUs.d.cts +14 -0
- package/dist/theme-CjyIlZUs.d.ts +14 -0
- package/dist/{urls-BXZnKexK.d.cts → urls-CIEpVEE0.d.ts} +4 -11
- package/dist/{urls-BXZnKexK.d.ts → urls-CwCeLPzt.d.cts} +4 -11
- package/package.json +30 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { VideoPlayer } from '@livo-tv/blocks';
|
|
2
|
-
import { useState, useEffect, useMemo } from 'react';
|
|
3
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { VideoPlayer, createPlaybackBeaconClient, CurtainStage } from '@livo-tv/blocks';
|
|
2
|
+
import { useState, useRef, useEffect, useMemo } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
4
|
|
|
5
5
|
// src/player.tsx
|
|
6
6
|
|
|
@@ -16,17 +16,35 @@ function parsePublicStream(value) {
|
|
|
16
16
|
if (typeof value.title !== "string" || typeof value.status !== "string") {
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
+
const organization = isRecord(value.organization) ? {
|
|
20
|
+
name: typeof value.organization.name === "string" ? value.organization.name : null,
|
|
21
|
+
image: typeof value.organization.image === "string" ? value.organization.image : null
|
|
22
|
+
} : { name: null, image: null };
|
|
19
23
|
return {
|
|
20
24
|
id: value.id,
|
|
21
25
|
title: value.title,
|
|
22
26
|
status: value.status,
|
|
23
27
|
playbackUrl: typeof value.playbackUrl === "string" ? value.playbackUrl : null,
|
|
28
|
+
posterUrl: typeof value.posterUrl === "string" ? value.posterUrl : null,
|
|
24
29
|
captionsUrl: typeof value.captionsUrl === "string" ? value.captionsUrl : null,
|
|
25
30
|
viewers: typeof value.viewers === "number" ? value.viewers : 0,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
startsAt: typeof value.startsAt === "number" ? value.startsAt : null,
|
|
32
|
+
durationSeconds: typeof value.durationSeconds === "number" ? value.durationSeconds : null,
|
|
33
|
+
organization,
|
|
34
|
+
curtain: parsePublicCurtain(value.curtain)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function parsePublicCurtain(value) {
|
|
38
|
+
if (!isRecord(value) || typeof value.url !== "string") return null;
|
|
39
|
+
const kind = value.kind === "video" ? "video" : value.kind === "shader" ? "shader" : value.kind === "image" ? "image" : null;
|
|
40
|
+
if (!kind) return null;
|
|
41
|
+
return {
|
|
42
|
+
kind,
|
|
43
|
+
url: value.url,
|
|
44
|
+
hideBranding: value.hideBranding === true,
|
|
45
|
+
hideStatus: value.hideStatus === true,
|
|
46
|
+
config: isRecord(value.config) ? value.config : void 0,
|
|
47
|
+
envBasePath: typeof value.envBasePath === "string" ? value.envBasePath : void 0
|
|
30
48
|
};
|
|
31
49
|
}
|
|
32
50
|
function parsePublicVod(value) {
|
|
@@ -40,10 +58,7 @@ function parsePublicVod(value) {
|
|
|
40
58
|
status: value.status,
|
|
41
59
|
playbackUrl: typeof value.playbackUrl === "string" ? value.playbackUrl : null,
|
|
42
60
|
posterUrl: typeof value.posterUrl === "string" ? value.posterUrl : null,
|
|
43
|
-
curtain:
|
|
44
|
-
kind: value.curtain.kind === "video" ? "video" : "image",
|
|
45
|
-
url: value.curtain.url
|
|
46
|
-
} : null
|
|
61
|
+
curtain: parsePublicCurtain(value.curtain)
|
|
47
62
|
};
|
|
48
63
|
}
|
|
49
64
|
async function fetchPublicStream(apiUrl, streamId, fetchFn = fetch) {
|
|
@@ -133,6 +148,93 @@ function embedThemeCssVars(theme) {
|
|
|
133
148
|
if (theme.fontFamily) vars["--font-sans"] = theme.fontFamily;
|
|
134
149
|
return vars;
|
|
135
150
|
}
|
|
151
|
+
function decodeEmbedTheme(raw) {
|
|
152
|
+
if (!raw) return null;
|
|
153
|
+
try {
|
|
154
|
+
const padded = raw.replace(/-/g, "+").replace(/_/g, "/");
|
|
155
|
+
const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
|
|
156
|
+
const binary = atob(padded + pad);
|
|
157
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
158
|
+
return sanitizeEmbedTheme(JSON.parse(new TextDecoder().decode(bytes)));
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function applyEmbedTheme(root, theme) {
|
|
164
|
+
if (!theme) return;
|
|
165
|
+
if (theme.mode === "dark") root.classList.add("dark");
|
|
166
|
+
if (theme.mode === "light") root.classList.remove("dark");
|
|
167
|
+
for (const [key, value] of Object.entries(embedThemeCssVars(theme))) {
|
|
168
|
+
root.style.setProperty(key, value);
|
|
169
|
+
}
|
|
170
|
+
if (theme.fontFamily) root.style.fontFamily = theme.fontFamily;
|
|
171
|
+
}
|
|
172
|
+
function LivoWaitingRoom({
|
|
173
|
+
title,
|
|
174
|
+
organization,
|
|
175
|
+
curtain,
|
|
176
|
+
primaryStatus,
|
|
177
|
+
hideBranding = false,
|
|
178
|
+
hideStatus = false,
|
|
179
|
+
labels
|
|
180
|
+
}) {
|
|
181
|
+
const waiting = labels?.waiting ?? "Waiting";
|
|
182
|
+
const statusText = [title, primaryStatus || waiting].filter(Boolean).join(". ");
|
|
183
|
+
return /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room", "data-livo-player": "waiting", children: [
|
|
184
|
+
curtain ? /* @__PURE__ */ jsx(
|
|
185
|
+
CurtainStage,
|
|
186
|
+
{
|
|
187
|
+
kind: curtain.kind,
|
|
188
|
+
url: curtain.url,
|
|
189
|
+
config: curtain.kind === "shader" ? curtain.config : void 0,
|
|
190
|
+
envBasePath: curtain.kind === "shader" ? curtain.envBasePath : void 0,
|
|
191
|
+
alt: "",
|
|
192
|
+
allowSound: true,
|
|
193
|
+
className: "livo-waiting-room__stage"
|
|
194
|
+
}
|
|
195
|
+
) : /* @__PURE__ */ jsx("div", { className: "livo-waiting-room__fallback" }),
|
|
196
|
+
/* @__PURE__ */ jsx("p", { className: "livo-sr-only", role: "status", children: statusText }),
|
|
197
|
+
hideBranding && hideStatus ? null : /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room__overlay", children: [
|
|
198
|
+
hideBranding ? null : /* @__PURE__ */ jsxs("div", { className: "livo-waiting-room__brand", children: [
|
|
199
|
+
organization.image ? /* @__PURE__ */ jsx("img", { src: organization.image, alt: "" }) : /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: (organization.name ?? title).slice(0, 1) }),
|
|
200
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
201
|
+
/* @__PURE__ */ jsx("h1", { children: title }),
|
|
202
|
+
organization.name ? /* @__PURE__ */ jsx("p", { children: organization.name }) : null
|
|
203
|
+
] })
|
|
204
|
+
] }),
|
|
205
|
+
hideStatus ? null : /* @__PURE__ */ jsx("p", { children: primaryStatus || waiting })
|
|
206
|
+
] })
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
function sdkSessionId() {
|
|
210
|
+
if (typeof sessionStorage === "undefined") return crypto.randomUUID();
|
|
211
|
+
const key = "livo-player-session";
|
|
212
|
+
const existing = sessionStorage.getItem(key);
|
|
213
|
+
if (existing) return existing;
|
|
214
|
+
const next = crypto.randomUUID();
|
|
215
|
+
sessionStorage.setItem(key, next);
|
|
216
|
+
return next;
|
|
217
|
+
}
|
|
218
|
+
function formatCountdown(ms) {
|
|
219
|
+
const totalSeconds = Math.max(0, Math.ceil(ms / 1e3));
|
|
220
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
221
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
222
|
+
const seconds = totalSeconds % 60;
|
|
223
|
+
if (hours > 0) {
|
|
224
|
+
return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
225
|
+
}
|
|
226
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
227
|
+
}
|
|
228
|
+
function createSdkBeacon(apiUrl, getContext) {
|
|
229
|
+
const endpoint = `${apiUrl.replace(/\/$/, "")}/public/playback/beacon`;
|
|
230
|
+
if (!endpoint.startsWith("http")) return null;
|
|
231
|
+
return createPlaybackBeaconClient({
|
|
232
|
+
endpoint,
|
|
233
|
+
sessionId: sdkSessionId(),
|
|
234
|
+
surface: "sdk",
|
|
235
|
+
getContext
|
|
236
|
+
});
|
|
237
|
+
}
|
|
136
238
|
function LivoPlayer({
|
|
137
239
|
streamId,
|
|
138
240
|
vodId,
|
|
@@ -151,6 +253,20 @@ function LivoPlayer({
|
|
|
151
253
|
const [stream, setStream] = useState(null);
|
|
152
254
|
const [vod, setVod] = useState(null);
|
|
153
255
|
const safeTheme = sanitizeEmbedTheme(theme ?? {});
|
|
256
|
+
const contextRef = useRef({ streamId, vodId });
|
|
257
|
+
contextRef.current = { streamId, vodId };
|
|
258
|
+
const [beacon, setBeacon] = useState(null);
|
|
259
|
+
useEffect(() => {
|
|
260
|
+
const client = createSdkBeacon(apiUrl, () => {
|
|
261
|
+
const ctx = contextRef.current;
|
|
262
|
+
return {
|
|
263
|
+
...ctx.streamId ? { streamId: ctx.streamId } : {},
|
|
264
|
+
...ctx.vodId ? { vodId: ctx.vodId } : {}
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
setBeacon(client);
|
|
268
|
+
return () => client?.destroy();
|
|
269
|
+
}, [apiUrl]);
|
|
154
270
|
useEffect(() => {
|
|
155
271
|
let cancelled = false;
|
|
156
272
|
async function poll() {
|
|
@@ -212,16 +328,20 @@ function LivoPlayer({
|
|
|
212
328
|
);
|
|
213
329
|
const rootClass = ["livo-sdk", className].filter(Boolean).join(" ");
|
|
214
330
|
if (!playbackUrl) {
|
|
215
|
-
|
|
216
|
-
|
|
331
|
+
const startsAt = stream?.startsAt ?? null;
|
|
332
|
+
const remainingMs = startsAt != null ? startsAt - Date.now() : null;
|
|
333
|
+
const primaryStatus = remainingMs != null ? remainingMs > 0 ? `Starts in ${formatCountdown(remainingMs)}` : "Starting soon" : "Waiting";
|
|
334
|
+
return /* @__PURE__ */ jsx("div", { className: rootClass, style, "data-locale": locale, children: /* @__PURE__ */ jsx(
|
|
335
|
+
LivoWaitingRoom,
|
|
217
336
|
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
337
|
+
title: stream?.title ?? vod?.title ?? "Livo",
|
|
338
|
+
organization: stream?.organization ?? { name: null, image: null },
|
|
339
|
+
curtain: stream?.curtain ?? vod?.curtain ?? null,
|
|
340
|
+
primaryStatus,
|
|
341
|
+
hideBranding: (stream?.curtain ?? vod?.curtain)?.hideBranding ?? false,
|
|
342
|
+
hideStatus: (stream?.curtain ?? vod?.curtain)?.hideStatus ?? false
|
|
223
343
|
}
|
|
224
|
-
);
|
|
344
|
+
) });
|
|
225
345
|
}
|
|
226
346
|
return /* @__PURE__ */ jsx(
|
|
227
347
|
"div",
|
|
@@ -240,7 +360,27 @@ function LivoPlayer({
|
|
|
240
360
|
fit: "contain",
|
|
241
361
|
labels,
|
|
242
362
|
liveCaptionsBaseUrl: stream?.status === "public" ? stream.captionsUrl ?? void 0 : void 0,
|
|
243
|
-
|
|
363
|
+
onFirstPlay: () => beacon?.track({ type: "view" }),
|
|
364
|
+
onPlaybackProgress: (currentTime, duration) => {
|
|
365
|
+
beacon?.track({ type: "progress", currentTime, duration });
|
|
366
|
+
onProgress?.({
|
|
367
|
+
currentTime,
|
|
368
|
+
duration: Number.isFinite(duration) ? duration : null
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
onRebuffer: (seconds) => {
|
|
372
|
+
beacon?.track({ type: "rebuffer", rebufferSeconds: seconds });
|
|
373
|
+
},
|
|
374
|
+
onQualityChange: (quality) => {
|
|
375
|
+
beacon?.track({ type: "quality_change", quality });
|
|
376
|
+
},
|
|
377
|
+
onError: (error) => {
|
|
378
|
+
beacon?.track({
|
|
379
|
+
type: "error",
|
|
380
|
+
errorCode: error && typeof error === "object" && "kind" in error ? String(error.kind) : "playback"
|
|
381
|
+
});
|
|
382
|
+
onError?.({ code: "playback" });
|
|
383
|
+
}
|
|
244
384
|
}
|
|
245
385
|
)
|
|
246
386
|
}
|
|
@@ -255,6 +395,7 @@ function playerUrl(streamId, options = {}) {
|
|
|
255
395
|
const origin = trimSlash(options.playerOrigin ?? "https://player.livo.tv");
|
|
256
396
|
const url = new URL(`${origin}/${streamId}`);
|
|
257
397
|
if (options.locale) url.searchParams.set("lang", options.locale);
|
|
398
|
+
if (options.autoplay) url.searchParams.set("autoplay", "1");
|
|
258
399
|
if (options.theme)
|
|
259
400
|
url.searchParams.set("theme", encodeEmbedTheme(options.theme));
|
|
260
401
|
return url.toString();
|
|
@@ -263,6 +404,7 @@ function vodPlayerUrl(vodId, options = {}) {
|
|
|
263
404
|
const origin = trimSlash(options.playerOrigin ?? "https://player.livo.tv");
|
|
264
405
|
const url = new URL(`${origin}/vod/${vodId}`);
|
|
265
406
|
if (options.locale) url.searchParams.set("lang", options.locale);
|
|
407
|
+
if (options.autoplay) url.searchParams.set("autoplay", "1");
|
|
266
408
|
if (options.theme)
|
|
267
409
|
url.searchParams.set("theme", encodeEmbedTheme(options.theme));
|
|
268
410
|
return url.toString();
|
|
@@ -338,6 +480,6 @@ function LivoStudio({
|
|
|
338
480
|
);
|
|
339
481
|
}
|
|
340
482
|
|
|
341
|
-
export { LivoPlayer, LivoStudio, encodeEmbedTheme, playerUrl, sanitizeEmbedTheme, studioGuestUrl, studioHostUrl, vodPlayerUrl };
|
|
483
|
+
export { LivoPlayer, LivoStudio, applyEmbedTheme, decodeEmbedTheme, encodeEmbedTheme, playerUrl, sanitizeEmbedTheme, studioGuestUrl, studioHostUrl, vodPlayerUrl };
|
|
342
484
|
//# sourceMappingURL=index.js.map
|
|
343
485
|
//# 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":";;;;;;;AAoBA,SAAS,SAAS,KAAA,EAAkD;AACnE,EAAA,OAAO,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAK,CAAA;AAC3E;AAEO,SAAS,aAAA,GAAwB;AACvC,EAAA,OAAO,2BAAA;AACR;AAEO,SAAS,kBAAkB,KAAA,EAAqC;AACtE,EAAA,IAAI,CAAC,SAAS,KAAK,CAAA,IAAK,OAAO,KAAA,CAAM,EAAA,KAAO,UAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,YAAY,OAAO,KAAA,CAAM,WAAW,QAAA,EAAU;AACxE,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,OAAO;AAAA,IACN,IAAI,KAAA,CAAM,EAAA;AAAA,IACV,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,aACC,OAAO,KAAA,CAAM,WAAA,KAAgB,QAAA,GAAW,MAAM,WAAA,GAAc,IAAA;AAAA,IAC7D,aACC,OAAO,KAAA,CAAM,WAAA,KAAgB,QAAA,GAAW,MAAM,WAAA,GAAc,IAAA;AAAA,IAC7D,SAAS,OAAO,KAAA,CAAM,OAAA,KAAY,QAAA,GAAW,MAAM,OAAA,GAAU,CAAA;AAAA,IAC7D,OAAA,EACC,SAAS,KAAA,CAAM,OAAO,KAAK,OAAO,KAAA,CAAM,OAAA,CAAQ,GAAA,KAAQ,QAAA,GACrD;AAAA,MACA,IAAA,EAAM,KAAA,CAAM,OAAA,CAAQ,IAAA,KAAS,UAAU,OAAA,GAAU,OAAA;AAAA,MACjD,GAAA,EAAK,MAAM,OAAA,CAAQ;AAAA,KACpB,GACC;AAAA,GACL;AACD;AAEO,SAAS,eAAe,KAAA,EAAkC;AAChE,EAAA,IAAI,CAAC,SAAS,KAAK,CAAA,IAAK,OAAO,KAAA,CAAM,EAAA,KAAO,UAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,KAAA,CAAM,KAAA,KAAU,YAAY,OAAO,KAAA,CAAM,WAAW,QAAA,EAAU;AACxE,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,OAAO;AAAA,IACN,IAAI,KAAA,CAAM,EAAA;AAAA,IACV,OAAO,KAAA,CAAM,KAAA;AAAA,IACb,QAAQ,KAAA,CAAM,MAAA;AAAA,IACd,aACC,OAAO,KAAA,CAAM,WAAA,KAAgB,QAAA,GAAW,MAAM,WAAA,GAAc,IAAA;AAAA,IAC7D,WAAW,OAAO,KAAA,CAAM,SAAA,KAAc,QAAA,GAAW,MAAM,SAAA,GAAY,IAAA;AAAA,IACnE,OAAA,EACC,SAAS,KAAA,CAAM,OAAO,KAAK,OAAO,KAAA,CAAM,OAAA,CAAQ,GAAA,KAAQ,QAAA,GACrD;AAAA,MACA,IAAA,EAAM,KAAA,CAAM,OAAA,CAAQ,IAAA,KAAS,UAAU,OAAA,GAAU,OAAA;AAAA,MACjD,GAAA,EAAK,MAAM,OAAA,CAAQ;AAAA,KACpB,GACC;AAAA,GACL;AACD;AAEA,eAAsB,iBAAA,CACrB,MAAA,EACA,QAAA,EACA,OAAA,GAAwB,KAAA,EACO;AAC/B,EAAA,MAAM,MAAM,MAAM,OAAA;AAAA,IACjB,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,gBAAA,EAAmB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,CAAA;AAAA,IAC3E,EAAE,OAAO,UAAA;AAAW,GACrB;AACA,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,IAAA;AACpB,EAAA,OAAO,iBAAA,CAAkB,MAAM,GAAA,CAAI,IAAA,EAAM,CAAA;AAC1C;AAEA,eAAsB,cAAA,CACrB,MAAA,EACA,KAAA,EACA,OAAA,GAAwB,KAAA,EACI;AAC5B,EAAA,MAAM,MAAM,MAAM,OAAA;AAAA,IACjB,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,aAAA,EAAgB,kBAAA,CAAmB,KAAK,CAAC,CAAA,CAAA;AAAA,IACrE,EAAE,OAAO,UAAA;AAAW,GACrB;AACA,EAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,OAAO,IAAA;AACpB,EAAA,OAAO,cAAA,CAAe,MAAM,GAAA,CAAI,IAAA,EAAM,CAAA;AACvC;AAEA,eAAsB,iBAAA,CACrB,MAAA,EACA,QAAA,EACA,SAAA,EACA,UAAwB,KAAA,EACR;AAChB,EAAA,MAAM,OAAA;AAAA,IACL,CAAA,EAAG,OAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,gBAAA,EAAmB,kBAAA,CAAmB,QAAQ,CAAC,CAAA,SAAA,CAAA;AAAA,IAC3E;AAAA,MACC,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,WAAW;AAAA;AACnC,GACD,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AACxB;;;ACzGA,IAAM,GAAA,GACL,oEAAA;AACD,IAAM,QAAA,GAAW,uDAAA;AACjB,IAAM,MAAA,GAAS,8BAAA;AACf,IAAM,IAAA,GAAO,4BAAA;AAEb,SAAS,cAAc,KAAA,EAAoC;AAC1D,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,EAAU,OAAO,MAAA;AACtC,EAAA,MAAM,OAAA,GAAU,MAAM,IAAA,EAAK;AAC3B,EAAA,IAAI,GAAA,CAAI,KAAK,OAAO,CAAA,IAAK,SAAS,IAAA,CAAK,OAAO,GAAG,OAAO,OAAA;AACxD,EAAA,OAAO,MAAA;AACR;AAEO,SAAS,mBAAmB,KAAA,EAAmC;AACrE,EAAA,IAAI,OAAO,UAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxE,IAAA,OAAO,IAAA;AAAA,EACR;AACA,EAAA,MAAM,GAAA,GAAM,KAAA;AACZ,EAAA,MAAM,QAAoB,EAAC;AAC3B,EAAA,MAAM,OAAA,GAAU,aAAA,CAAc,GAAA,CAAI,OAAO,CAAA;AACzC,EAAA,MAAM,UAAA,GAAa,aAAA,CAAc,GAAA,CAAI,UAAU,CAAA;AAC/C,EAAA,MAAM,UAAA,GAAa,aAAA,CAAc,GAAA,CAAI,UAAU,CAAA;AAC/C,EAAA,IAAI,OAAO,GAAA,CAAI,MAAA,KAAW,QAAA,IAAY,MAAA,CAAO,KAAK,GAAA,CAAI,MAAA,CAAO,IAAA,EAAM,CAAA,EAAG;AACrE,IAAA,KAAA,CAAM,MAAA,GAAS,GAAA,CAAI,MAAA,CAAO,IAAA,EAAK;AAAA,EAChC;AACA,EAAA,IAAI,OAAO,GAAA,CAAI,UAAA,KAAe,QAAA,IAAY,IAAA,CAAK,KAAK,GAAA,CAAI,UAAA,CAAW,IAAA,EAAM,CAAA,EAAG;AAC3E,IAAA,KAAA,CAAM,UAAA,GAAa,GAAA,CAAI,UAAA,CAAW,IAAA,EAAK;AAAA,EACxC;AACA,EAAA,IAAI,OAAA,QAAe,OAAA,GAAU,OAAA;AAC7B,EAAA,IAAI,UAAA,QAAkB,UAAA,GAAa,UAAA;AACnC,EAAA,IAAI,UAAA,QAAkB,UAAA,GAAa,UAAA;AACnC,EAAA,IAAI,GAAA,CAAI,SAAS,OAAA,IAAW,GAAA,CAAI,SAAS,MAAA,EAAQ,KAAA,CAAM,OAAO,GAAA,CAAI,IAAA;AAClE,EAAA,OAAO,OAAO,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA,GAAS,IAAI,KAAA,GAAQ,IAAA;AAChD;AAEO,SAAS,iBAAiB,KAAA,EAA2B;AAC3D,EAAA,MAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACjC,EAAA,MAAM,KAAA,GAAQ,IAAI,WAAA,EAAY,CAAE,OAAO,IAAI,CAAA;AAC3C,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,EAAO,MAAA,IAAU,MAAA,CAAO,aAAa,IAAI,CAAA;AAC5D,EAAA,OAAO,IAAA,CAAK,MAAM,CAAA,CAChB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAClB,OAAA,CAAQ,OAAO,EAAE,CAAA;AACpB;AAEO,SAAS,kBAAkB,KAAA,EAA2C;AAC5E,EAAA,MAAM,OAA+B,EAAC;AACtC,EAAA,IAAI,MAAM,OAAA,EAAS;AAClB,IAAA,IAAA,CAAK,gBAAgB,IAAI,KAAA,CAAM,OAAA;AAC/B,IAAA,IAAA,CAAK,WAAW,IAAI,KAAA,CAAM,OAAA;AAAA,EAC3B;AACA,EAAA,IAAI,MAAM,UAAA,EAAY;AACrB,IAAA,IAAA,CAAK,mBAAmB,IAAI,KAAA,CAAM,UAAA;AAClC,IAAA,IAAA,CAAK,cAAc,IAAI,KAAA,CAAM,UAAA;AAAA,EAC9B;AACA,EAAA,IAAI,MAAM,UAAA,EAAY;AACrB,IAAA,IAAA,CAAK,mBAAmB,IAAI,KAAA,CAAM,UAAA;AAClC,IAAA,IAAA,CAAK,cAAc,IAAI,KAAA,CAAM,UAAA;AAAA,EAC9B;AACA,EAAA,IAAI,MAAM,MAAA,EAAQ;AACjB,IAAA,IAAA,CAAK,eAAe,IAAI,KAAA,CAAM,MAAA;AAC9B,IAAA,IAAA,CAAK,UAAU,IAAI,KAAA,CAAM,MAAA;AAAA,EAC1B;AACA,EAAA,IAAI,KAAA,CAAM,UAAA,EAAY,IAAA,CAAK,aAAa,IAAI,KAAA,CAAM,UAAA;AAClD,EAAA,OAAO,IAAA;AACR;ACxCO,SAAS,UAAA,CAAW;AAAA,EAC1B,QAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAS,aAAA,EAAc;AAAA,EACvB,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA,GAAW,KAAA;AAAA,EACX,KAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,cAAA;AAAA,EACA,UAAA;AAAA,EACA,OAAA;AAAA,EACA;AACD,CAAA,EAAoB;AACnB,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA8B,IAAI,CAAA;AAC9D,EAAA,MAAM,CAAC,GAAA,EAAK,MAAM,CAAA,GAAI,SAA2B,IAAI,CAAA;AACrD,EAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,KAAA,IAAS,EAAE,CAAA;AAEhD,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,eAAe,IAAA,GAAO;AACrB,MAAA,IAAI,QAAA,EAAU;AACb,QAAA,MAAM,IAAA,GAAO,MAAM,iBAAA,CAAkB,MAAA,EAAQ,QAAQ,CAAA;AACrD,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,IAAI,CAAC,IAAA,EAAM;AACV,UAAA,OAAA,GAAU,EAAE,IAAA,EAAM,WAAA,EAAa,CAAA;AAC/B,UAAA;AAAA,QACD;AACA,QAAA,SAAA,CAAU,IAAI,CAAA;AACd,QAAA,cAAA,GAAiB,KAAK,MAAM,CAAA;AAC5B,QAAA,IAAI,IAAA,CAAK,MAAA,KAAW,OAAA,EAAS,OAAA,IAAU;AACvC,QAAA;AAAA,MACD;AACA,MAAA,IAAI,KAAA,EAAO;AACV,QAAA,MAAM,IAAA,GAAO,MAAM,cAAA,CAAe,MAAA,EAAQ,KAAK,CAAA;AAC/C,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,IAAI,CAAC,IAAA,EAAM;AACV,UAAA,OAAA,GAAU,EAAE,IAAA,EAAM,WAAA,EAAa,CAAA;AAC/B,UAAA;AAAA,QACD;AACA,QAAA,MAAA,CAAO,IAAI,CAAA;AACX,QAAA,cAAA,GAAiB,KAAK,MAAM,CAAA;AAAA,MAC7B;AAAA,IACD;AACA,IAAA,KAAK,IAAA,EAAK;AACV,IAAA,MAAM,QAAQ,MAAA,CAAO,WAAA,CAAY,MAAM,KAAK,IAAA,IAAQ,GAAI,CAAA;AACxD,IAAA,OAAO,MAAM;AACZ,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,MAAA,CAAO,cAAc,KAAK,CAAA;AAAA,IAC3B,CAAA;AAAA,EACD,CAAA,EAAG,CAAC,MAAA,EAAQ,OAAA,EAAS,SAAS,cAAA,EAAgB,QAAA,EAAU,KAAK,CAAC,CAAA;AAE9D,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,IAAI,CAAC,QAAA,EAAU;AACf,IAAA,MAAM,SAAA,GAAY,OAAO,UAAA,EAAW;AACpC,IAAA,MAAM,OAAO,MAAM;AAClB,MAAA,KAAK,iBAAA,CAAkB,MAAA,EAAQ,QAAA,EAAU,SAAS,CAAA;AAAA,IACnD,CAAA;AACA,IAAA,IAAA,EAAK;AACL,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,WAAA,CAAY,IAAA,EAAM,IAAM,CAAA;AAC7C,IAAA,OAAO,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA;AAAA,EACxC,CAAA,EAAG,CAAC,MAAA,EAAQ,QAAQ,CAAC,CAAA;AAErB,EAAA,SAAA,CAAU,MAAM;AACf,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,WAAA,CAAY,MAAM;AACtC,MAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,aAAA,CAAc,OAAO,CAAA;AAC5C,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,UAAA,GAAa;AAAA,QACZ,aAAa,KAAA,CAAM,WAAA;AAAA,QACnB,UAAU,MAAA,CAAO,QAAA,CAAS,MAAM,QAAQ,CAAA,GAAI,MAAM,QAAA,GAAW;AAAA,OAC7D,CAAA;AAAA,IACF,GAAG,GAAI,CAAA;AACP,IAAA,OAAO,MAAM,MAAA,CAAO,aAAA,CAAc,KAAK,CAAA;AAAA,EACxC,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AAEf,EAAA,MAAM,WAAA,GAAc,MAAA,EAAQ,WAAA,IAAe,GAAA,EAAK,WAAA;AAChD,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACb,MACC,SAAA,GAAa,iBAAA,CAAkB,SAAS,CAAA,GAAsB,MAAA;AAAA,IAC/D,CAAC,SAAS;AAAA,GACX;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,UAAA,EAAY,SAAS,EAAE,MAAA,CAAO,OAAO,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAElE,EAAA,IAAI,CAAC,WAAA,EAAa;AACjB,IAAA,uBACC,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACA,SAAA,EAAW,SAAA;AAAA,QACX,KAAA;AAAA,QACA,kBAAA,EAAiB,SAAA;AAAA,QACjB,aAAA,EAAa,MAAA;AAAA,QAEZ,QAAA,EAAA,MAAA,EAAQ,KAAA,IAAS,GAAA,EAAK,KAAA,IAAS;AAAA;AAAA,KACjC;AAAA,EAEF;AAEA,EAAA,uBACC,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACA,SAAA,EAAW,SAAA;AAAA,MACX,KAAA;AAAA,MACA,kBAAA,EAAiB,SAAA;AAAA,MACjB,aAAA,EAAa,MAAA;AAAA,MAEb,QAAA,kBAAA,GAAA;AAAA,QAAC,WAAA;AAAA,QAAA;AAAA,UACA,GAAA,EAAK,WAAA;AAAA,UACL,IAAA,EAAM,GAAA,IAAO,MAAA,EAAQ,MAAA,KAAW,UAAU,KAAA,GAAQ,MAAA;AAAA,UAClD,QAAA,EAAU,QAAA;AAAA,UACV,OAAO,KAAA,IAAS,QAAA;AAAA,UAChB,GAAA,EAAI,SAAA;AAAA,UACJ,MAAA;AAAA,UACA,qBACC,MAAA,EAAQ,MAAA,KAAW,QAAA,GACf,MAAA,CAAO,eAAe,MAAA,GACvB,MAAA;AAAA,UAEJ,SAAS,MAAM,OAAA,GAAU,EAAE,IAAA,EAAM,YAAY;AAAA;AAAA;AAC9C;AAAA,GACD;AAEF;;;ACrJA,SAAS,UAAU,KAAA,EAAuB;AACzC,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC/B;AAEO,SAAS,SAAA,CACf,QAAA,EACA,OAAA,GAAgE,EAAC,EACxD;AACT,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,YAAA,IAAgB,wBAAwB,CAAA;AACzE,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,MAAM,CAAA,CAAA,EAAI,QAAQ,CAAA,CAAE,CAAA;AAC3C,EAAA,IAAI,QAAQ,MAAA,EAAQ,GAAA,CAAI,aAAa,GAAA,CAAI,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAC/D,EAAA,IAAI,OAAA,CAAQ,KAAA;AACX,IAAA,GAAA,CAAI,aAAa,GAAA,CAAI,OAAA,EAAS,gBAAA,CAAiB,OAAA,CAAQ,KAAK,CAAC,CAAA;AAC9D,EAAA,OAAO,IAAI,QAAA,EAAS;AACrB;AAEO,SAAS,YAAA,CACf,KAAA,EACA,OAAA,GAAgE,EAAC,EACxD;AACT,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,YAAA,IAAgB,wBAAwB,CAAA;AACzE,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,MAAM,CAAA,KAAA,EAAQ,KAAK,CAAA,CAAE,CAAA;AAC5C,EAAA,IAAI,QAAQ,MAAA,EAAQ,GAAA,CAAI,aAAa,GAAA,CAAI,MAAA,EAAQ,QAAQ,MAAM,CAAA;AAC/D,EAAA,IAAI,OAAA,CAAQ,KAAA;AACX,IAAA,GAAA,CAAI,aAAa,GAAA,CAAI,OAAA,EAAS,gBAAA,CAAiB,OAAA,CAAQ,KAAK,CAAC,CAAA;AAC9D,EAAA,OAAO,IAAI,QAAA,EAAS;AACrB;AAEO,SAAS,cAAA,CACf,KAAA,EACA,OAAA,GAAgE,EAAC,EACxD;AACT,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,SAAA,IAAa,qBAAqB,CAAA;AACnE,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,MAAM,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAE,CAAA;AACpD,EAAA,IAAI,QAAQ,KAAA,KAAU,KAAA,MAAW,YAAA,CAAa,GAAA,CAAI,SAAS,GAAG,CAAA;AAC9D,EAAA,IAAI,OAAA,CAAQ,KAAA;AACX,IAAA,GAAA,CAAI,aAAa,GAAA,CAAI,OAAA,EAAS,gBAAA,CAAiB,OAAA,CAAQ,KAAK,CAAC,CAAA;AAC9D,EAAA,OAAO,IAAI,QAAA,EAAS;AACrB;AAEO,SAAS,aAAA,CACf,KAAA,EACA,OAAA,GAAgE,EAAC,EACxD;AACT,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,OAAA,CAAQ,SAAA,IAAa,qBAAqB,CAAA;AACnE,EAAA,MAAM,MAAM,IAAI,GAAA,CAAI,GAAG,MAAM,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAE,CAAA;AACpD,EAAA,IAAI,QAAQ,KAAA,KAAU,KAAA,MAAW,YAAA,CAAa,GAAA,CAAI,SAAS,GAAG,CAAA;AAC9D,EAAA,IAAI,OAAA,CAAQ,KAAA;AACX,IAAA,GAAA,CAAI,aAAa,GAAA,CAAI,OAAA,EAAS,gBAAA,CAAiB,OAAA,CAAQ,KAAK,CAAC,CAAA;AAC9D,EAAA,OAAO,IAAI,QAAA,EAAS;AACrB;ACtCO,SAAS,UAAA,CAAW;AAAA,EAC1B,UAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA,GAAQ,aAAA;AAAA,EACR;AACD,CAAA,EAAoB;AACnB,EAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,KAAA,IAAS,EAAE,CAAA;AAChD,EAAA,MAAM,GAAA,GAAMA,QAAQ,MAAM;AACzB,IAAA,IAAI,SAAA,EAAW;AACd,MAAA,OAAO,cAAc,SAAA,EAAW;AAAA,QAC/B,SAAA;AAAA,QACA,OAAO,SAAA,IAAa;AAAA,OACpB,CAAA;AAAA,IACF;AACA,IAAA,IAAI,UAAA,EAAY;AACf,MAAA,OAAO,eAAe,UAAA,EAAY;AAAA,QACjC,SAAA;AAAA,QACA,OAAO,SAAA,IAAa;AAAA,OACpB,CAAA;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACR,GAAG,CAAC,SAAA,EAAW,UAAA,EAAY,SAAA,EAAW,SAAS,CAAC,CAAA;AAEhD,EAAAC,UAAU,MAAM;AACf,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,MAAM,IAAA,GAAO,OAAA;AACb,IAAA,SAAS,UAAU,KAAA,EAAqB;AACvC,MAAA,MAAM,OAAO,KAAA,CAAM,IAAA;AACnB,MAAA,IACC,OAAO,IAAA,KAAS,QAAA,IAChB,SAAS,IAAA,IACR,IAAA,CAA6B,WAAW,MAAA,EACxC;AACD,QAAA;AAAA,MACD;AACA,MAAA,IAAA,CAAK;AAAA,QACJ,IAAA,EAAM,MAAA,CAAQ,IAAA,CAA0B,IAAI,CAAA;AAAA,QAC5C,SAAU,IAAA,CAA+B;AAAA,OACzC,CAAA;AAAA,IACF;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,SAAS,CAAA;AAC5C,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,SAAS,CAAA;AAAA,EAC7D,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,uBACCC,GAAAA;AAAA,IAAC,QAAA;AAAA,IAAA;AAAA,MACA,GAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA;AAAA,MACA,OACC,EAAE,MAAA,EAAQ,GAAG,KAAA,EAAO,MAAA,EAAQ,WAAW,GAAA,EAAI;AAAA,MAE5C,KAAA,EAAM,2DAAA;AAAA,MACN,eAAA,EAAe;AAAA;AAAA,GAChB;AAEF","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",
|
package/dist/server.cjs.map
CHANGED
|
@@ -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"]}
|