@livo-tv/sdk 1.1.0 → 1.1.2
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 +1 -1
- package/dist/index.cjs +1 -0
- package/dist/index.js +1 -0
- package/dist/studio/index.cjs +315 -129
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.cts +1 -0
- package/dist/studio/index.d.ts +1 -0
- package/dist/studio/index.js +316 -130
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +9 -3
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ import "@livo-tv/sdk/styles.css";
|
|
|
11
11
|
<LivoStudio guestToken="..." theme={{ primary: "#2563eb" }} />
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
`@livo-tv/sdk/studio`
|
|
14
|
+
`@livo-tv/sdk` and `@livo-tv/sdk/studio` are client entries (the published bundles start with `"use client"`). Import them from `"use client"` files in Next.js App Router — do not import `@livo-tv/sdk/studio` from a Server Component page. Tailwind hosts must `@source` `node_modules/@livo-tv/sdk/dist/*.{js,cjs,mjs}` like `@livo-tv/blocks`. RealtimeKit is an optional peer (`@cloudflare/realtimekit`). Root `<LivoStudio>` remains the iframe fallback.
|
|
15
15
|
|
|
16
16
|
Server (Node / Workers):
|
|
17
17
|
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/dist/studio/index.cjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
var react = require('react');
|
|
@@ -10,6 +11,8 @@ var tailwindMerge = require('tailwind-merge');
|
|
|
10
11
|
var button = require('@base-ui/react/button');
|
|
11
12
|
var classVarianceAuthority = require('class-variance-authority');
|
|
12
13
|
var jsxRuntime = require('react/jsx-runtime');
|
|
14
|
+
var mergeProps = require('@base-ui/react/merge-props');
|
|
15
|
+
var useRender = require('@base-ui/react/use-render');
|
|
13
16
|
var menu = require('@base-ui/react/menu');
|
|
14
17
|
var tooltip = require('@base-ui/react/tooltip');
|
|
15
18
|
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
@@ -775,6 +778,58 @@ function postLivoEvent(type, payload) {
|
|
|
775
778
|
window.parent.postMessage({ source: "livo", type, payload }, "*");
|
|
776
779
|
}
|
|
777
780
|
|
|
781
|
+
// src/studio/lib/embed-urls.ts
|
|
782
|
+
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
783
|
+
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/studio/lib/clipboard.ts
|
|
787
|
+
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
788
|
+
if (!clipboard) return false;
|
|
789
|
+
try {
|
|
790
|
+
await clipboard.writeText(value);
|
|
791
|
+
return true;
|
|
792
|
+
} catch {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/studio/lib/share.ts
|
|
798
|
+
function isAbort(error) {
|
|
799
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
800
|
+
}
|
|
801
|
+
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
802
|
+
return share ?? null;
|
|
803
|
+
}
|
|
804
|
+
function canNativeShare(data, share = resolveShareApi()) {
|
|
805
|
+
if (!share) return false;
|
|
806
|
+
if (typeof share.canShare !== "function") return true;
|
|
807
|
+
try {
|
|
808
|
+
return share.canShare(data);
|
|
809
|
+
} catch {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
async function shareOrCopyLink(options, deps) {
|
|
814
|
+
const data = {
|
|
815
|
+
url: options.url,
|
|
816
|
+
...options.title ? { title: options.title } : {},
|
|
817
|
+
...options.text ? { text: options.text } : {}
|
|
818
|
+
};
|
|
819
|
+
const share = resolveShareApi(deps?.share);
|
|
820
|
+
if (canNativeShare(data, share) && share) {
|
|
821
|
+
try {
|
|
822
|
+
await share.share(data);
|
|
823
|
+
return "shared";
|
|
824
|
+
} catch (error) {
|
|
825
|
+
if (isAbort(error)) return "cancelled";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
const copy = copyText;
|
|
829
|
+
const ok = await copy(options.url);
|
|
830
|
+
return ok ? "copied" : "failed";
|
|
831
|
+
}
|
|
832
|
+
|
|
778
833
|
// src/studio/lib/status.ts
|
|
779
834
|
function canPublish(status) {
|
|
780
835
|
return status === "preview";
|
|
@@ -1448,9 +1503,124 @@ function StudioRemoteAudio({
|
|
|
1448
1503
|
}, [meeting, participantId]);
|
|
1449
1504
|
return /* @__PURE__ */ jsxRuntime.jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
1450
1505
|
}
|
|
1506
|
+
function BubbleGroup({ className, ...props }) {
|
|
1507
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
|
+
"div",
|
|
1509
|
+
{
|
|
1510
|
+
"data-slot": "bubble-group",
|
|
1511
|
+
className: cn("flex min-w-0 flex-col gap-2", className),
|
|
1512
|
+
...props
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
var bubbleVariants = classVarianceAuthority.cva(
|
|
1517
|
+
"group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
|
|
1518
|
+
{
|
|
1519
|
+
variants: {
|
|
1520
|
+
variant: {
|
|
1521
|
+
default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
|
|
1522
|
+
secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
|
|
1523
|
+
muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
|
|
1524
|
+
tinted: "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
|
|
1525
|
+
outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
|
|
1526
|
+
ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
|
|
1527
|
+
destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
|
|
1528
|
+
}
|
|
1529
|
+
},
|
|
1530
|
+
defaultVariants: {
|
|
1531
|
+
variant: "default"
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
);
|
|
1535
|
+
function Bubble({
|
|
1536
|
+
variant = "default",
|
|
1537
|
+
align = "start",
|
|
1538
|
+
className,
|
|
1539
|
+
...props
|
|
1540
|
+
}) {
|
|
1541
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1542
|
+
"div",
|
|
1543
|
+
{
|
|
1544
|
+
"data-slot": "bubble",
|
|
1545
|
+
"data-variant": variant,
|
|
1546
|
+
"data-align": align,
|
|
1547
|
+
className: cn(bubbleVariants({ variant }), className),
|
|
1548
|
+
...props
|
|
1549
|
+
}
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
function BubbleContent({
|
|
1553
|
+
className,
|
|
1554
|
+
render,
|
|
1555
|
+
...props
|
|
1556
|
+
}) {
|
|
1557
|
+
return useRender.useRender({
|
|
1558
|
+
defaultTagName: "div",
|
|
1559
|
+
props: mergeProps.mergeProps(
|
|
1560
|
+
{
|
|
1561
|
+
className: cn(
|
|
1562
|
+
"w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
|
|
1563
|
+
className
|
|
1564
|
+
)
|
|
1565
|
+
},
|
|
1566
|
+
props
|
|
1567
|
+
),
|
|
1568
|
+
render,
|
|
1569
|
+
state: {
|
|
1570
|
+
slot: "bubble-content"
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
classVarianceAuthority.cva(
|
|
1575
|
+
"absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
|
|
1576
|
+
{
|
|
1577
|
+
variants: {
|
|
1578
|
+
side: {
|
|
1579
|
+
top: "top-0 -translate-y-3/4",
|
|
1580
|
+
bottom: "bottom-0 translate-y-3/4"
|
|
1581
|
+
},
|
|
1582
|
+
align: {
|
|
1583
|
+
start: "left-3",
|
|
1584
|
+
end: "right-3"
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
defaultVariants: {
|
|
1588
|
+
side: "bottom",
|
|
1589
|
+
align: "end"
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
);
|
|
1593
|
+
|
|
1594
|
+
// src/studio/lib/chat-groups.ts
|
|
1595
|
+
function groupChatMessages(messages, selfId) {
|
|
1596
|
+
const groups = [];
|
|
1597
|
+
for (const row of messages) {
|
|
1598
|
+
const mine = Boolean(selfId) && row.userId === selfId;
|
|
1599
|
+
const last = groups[groups.length - 1];
|
|
1600
|
+
if (last && last.userId === row.userId && last.mine === mine) {
|
|
1601
|
+
last.rows.push(row);
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
groups.push({
|
|
1605
|
+
userId: row.userId,
|
|
1606
|
+
name: row.name,
|
|
1607
|
+
mine,
|
|
1608
|
+
rows: [row]
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
return groups;
|
|
1612
|
+
}
|
|
1613
|
+
function senderInitials(name) {
|
|
1614
|
+
const parts = name.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
1615
|
+
if (parts.length === 0) return "?";
|
|
1616
|
+
const first = parts[0];
|
|
1617
|
+
if (parts.length === 1) return first.slice(0, 2).toUpperCase();
|
|
1618
|
+
return `${first[0]}${parts[1][0]}`.toUpperCase();
|
|
1619
|
+
}
|
|
1451
1620
|
function StudioChatPanel({
|
|
1452
1621
|
meeting,
|
|
1453
1622
|
selfId,
|
|
1623
|
+
participants,
|
|
1454
1624
|
isModerator: isModerator2,
|
|
1455
1625
|
labels
|
|
1456
1626
|
}) {
|
|
@@ -1468,6 +1638,17 @@ function StudioChatPanel({
|
|
|
1468
1638
|
);
|
|
1469
1639
|
const pinned = react.useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
1470
1640
|
const limit = meeting.chatMaxTextLimit();
|
|
1641
|
+
const pictures = react.useMemo(() => {
|
|
1642
|
+
const map = /* @__PURE__ */ new Map();
|
|
1643
|
+
for (const row of participants) {
|
|
1644
|
+
if (row.userId) map.set(row.userId, row.picture);
|
|
1645
|
+
}
|
|
1646
|
+
return map;
|
|
1647
|
+
}, [participants]);
|
|
1648
|
+
const groups = react.useMemo(
|
|
1649
|
+
() => groupChatMessages(messages, selfId),
|
|
1650
|
+
[messages, selfId]
|
|
1651
|
+
);
|
|
1471
1652
|
react.useEffect(() => {
|
|
1472
1653
|
const list = listRef.current;
|
|
1473
1654
|
if (!list) return;
|
|
@@ -1491,6 +1672,44 @@ function StudioChatPanel({
|
|
|
1491
1672
|
);
|
|
1492
1673
|
}
|
|
1493
1674
|
}
|
|
1675
|
+
function messageActions(row, mine) {
|
|
1676
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1677
|
+
"p",
|
|
1678
|
+
{
|
|
1679
|
+
className: mine ? "text-muted-foreground flex flex-wrap justify-end gap-2 text-xs" : "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
1680
|
+
children: [
|
|
1681
|
+
row.isEdited ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.chatEdited }) : null,
|
|
1682
|
+
mine ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1683
|
+
"button",
|
|
1684
|
+
{
|
|
1685
|
+
type: "button",
|
|
1686
|
+
onClick: () => {
|
|
1687
|
+
setEditingId(row.id);
|
|
1688
|
+
setDraft(row.text);
|
|
1689
|
+
},
|
|
1690
|
+
children: labels.chatEdit
|
|
1691
|
+
}
|
|
1692
|
+
) : null,
|
|
1693
|
+
isModerator2 || mine ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1694
|
+
"button",
|
|
1695
|
+
{
|
|
1696
|
+
type: "button",
|
|
1697
|
+
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => sonner.toast.error(labels.chatError)),
|
|
1698
|
+
children: labels.chatDelete
|
|
1699
|
+
}
|
|
1700
|
+
) : null,
|
|
1701
|
+
isModerator2 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1702
|
+
"button",
|
|
1703
|
+
{
|
|
1704
|
+
type: "button",
|
|
1705
|
+
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => sonner.toast.error(labels.chatError)),
|
|
1706
|
+
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1707
|
+
}
|
|
1708
|
+
) : null
|
|
1709
|
+
]
|
|
1710
|
+
}
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1494
1713
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1495
1714
|
pinned[0] ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "status", className: "bg-muted border-b px-3 py-2 text-sm", children: [
|
|
1496
1715
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: labels.chatPinned }),
|
|
@@ -1502,7 +1721,7 @@ function StudioChatPanel({
|
|
|
1502
1721
|
ref: listRef,
|
|
1503
1722
|
role: "log",
|
|
1504
1723
|
"aria-live": "polite",
|
|
1505
|
-
className: "flex min-h-0 flex-1 flex-col gap-
|
|
1724
|
+
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-3",
|
|
1506
1725
|
children: [
|
|
1507
1726
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1508
1727
|
Button,
|
|
@@ -1520,48 +1739,42 @@ function StudioChatPanel({
|
|
|
1520
1739
|
children: labels.chatLoadOlder
|
|
1521
1740
|
}
|
|
1522
1741
|
),
|
|
1523
|
-
|
|
1524
|
-
const
|
|
1742
|
+
groups.map((group) => {
|
|
1743
|
+
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
1744
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1745
|
+
Bubble,
|
|
1746
|
+
{
|
|
1747
|
+
align: group.mine ? "end" : "start",
|
|
1748
|
+
variant: group.mine ? "default" : "secondary",
|
|
1749
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(BubbleContent, { children: row.text })
|
|
1750
|
+
}
|
|
1751
|
+
),
|
|
1752
|
+
messageActions(row, group.mine)
|
|
1753
|
+
] }, row.id));
|
|
1754
|
+
if (group.mine) {
|
|
1755
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
1756
|
+
}
|
|
1757
|
+
const picture = pictures.get(group.userId) ?? null;
|
|
1525
1758
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1526
1759
|
"div",
|
|
1527
1760
|
{
|
|
1528
|
-
className:
|
|
1761
|
+
className: "flex max-w-full gap-2 self-start",
|
|
1529
1762
|
children: [
|
|
1530
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
"
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
},
|
|
1542
|
-
children: labels.chatEdit
|
|
1543
|
-
}
|
|
1544
|
-
) : null,
|
|
1545
|
-
isModerator2 || mine ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1546
|
-
"button",
|
|
1547
|
-
{
|
|
1548
|
-
type: "button",
|
|
1549
|
-
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => sonner.toast.error(labels.chatError)),
|
|
1550
|
-
children: labels.chatDelete
|
|
1551
|
-
}
|
|
1552
|
-
) : null,
|
|
1553
|
-
isModerator2 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1554
|
-
"button",
|
|
1555
|
-
{
|
|
1556
|
-
type: "button",
|
|
1557
|
-
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => sonner.toast.error(labels.chatError)),
|
|
1558
|
-
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1559
|
-
}
|
|
1560
|
-
) : null
|
|
1763
|
+
picture ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1764
|
+
"img",
|
|
1765
|
+
{
|
|
1766
|
+
src: picture,
|
|
1767
|
+
alt: "",
|
|
1768
|
+
className: "mt-0.5 size-7 shrink-0 rounded-full object-cover"
|
|
1769
|
+
}
|
|
1770
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-muted text-muted-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full text-xs", children: senderInitials(group.name) }),
|
|
1771
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1772
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground mb-1 text-xs", children: group.name }),
|
|
1773
|
+
/* @__PURE__ */ jsxRuntime.jsx(BubbleGroup, { children: bubbles })
|
|
1561
1774
|
] })
|
|
1562
1775
|
]
|
|
1563
1776
|
},
|
|
1564
|
-
|
|
1777
|
+
group.rows[0].id
|
|
1565
1778
|
);
|
|
1566
1779
|
})
|
|
1567
1780
|
]
|
|
@@ -1691,7 +1904,9 @@ function StudioSidePanel({
|
|
|
1691
1904
|
onDeny,
|
|
1692
1905
|
onGrantStage,
|
|
1693
1906
|
onDenyStage,
|
|
1694
|
-
onTakeOffAir
|
|
1907
|
+
onTakeOffAir,
|
|
1908
|
+
guestUrl,
|
|
1909
|
+
onShareGuestLink
|
|
1695
1910
|
}) {
|
|
1696
1911
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
1697
1912
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
@@ -1748,6 +1963,19 @@ function StudioSidePanel({
|
|
|
1748
1963
|
labels
|
|
1749
1964
|
}
|
|
1750
1965
|
) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-3", children: [
|
|
1966
|
+
guestUrl ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1967
|
+
Button,
|
|
1968
|
+
{
|
|
1969
|
+
type: "button",
|
|
1970
|
+
variant: "outline",
|
|
1971
|
+
className: "w-full",
|
|
1972
|
+
onClick: onShareGuestLink,
|
|
1973
|
+
children: [
|
|
1974
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, {}),
|
|
1975
|
+
labels.shareLink
|
|
1976
|
+
]
|
|
1977
|
+
}
|
|
1978
|
+
) : null,
|
|
1751
1979
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1752
1980
|
StudioWaitingRoom,
|
|
1753
1981
|
{
|
|
@@ -2299,58 +2527,6 @@ function TooltipContent({
|
|
|
2299
2527
|
}
|
|
2300
2528
|
) });
|
|
2301
2529
|
}
|
|
2302
|
-
|
|
2303
|
-
// src/studio/lib/clipboard.ts
|
|
2304
|
-
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
2305
|
-
if (!clipboard) return false;
|
|
2306
|
-
try {
|
|
2307
|
-
await clipboard.writeText(value);
|
|
2308
|
-
return true;
|
|
2309
|
-
} catch {
|
|
2310
|
-
return false;
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
// src/studio/lib/share.ts
|
|
2315
|
-
function isAbort(error) {
|
|
2316
|
-
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
2317
|
-
}
|
|
2318
|
-
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
2319
|
-
return share ?? null;
|
|
2320
|
-
}
|
|
2321
|
-
function canNativeShare(data, share = resolveShareApi()) {
|
|
2322
|
-
if (!share) return false;
|
|
2323
|
-
if (typeof share.canShare !== "function") return true;
|
|
2324
|
-
try {
|
|
2325
|
-
return share.canShare(data);
|
|
2326
|
-
} catch {
|
|
2327
|
-
return false;
|
|
2328
|
-
}
|
|
2329
|
-
}
|
|
2330
|
-
async function shareOrCopyLink(options, deps) {
|
|
2331
|
-
const data = {
|
|
2332
|
-
url: options.url,
|
|
2333
|
-
...options.title ? { title: options.title } : {},
|
|
2334
|
-
...options.text ? { text: options.text } : {}
|
|
2335
|
-
};
|
|
2336
|
-
const share = resolveShareApi(deps?.share);
|
|
2337
|
-
if (canNativeShare(data, share) && share) {
|
|
2338
|
-
try {
|
|
2339
|
-
await share.share(data);
|
|
2340
|
-
return "shared";
|
|
2341
|
-
} catch (error) {
|
|
2342
|
-
if (isAbort(error)) return "cancelled";
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
const copy = copyText;
|
|
2346
|
-
const ok = await copy(options.url);
|
|
2347
|
-
return ok ? "copied" : "failed";
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
// src/studio/lib/embed-urls.ts
|
|
2351
|
-
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
2352
|
-
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
2353
|
-
}
|
|
2354
2530
|
function Dialog({ ...props }) {
|
|
2355
2531
|
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Root, { "data-slot": "dialog", ...props });
|
|
2356
2532
|
}
|
|
@@ -2820,7 +2996,6 @@ function StudioToolbar({
|
|
|
2820
2996
|
stageEnabled,
|
|
2821
2997
|
canLeaveStage,
|
|
2822
2998
|
reducedMotion,
|
|
2823
|
-
playerOrigin,
|
|
2824
2999
|
onToggleMute,
|
|
2825
3000
|
onToggleCamera,
|
|
2826
3001
|
onToggleScreen,
|
|
@@ -2944,41 +3119,6 @@ function StudioToolbar({
|
|
|
2944
3119
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, {})
|
|
2945
3120
|
}
|
|
2946
3121
|
) : null,
|
|
2947
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2948
|
-
ControlIcon,
|
|
2949
|
-
{
|
|
2950
|
-
label: labels.share,
|
|
2951
|
-
onClick: () => {
|
|
2952
|
-
void navigator.clipboard.writeText(
|
|
2953
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
2954
|
-
);
|
|
2955
|
-
sonner.toast.success(labels.shareCopied);
|
|
2956
|
-
},
|
|
2957
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Share2, {})
|
|
2958
|
-
}
|
|
2959
|
-
),
|
|
2960
|
-
session.role === "moderator" && session.guestUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2961
|
-
ControlIcon,
|
|
2962
|
-
{
|
|
2963
|
-
label: labels.shareLink,
|
|
2964
|
-
onClick: () => {
|
|
2965
|
-
const url = session.guestUrl;
|
|
2966
|
-
if (!url) return;
|
|
2967
|
-
void shareOrCopyLink({
|
|
2968
|
-
url,
|
|
2969
|
-
title: session.stream.title,
|
|
2970
|
-
text: labels.shareGuestText.replace(
|
|
2971
|
-
"{title}",
|
|
2972
|
-
session.stream.title
|
|
2973
|
-
)
|
|
2974
|
-
}).then((result) => {
|
|
2975
|
-
if (result === "copied") sonner.toast.success(labels.copied);
|
|
2976
|
-
if (result === "failed") sonner.toast.error(labels.shareFailed);
|
|
2977
|
-
});
|
|
2978
|
-
},
|
|
2979
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, {})
|
|
2980
|
-
}
|
|
2981
|
-
) : null,
|
|
2982
3122
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2983
3123
|
ControlIcon,
|
|
2984
3124
|
{
|
|
@@ -3751,6 +3891,22 @@ function StudioRoom({
|
|
|
3751
3891
|
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "destructive", disabled: pending, children: labels.stop })
|
|
3752
3892
|
}
|
|
3753
3893
|
) : null,
|
|
3894
|
+
session.role === "moderator" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
3895
|
+
Button,
|
|
3896
|
+
{
|
|
3897
|
+
type: "button",
|
|
3898
|
+
variant: "outline",
|
|
3899
|
+
size: "icon-sm",
|
|
3900
|
+
"aria-label": labels.share,
|
|
3901
|
+
onClick: () => {
|
|
3902
|
+
void navigator.clipboard.writeText(
|
|
3903
|
+
playerWatchUrl(session.stream.id, playerOrigin)
|
|
3904
|
+
);
|
|
3905
|
+
sonner.toast.success(labels.shareCopied);
|
|
3906
|
+
},
|
|
3907
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Share2, {})
|
|
3908
|
+
}
|
|
3909
|
+
) : null,
|
|
3754
3910
|
embed ? null : chrome
|
|
3755
3911
|
] }),
|
|
3756
3912
|
!stageEnabled && session.role === "guest" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3838,6 +3994,22 @@ function StudioRoom({
|
|
|
3838
3994
|
onTakeOffAir: (userId) => {
|
|
3839
3995
|
const tile = participants.find((row) => row.userId === userId);
|
|
3840
3996
|
if (tile) void onSetStage(tile, false);
|
|
3997
|
+
},
|
|
3998
|
+
guestUrl: session.guestUrl,
|
|
3999
|
+
onShareGuestLink: () => {
|
|
4000
|
+
const url = session.guestUrl;
|
|
4001
|
+
if (!url) return;
|
|
4002
|
+
void shareOrCopyLink({
|
|
4003
|
+
url,
|
|
4004
|
+
title: session.stream.title,
|
|
4005
|
+
text: labels.shareGuestText.replace(
|
|
4006
|
+
"{title}",
|
|
4007
|
+
session.stream.title
|
|
4008
|
+
)
|
|
4009
|
+
}).then((result) => {
|
|
4010
|
+
if (result === "copied") sonner.toast.success(labels.copied);
|
|
4011
|
+
if (result === "failed") sonner.toast.error(labels.shareFailed);
|
|
4012
|
+
});
|
|
3841
4013
|
}
|
|
3842
4014
|
}
|
|
3843
4015
|
) : null
|
|
@@ -3856,7 +4028,6 @@ function StudioRoom({
|
|
|
3856
4028
|
session,
|
|
3857
4029
|
meeting,
|
|
3858
4030
|
labels,
|
|
3859
|
-
playerOrigin,
|
|
3860
4031
|
muted,
|
|
3861
4032
|
cameraOn,
|
|
3862
4033
|
screenOn,
|
|
@@ -3886,9 +4057,9 @@ function StudioRoom({
|
|
|
3886
4057
|
onCancelShareRequest: () => setShareGrant("idle"),
|
|
3887
4058
|
onAskStage: () => {
|
|
3888
4059
|
if (session.role === "moderator") {
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4060
|
+
void Promise.resolve(meeting.joinStage()).catch(
|
|
4061
|
+
() => sonner.toast.error(labels.stageError)
|
|
4062
|
+
);
|
|
3892
4063
|
return;
|
|
3893
4064
|
}
|
|
3894
4065
|
void meeting.requestStageAccess();
|
|
@@ -4636,6 +4807,21 @@ function studioMeetingFromRtk(raw) {
|
|
|
4636
4807
|
await meeting.stage?.requestAccess?.();
|
|
4637
4808
|
notifyStage();
|
|
4638
4809
|
},
|
|
4810
|
+
async joinStage() {
|
|
4811
|
+
if (currentStageStatus() === "ON_STAGE") {
|
|
4812
|
+
notifyStage();
|
|
4813
|
+
return;
|
|
4814
|
+
}
|
|
4815
|
+
if (meeting.self?.permissions?.stageAccess !== "ALLOWED") {
|
|
4816
|
+
await meeting.stage?.requestAccess?.();
|
|
4817
|
+
notifyStage();
|
|
4818
|
+
return;
|
|
4819
|
+
}
|
|
4820
|
+
await meeting.stage?.join?.();
|
|
4821
|
+
joinedStageLocally = true;
|
|
4822
|
+
await republishCameraIfOn();
|
|
4823
|
+
notifyStage();
|
|
4824
|
+
},
|
|
4639
4825
|
async leaveStage() {
|
|
4640
4826
|
await meeting.stage?.leave?.();
|
|
4641
4827
|
notifyStage();
|