@livo-tv/sdk 1.1.1 → 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/dist/studio/index.cjs +314 -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 +315 -130
- package/dist/studio/index.js.map +1 -1
- package/dist/styles.css +9 -3
- package/package.json +2 -2
package/dist/studio/index.cjs
CHANGED
|
@@ -11,6 +11,8 @@ var tailwindMerge = require('tailwind-merge');
|
|
|
11
11
|
var button = require('@base-ui/react/button');
|
|
12
12
|
var classVarianceAuthority = require('class-variance-authority');
|
|
13
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');
|
|
14
16
|
var menu = require('@base-ui/react/menu');
|
|
15
17
|
var tooltip = require('@base-ui/react/tooltip');
|
|
16
18
|
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
@@ -776,6 +778,58 @@ function postLivoEvent(type, payload) {
|
|
|
776
778
|
window.parent.postMessage({ source: "livo", type, payload }, "*");
|
|
777
779
|
}
|
|
778
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
|
+
|
|
779
833
|
// src/studio/lib/status.ts
|
|
780
834
|
function canPublish(status) {
|
|
781
835
|
return status === "preview";
|
|
@@ -1449,9 +1503,124 @@ function StudioRemoteAudio({
|
|
|
1449
1503
|
}, [meeting, participantId]);
|
|
1450
1504
|
return /* @__PURE__ */ jsxRuntime.jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
1451
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
|
+
}
|
|
1452
1620
|
function StudioChatPanel({
|
|
1453
1621
|
meeting,
|
|
1454
1622
|
selfId,
|
|
1623
|
+
participants,
|
|
1455
1624
|
isModerator: isModerator2,
|
|
1456
1625
|
labels
|
|
1457
1626
|
}) {
|
|
@@ -1469,6 +1638,17 @@ function StudioChatPanel({
|
|
|
1469
1638
|
);
|
|
1470
1639
|
const pinned = react.useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
1471
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
|
+
);
|
|
1472
1652
|
react.useEffect(() => {
|
|
1473
1653
|
const list = listRef.current;
|
|
1474
1654
|
if (!list) return;
|
|
@@ -1492,6 +1672,44 @@ function StudioChatPanel({
|
|
|
1492
1672
|
);
|
|
1493
1673
|
}
|
|
1494
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
|
+
}
|
|
1495
1713
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1496
1714
|
pinned[0] ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "status", className: "bg-muted border-b px-3 py-2 text-sm", children: [
|
|
1497
1715
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground text-xs", children: labels.chatPinned }),
|
|
@@ -1503,7 +1721,7 @@ function StudioChatPanel({
|
|
|
1503
1721
|
ref: listRef,
|
|
1504
1722
|
role: "log",
|
|
1505
1723
|
"aria-live": "polite",
|
|
1506
|
-
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",
|
|
1507
1725
|
children: [
|
|
1508
1726
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1509
1727
|
Button,
|
|
@@ -1521,48 +1739,42 @@ function StudioChatPanel({
|
|
|
1521
1739
|
children: labels.chatLoadOlder
|
|
1522
1740
|
}
|
|
1523
1741
|
),
|
|
1524
|
-
|
|
1525
|
-
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;
|
|
1526
1758
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1527
1759
|
"div",
|
|
1528
1760
|
{
|
|
1529
|
-
className:
|
|
1761
|
+
className: "flex max-w-full gap-2 self-start",
|
|
1530
1762
|
children: [
|
|
1531
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
"
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
},
|
|
1543
|
-
children: labels.chatEdit
|
|
1544
|
-
}
|
|
1545
|
-
) : null,
|
|
1546
|
-
isModerator2 || mine ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1547
|
-
"button",
|
|
1548
|
-
{
|
|
1549
|
-
type: "button",
|
|
1550
|
-
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => sonner.toast.error(labels.chatError)),
|
|
1551
|
-
children: labels.chatDelete
|
|
1552
|
-
}
|
|
1553
|
-
) : null,
|
|
1554
|
-
isModerator2 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1555
|
-
"button",
|
|
1556
|
-
{
|
|
1557
|
-
type: "button",
|
|
1558
|
-
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => sonner.toast.error(labels.chatError)),
|
|
1559
|
-
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1560
|
-
}
|
|
1561
|
-
) : 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 })
|
|
1562
1774
|
] })
|
|
1563
1775
|
]
|
|
1564
1776
|
},
|
|
1565
|
-
|
|
1777
|
+
group.rows[0].id
|
|
1566
1778
|
);
|
|
1567
1779
|
})
|
|
1568
1780
|
]
|
|
@@ -1692,7 +1904,9 @@ function StudioSidePanel({
|
|
|
1692
1904
|
onDeny,
|
|
1693
1905
|
onGrantStage,
|
|
1694
1906
|
onDenyStage,
|
|
1695
|
-
onTakeOffAir
|
|
1907
|
+
onTakeOffAir,
|
|
1908
|
+
guestUrl,
|
|
1909
|
+
onShareGuestLink
|
|
1696
1910
|
}) {
|
|
1697
1911
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
1698
1912
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
@@ -1749,6 +1963,19 @@ function StudioSidePanel({
|
|
|
1749
1963
|
labels
|
|
1750
1964
|
}
|
|
1751
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,
|
|
1752
1979
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1753
1980
|
StudioWaitingRoom,
|
|
1754
1981
|
{
|
|
@@ -2300,58 +2527,6 @@ function TooltipContent({
|
|
|
2300
2527
|
}
|
|
2301
2528
|
) });
|
|
2302
2529
|
}
|
|
2303
|
-
|
|
2304
|
-
// src/studio/lib/clipboard.ts
|
|
2305
|
-
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
2306
|
-
if (!clipboard) return false;
|
|
2307
|
-
try {
|
|
2308
|
-
await clipboard.writeText(value);
|
|
2309
|
-
return true;
|
|
2310
|
-
} catch {
|
|
2311
|
-
return false;
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
// src/studio/lib/share.ts
|
|
2316
|
-
function isAbort(error) {
|
|
2317
|
-
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
2318
|
-
}
|
|
2319
|
-
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
2320
|
-
return share ?? null;
|
|
2321
|
-
}
|
|
2322
|
-
function canNativeShare(data, share = resolveShareApi()) {
|
|
2323
|
-
if (!share) return false;
|
|
2324
|
-
if (typeof share.canShare !== "function") return true;
|
|
2325
|
-
try {
|
|
2326
|
-
return share.canShare(data);
|
|
2327
|
-
} catch {
|
|
2328
|
-
return false;
|
|
2329
|
-
}
|
|
2330
|
-
}
|
|
2331
|
-
async function shareOrCopyLink(options, deps) {
|
|
2332
|
-
const data = {
|
|
2333
|
-
url: options.url,
|
|
2334
|
-
...options.title ? { title: options.title } : {},
|
|
2335
|
-
...options.text ? { text: options.text } : {}
|
|
2336
|
-
};
|
|
2337
|
-
const share = resolveShareApi(deps?.share);
|
|
2338
|
-
if (canNativeShare(data, share) && share) {
|
|
2339
|
-
try {
|
|
2340
|
-
await share.share(data);
|
|
2341
|
-
return "shared";
|
|
2342
|
-
} catch (error) {
|
|
2343
|
-
if (isAbort(error)) return "cancelled";
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
const copy = copyText;
|
|
2347
|
-
const ok = await copy(options.url);
|
|
2348
|
-
return ok ? "copied" : "failed";
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
// src/studio/lib/embed-urls.ts
|
|
2352
|
-
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
2353
|
-
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
2354
|
-
}
|
|
2355
2530
|
function Dialog({ ...props }) {
|
|
2356
2531
|
return /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Root, { "data-slot": "dialog", ...props });
|
|
2357
2532
|
}
|
|
@@ -2821,7 +2996,6 @@ function StudioToolbar({
|
|
|
2821
2996
|
stageEnabled,
|
|
2822
2997
|
canLeaveStage,
|
|
2823
2998
|
reducedMotion,
|
|
2824
|
-
playerOrigin,
|
|
2825
2999
|
onToggleMute,
|
|
2826
3000
|
onToggleCamera,
|
|
2827
3001
|
onToggleScreen,
|
|
@@ -2945,41 +3119,6 @@ function StudioToolbar({
|
|
|
2945
3119
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, {})
|
|
2946
3120
|
}
|
|
2947
3121
|
) : null,
|
|
2948
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2949
|
-
ControlIcon,
|
|
2950
|
-
{
|
|
2951
|
-
label: labels.share,
|
|
2952
|
-
onClick: () => {
|
|
2953
|
-
void navigator.clipboard.writeText(
|
|
2954
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
2955
|
-
);
|
|
2956
|
-
sonner.toast.success(labels.shareCopied);
|
|
2957
|
-
},
|
|
2958
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Share2, {})
|
|
2959
|
-
}
|
|
2960
|
-
),
|
|
2961
|
-
session.role === "moderator" && session.guestUrl ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
2962
|
-
ControlIcon,
|
|
2963
|
-
{
|
|
2964
|
-
label: labels.shareLink,
|
|
2965
|
-
onClick: () => {
|
|
2966
|
-
const url = session.guestUrl;
|
|
2967
|
-
if (!url) return;
|
|
2968
|
-
void shareOrCopyLink({
|
|
2969
|
-
url,
|
|
2970
|
-
title: session.stream.title,
|
|
2971
|
-
text: labels.shareGuestText.replace(
|
|
2972
|
-
"{title}",
|
|
2973
|
-
session.stream.title
|
|
2974
|
-
)
|
|
2975
|
-
}).then((result) => {
|
|
2976
|
-
if (result === "copied") sonner.toast.success(labels.copied);
|
|
2977
|
-
if (result === "failed") sonner.toast.error(labels.shareFailed);
|
|
2978
|
-
});
|
|
2979
|
-
},
|
|
2980
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, {})
|
|
2981
|
-
}
|
|
2982
|
-
) : null,
|
|
2983
3122
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2984
3123
|
ControlIcon,
|
|
2985
3124
|
{
|
|
@@ -3752,6 +3891,22 @@ function StudioRoom({
|
|
|
3752
3891
|
trigger: /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "destructive", disabled: pending, children: labels.stop })
|
|
3753
3892
|
}
|
|
3754
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,
|
|
3755
3910
|
embed ? null : chrome
|
|
3756
3911
|
] }),
|
|
3757
3912
|
!stageEnabled && session.role === "guest" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3839,6 +3994,22 @@ function StudioRoom({
|
|
|
3839
3994
|
onTakeOffAir: (userId) => {
|
|
3840
3995
|
const tile = participants.find((row) => row.userId === userId);
|
|
3841
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
|
+
});
|
|
3842
4013
|
}
|
|
3843
4014
|
}
|
|
3844
4015
|
) : null
|
|
@@ -3857,7 +4028,6 @@ function StudioRoom({
|
|
|
3857
4028
|
session,
|
|
3858
4029
|
meeting,
|
|
3859
4030
|
labels,
|
|
3860
|
-
playerOrigin,
|
|
3861
4031
|
muted,
|
|
3862
4032
|
cameraOn,
|
|
3863
4033
|
screenOn,
|
|
@@ -3887,9 +4057,9 @@ function StudioRoom({
|
|
|
3887
4057
|
onCancelShareRequest: () => setShareGrant("idle"),
|
|
3888
4058
|
onAskStage: () => {
|
|
3889
4059
|
if (session.role === "moderator") {
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
4060
|
+
void Promise.resolve(meeting.joinStage()).catch(
|
|
4061
|
+
() => sonner.toast.error(labels.stageError)
|
|
4062
|
+
);
|
|
3893
4063
|
return;
|
|
3894
4064
|
}
|
|
3895
4065
|
void meeting.requestStageAccess();
|
|
@@ -4637,6 +4807,21 @@ function studioMeetingFromRtk(raw) {
|
|
|
4637
4807
|
await meeting.stage?.requestAccess?.();
|
|
4638
4808
|
notifyStage();
|
|
4639
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
|
+
},
|
|
4640
4825
|
async leaveStage() {
|
|
4641
4826
|
await meeting.stage?.leave?.();
|
|
4642
4827
|
notifyStage();
|