@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.d.cts
CHANGED
|
@@ -164,6 +164,7 @@ type StudioMeetingHandle = {
|
|
|
164
164
|
setStageAccess: (userId: string, on: boolean) => Promise<void>;
|
|
165
165
|
denyStageAccess: (userId: string) => Promise<void>;
|
|
166
166
|
requestStageAccess: () => Promise<void>;
|
|
167
|
+
joinStage: () => Promise<void>;
|
|
167
168
|
leaveStage: () => Promise<void>;
|
|
168
169
|
stageStatus: () => StudioStageStatus;
|
|
169
170
|
stageRequests: () => StudioStageRequest[];
|
package/dist/studio/index.d.ts
CHANGED
|
@@ -164,6 +164,7 @@ type StudioMeetingHandle = {
|
|
|
164
164
|
setStageAccess: (userId: string, on: boolean) => Promise<void>;
|
|
165
165
|
denyStageAccess: (userId: string) => Promise<void>;
|
|
166
166
|
requestStageAccess: () => Promise<void>;
|
|
167
|
+
joinStage: () => Promise<void>;
|
|
167
168
|
leaveStage: () => Promise<void>;
|
|
168
169
|
stageStatus: () => StudioStageStatus;
|
|
169
170
|
stageRequests: () => StudioStageRequest[];
|
package/dist/studio/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
|
-
import { Loader2, MicOff, Mic, Video, VideoOff, X, EyeOff, Eye, Users, MonitorUp, MessageSquare,
|
|
4
|
+
import { Loader2, Share2, MicOff, Mic, Video, VideoOff, X, Link2, EyeOff, Eye, Users, MonitorUp, MessageSquare, Maximize, PictureInPicture2, Settings, PhoneOff, Undo2, Volume2, XIcon, Pin, MessageSquareOff, MoreVertical } from 'lucide-react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
7
7
|
import { clsx } from 'clsx';
|
|
@@ -9,6 +9,8 @@ import { twMerge } from 'tailwind-merge';
|
|
|
9
9
|
import { Button as Button$1 } from '@base-ui/react/button';
|
|
10
10
|
import { cva } from 'class-variance-authority';
|
|
11
11
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
12
|
+
import { mergeProps } from '@base-ui/react/merge-props';
|
|
13
|
+
import { useRender } from '@base-ui/react/use-render';
|
|
12
14
|
import { Menu } from '@base-ui/react/menu';
|
|
13
15
|
import { Tooltip as Tooltip$1 } from '@base-ui/react/tooltip';
|
|
14
16
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
@@ -752,6 +754,58 @@ function postLivoEvent(type, payload) {
|
|
|
752
754
|
window.parent.postMessage({ source: "livo", type, payload }, "*");
|
|
753
755
|
}
|
|
754
756
|
|
|
757
|
+
// src/studio/lib/embed-urls.ts
|
|
758
|
+
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
759
|
+
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/studio/lib/clipboard.ts
|
|
763
|
+
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
764
|
+
if (!clipboard) return false;
|
|
765
|
+
try {
|
|
766
|
+
await clipboard.writeText(value);
|
|
767
|
+
return true;
|
|
768
|
+
} catch {
|
|
769
|
+
return false;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/studio/lib/share.ts
|
|
774
|
+
function isAbort(error) {
|
|
775
|
+
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
776
|
+
}
|
|
777
|
+
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
778
|
+
return share ?? null;
|
|
779
|
+
}
|
|
780
|
+
function canNativeShare(data, share = resolveShareApi()) {
|
|
781
|
+
if (!share) return false;
|
|
782
|
+
if (typeof share.canShare !== "function") return true;
|
|
783
|
+
try {
|
|
784
|
+
return share.canShare(data);
|
|
785
|
+
} catch {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
async function shareOrCopyLink(options, deps) {
|
|
790
|
+
const data = {
|
|
791
|
+
url: options.url,
|
|
792
|
+
...options.title ? { title: options.title } : {},
|
|
793
|
+
...options.text ? { text: options.text } : {}
|
|
794
|
+
};
|
|
795
|
+
const share = resolveShareApi(deps?.share);
|
|
796
|
+
if (canNativeShare(data, share) && share) {
|
|
797
|
+
try {
|
|
798
|
+
await share.share(data);
|
|
799
|
+
return "shared";
|
|
800
|
+
} catch (error) {
|
|
801
|
+
if (isAbort(error)) return "cancelled";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const copy = copyText;
|
|
805
|
+
const ok = await copy(options.url);
|
|
806
|
+
return ok ? "copied" : "failed";
|
|
807
|
+
}
|
|
808
|
+
|
|
755
809
|
// src/studio/lib/status.ts
|
|
756
810
|
function canPublish(status) {
|
|
757
811
|
return status === "preview";
|
|
@@ -1425,9 +1479,124 @@ function StudioRemoteAudio({
|
|
|
1425
1479
|
}, [meeting, participantId]);
|
|
1426
1480
|
return /* @__PURE__ */ jsx("audio", { ref, autoPlay: true, className: "sr-only" });
|
|
1427
1481
|
}
|
|
1482
|
+
function BubbleGroup({ className, ...props }) {
|
|
1483
|
+
return /* @__PURE__ */ jsx(
|
|
1484
|
+
"div",
|
|
1485
|
+
{
|
|
1486
|
+
"data-slot": "bubble-group",
|
|
1487
|
+
className: cn("flex min-w-0 flex-col gap-2", className),
|
|
1488
|
+
...props
|
|
1489
|
+
}
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
var bubbleVariants = cva(
|
|
1493
|
+
"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",
|
|
1494
|
+
{
|
|
1495
|
+
variants: {
|
|
1496
|
+
variant: {
|
|
1497
|
+
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",
|
|
1498
|
+
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%)]",
|
|
1499
|
+
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%)]",
|
|
1500
|
+
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)]",
|
|
1501
|
+
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",
|
|
1502
|
+
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",
|
|
1503
|
+
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"
|
|
1504
|
+
}
|
|
1505
|
+
},
|
|
1506
|
+
defaultVariants: {
|
|
1507
|
+
variant: "default"
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
);
|
|
1511
|
+
function Bubble({
|
|
1512
|
+
variant = "default",
|
|
1513
|
+
align = "start",
|
|
1514
|
+
className,
|
|
1515
|
+
...props
|
|
1516
|
+
}) {
|
|
1517
|
+
return /* @__PURE__ */ jsx(
|
|
1518
|
+
"div",
|
|
1519
|
+
{
|
|
1520
|
+
"data-slot": "bubble",
|
|
1521
|
+
"data-variant": variant,
|
|
1522
|
+
"data-align": align,
|
|
1523
|
+
className: cn(bubbleVariants({ variant }), className),
|
|
1524
|
+
...props
|
|
1525
|
+
}
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
function BubbleContent({
|
|
1529
|
+
className,
|
|
1530
|
+
render,
|
|
1531
|
+
...props
|
|
1532
|
+
}) {
|
|
1533
|
+
return useRender({
|
|
1534
|
+
defaultTagName: "div",
|
|
1535
|
+
props: mergeProps(
|
|
1536
|
+
{
|
|
1537
|
+
className: cn(
|
|
1538
|
+
"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",
|
|
1539
|
+
className
|
|
1540
|
+
)
|
|
1541
|
+
},
|
|
1542
|
+
props
|
|
1543
|
+
),
|
|
1544
|
+
render,
|
|
1545
|
+
state: {
|
|
1546
|
+
slot: "bubble-content"
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
cva(
|
|
1551
|
+
"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",
|
|
1552
|
+
{
|
|
1553
|
+
variants: {
|
|
1554
|
+
side: {
|
|
1555
|
+
top: "top-0 -translate-y-3/4",
|
|
1556
|
+
bottom: "bottom-0 translate-y-3/4"
|
|
1557
|
+
},
|
|
1558
|
+
align: {
|
|
1559
|
+
start: "left-3",
|
|
1560
|
+
end: "right-3"
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
defaultVariants: {
|
|
1564
|
+
side: "bottom",
|
|
1565
|
+
align: "end"
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
);
|
|
1569
|
+
|
|
1570
|
+
// src/studio/lib/chat-groups.ts
|
|
1571
|
+
function groupChatMessages(messages, selfId) {
|
|
1572
|
+
const groups = [];
|
|
1573
|
+
for (const row of messages) {
|
|
1574
|
+
const mine = Boolean(selfId) && row.userId === selfId;
|
|
1575
|
+
const last = groups[groups.length - 1];
|
|
1576
|
+
if (last && last.userId === row.userId && last.mine === mine) {
|
|
1577
|
+
last.rows.push(row);
|
|
1578
|
+
continue;
|
|
1579
|
+
}
|
|
1580
|
+
groups.push({
|
|
1581
|
+
userId: row.userId,
|
|
1582
|
+
name: row.name,
|
|
1583
|
+
mine,
|
|
1584
|
+
rows: [row]
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
return groups;
|
|
1588
|
+
}
|
|
1589
|
+
function senderInitials(name) {
|
|
1590
|
+
const parts = name.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
1591
|
+
if (parts.length === 0) return "?";
|
|
1592
|
+
const first = parts[0];
|
|
1593
|
+
if (parts.length === 1) return first.slice(0, 2).toUpperCase();
|
|
1594
|
+
return `${first[0]}${parts[1][0]}`.toUpperCase();
|
|
1595
|
+
}
|
|
1428
1596
|
function StudioChatPanel({
|
|
1429
1597
|
meeting,
|
|
1430
1598
|
selfId,
|
|
1599
|
+
participants,
|
|
1431
1600
|
isModerator: isModerator2,
|
|
1432
1601
|
labels
|
|
1433
1602
|
}) {
|
|
@@ -1445,6 +1614,17 @@ function StudioChatPanel({
|
|
|
1445
1614
|
);
|
|
1446
1615
|
const pinned = useMemo(() => meeting.pinnedChatMessages(), [meeting, tick]);
|
|
1447
1616
|
const limit = meeting.chatMaxTextLimit();
|
|
1617
|
+
const pictures = useMemo(() => {
|
|
1618
|
+
const map = /* @__PURE__ */ new Map();
|
|
1619
|
+
for (const row of participants) {
|
|
1620
|
+
if (row.userId) map.set(row.userId, row.picture);
|
|
1621
|
+
}
|
|
1622
|
+
return map;
|
|
1623
|
+
}, [participants]);
|
|
1624
|
+
const groups = useMemo(
|
|
1625
|
+
() => groupChatMessages(messages, selfId),
|
|
1626
|
+
[messages, selfId]
|
|
1627
|
+
);
|
|
1448
1628
|
useEffect(() => {
|
|
1449
1629
|
const list = listRef.current;
|
|
1450
1630
|
if (!list) return;
|
|
@@ -1468,6 +1648,44 @@ function StudioChatPanel({
|
|
|
1468
1648
|
);
|
|
1469
1649
|
}
|
|
1470
1650
|
}
|
|
1651
|
+
function messageActions(row, mine) {
|
|
1652
|
+
return /* @__PURE__ */ jsxs(
|
|
1653
|
+
"p",
|
|
1654
|
+
{
|
|
1655
|
+
className: mine ? "text-muted-foreground flex flex-wrap justify-end gap-2 text-xs" : "text-muted-foreground flex flex-wrap gap-2 text-xs",
|
|
1656
|
+
children: [
|
|
1657
|
+
row.isEdited ? /* @__PURE__ */ jsx("span", { children: labels.chatEdited }) : null,
|
|
1658
|
+
mine ? /* @__PURE__ */ jsx(
|
|
1659
|
+
"button",
|
|
1660
|
+
{
|
|
1661
|
+
type: "button",
|
|
1662
|
+
onClick: () => {
|
|
1663
|
+
setEditingId(row.id);
|
|
1664
|
+
setDraft(row.text);
|
|
1665
|
+
},
|
|
1666
|
+
children: labels.chatEdit
|
|
1667
|
+
}
|
|
1668
|
+
) : null,
|
|
1669
|
+
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
1670
|
+
"button",
|
|
1671
|
+
{
|
|
1672
|
+
type: "button",
|
|
1673
|
+
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
1674
|
+
children: labels.chatDelete
|
|
1675
|
+
}
|
|
1676
|
+
) : null,
|
|
1677
|
+
isModerator2 ? /* @__PURE__ */ jsx(
|
|
1678
|
+
"button",
|
|
1679
|
+
{
|
|
1680
|
+
type: "button",
|
|
1681
|
+
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
1682
|
+
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1683
|
+
}
|
|
1684
|
+
) : null
|
|
1685
|
+
]
|
|
1686
|
+
}
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1471
1689
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col", children: [
|
|
1472
1690
|
pinned[0] ? /* @__PURE__ */ jsxs("div", { role: "status", className: "bg-muted border-b px-3 py-2 text-sm", children: [
|
|
1473
1691
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: labels.chatPinned }),
|
|
@@ -1479,7 +1697,7 @@ function StudioChatPanel({
|
|
|
1479
1697
|
ref: listRef,
|
|
1480
1698
|
role: "log",
|
|
1481
1699
|
"aria-live": "polite",
|
|
1482
|
-
className: "flex min-h-0 flex-1 flex-col gap-
|
|
1700
|
+
className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-3",
|
|
1483
1701
|
children: [
|
|
1484
1702
|
/* @__PURE__ */ jsx(
|
|
1485
1703
|
Button,
|
|
@@ -1497,48 +1715,42 @@ function StudioChatPanel({
|
|
|
1497
1715
|
children: labels.chatLoadOlder
|
|
1498
1716
|
}
|
|
1499
1717
|
),
|
|
1500
|
-
|
|
1501
|
-
const
|
|
1718
|
+
groups.map((group) => {
|
|
1719
|
+
const bubbles = group.rows.map((row) => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx(
|
|
1721
|
+
Bubble,
|
|
1722
|
+
{
|
|
1723
|
+
align: group.mine ? "end" : "start",
|
|
1724
|
+
variant: group.mine ? "default" : "secondary",
|
|
1725
|
+
children: /* @__PURE__ */ jsx(BubbleContent, { children: row.text })
|
|
1726
|
+
}
|
|
1727
|
+
),
|
|
1728
|
+
messageActions(row, group.mine)
|
|
1729
|
+
] }, row.id));
|
|
1730
|
+
if (group.mine) {
|
|
1731
|
+
return /* @__PURE__ */ jsx(BubbleGroup, { className: "items-end", children: bubbles }, group.rows[0].id);
|
|
1732
|
+
}
|
|
1733
|
+
const picture = pictures.get(group.userId) ?? null;
|
|
1502
1734
|
return /* @__PURE__ */ jsxs(
|
|
1503
1735
|
"div",
|
|
1504
1736
|
{
|
|
1505
|
-
className:
|
|
1737
|
+
className: "flex max-w-full gap-2 self-start",
|
|
1506
1738
|
children: [
|
|
1507
|
-
/* @__PURE__ */ jsx(
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
"
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
},
|
|
1519
|
-
children: labels.chatEdit
|
|
1520
|
-
}
|
|
1521
|
-
) : null,
|
|
1522
|
-
isModerator2 || mine ? /* @__PURE__ */ jsx(
|
|
1523
|
-
"button",
|
|
1524
|
-
{
|
|
1525
|
-
type: "button",
|
|
1526
|
-
onClick: () => void meeting.deleteChatMessage(row.id).catch(() => toast.error(labels.chatError)),
|
|
1527
|
-
children: labels.chatDelete
|
|
1528
|
-
}
|
|
1529
|
-
) : null,
|
|
1530
|
-
isModerator2 ? /* @__PURE__ */ jsx(
|
|
1531
|
-
"button",
|
|
1532
|
-
{
|
|
1533
|
-
type: "button",
|
|
1534
|
-
onClick: () => void meeting.pinChatMessage(row.id, row.pinned !== true).catch(() => toast.error(labels.chatError)),
|
|
1535
|
-
children: row.pinned ? labels.chatUnpin : labels.chatPin
|
|
1536
|
-
}
|
|
1537
|
-
) : null
|
|
1739
|
+
picture ? /* @__PURE__ */ jsx(
|
|
1740
|
+
"img",
|
|
1741
|
+
{
|
|
1742
|
+
src: picture,
|
|
1743
|
+
alt: "",
|
|
1744
|
+
className: "mt-0.5 size-7 shrink-0 rounded-full object-cover"
|
|
1745
|
+
}
|
|
1746
|
+
) : /* @__PURE__ */ 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) }),
|
|
1747
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1748
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground mb-1 text-xs", children: group.name }),
|
|
1749
|
+
/* @__PURE__ */ jsx(BubbleGroup, { children: bubbles })
|
|
1538
1750
|
] })
|
|
1539
1751
|
]
|
|
1540
1752
|
},
|
|
1541
|
-
|
|
1753
|
+
group.rows[0].id
|
|
1542
1754
|
);
|
|
1543
1755
|
})
|
|
1544
1756
|
]
|
|
@@ -1668,7 +1880,9 @@ function StudioSidePanel({
|
|
|
1668
1880
|
onDeny,
|
|
1669
1881
|
onGrantStage,
|
|
1670
1882
|
onDenyStage,
|
|
1671
|
-
onTakeOffAir
|
|
1883
|
+
onTakeOffAir,
|
|
1884
|
+
guestUrl,
|
|
1885
|
+
onShareGuestLink
|
|
1672
1886
|
}) {
|
|
1673
1887
|
const panelists = participants.filter((row) => isOnStage(row.stageStatus));
|
|
1674
1888
|
const audience = participants.filter((row) => !isOnStage(row.stageStatus));
|
|
@@ -1725,6 +1939,19 @@ function StudioSidePanel({
|
|
|
1725
1939
|
labels
|
|
1726
1940
|
}
|
|
1727
1941
|
) : /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto p-3", children: [
|
|
1942
|
+
guestUrl ? /* @__PURE__ */ jsxs(
|
|
1943
|
+
Button,
|
|
1944
|
+
{
|
|
1945
|
+
type: "button",
|
|
1946
|
+
variant: "outline",
|
|
1947
|
+
className: "w-full",
|
|
1948
|
+
onClick: onShareGuestLink,
|
|
1949
|
+
children: [
|
|
1950
|
+
/* @__PURE__ */ jsx(Link2, {}),
|
|
1951
|
+
labels.shareLink
|
|
1952
|
+
]
|
|
1953
|
+
}
|
|
1954
|
+
) : null,
|
|
1728
1955
|
/* @__PURE__ */ jsx(
|
|
1729
1956
|
StudioWaitingRoom,
|
|
1730
1957
|
{
|
|
@@ -2276,58 +2503,6 @@ function TooltipContent({
|
|
|
2276
2503
|
}
|
|
2277
2504
|
) });
|
|
2278
2505
|
}
|
|
2279
|
-
|
|
2280
|
-
// src/studio/lib/clipboard.ts
|
|
2281
|
-
async function copyText(value, clipboard = globalThis.navigator?.clipboard ?? null) {
|
|
2282
|
-
if (!clipboard) return false;
|
|
2283
|
-
try {
|
|
2284
|
-
await clipboard.writeText(value);
|
|
2285
|
-
return true;
|
|
2286
|
-
} catch {
|
|
2287
|
-
return false;
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
|
|
2291
|
-
// src/studio/lib/share.ts
|
|
2292
|
-
function isAbort(error) {
|
|
2293
|
-
return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
|
|
2294
|
-
}
|
|
2295
|
-
function resolveShareApi(share = typeof navigator === "undefined" ? null : typeof navigator.share === "function" ? navigator : null) {
|
|
2296
|
-
return share ?? null;
|
|
2297
|
-
}
|
|
2298
|
-
function canNativeShare(data, share = resolveShareApi()) {
|
|
2299
|
-
if (!share) return false;
|
|
2300
|
-
if (typeof share.canShare !== "function") return true;
|
|
2301
|
-
try {
|
|
2302
|
-
return share.canShare(data);
|
|
2303
|
-
} catch {
|
|
2304
|
-
return false;
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
async function shareOrCopyLink(options, deps) {
|
|
2308
|
-
const data = {
|
|
2309
|
-
url: options.url,
|
|
2310
|
-
...options.title ? { title: options.title } : {},
|
|
2311
|
-
...options.text ? { text: options.text } : {}
|
|
2312
|
-
};
|
|
2313
|
-
const share = resolveShareApi(deps?.share);
|
|
2314
|
-
if (canNativeShare(data, share) && share) {
|
|
2315
|
-
try {
|
|
2316
|
-
await share.share(data);
|
|
2317
|
-
return "shared";
|
|
2318
|
-
} catch (error) {
|
|
2319
|
-
if (isAbort(error)) return "cancelled";
|
|
2320
|
-
}
|
|
2321
|
-
}
|
|
2322
|
-
const copy = copyText;
|
|
2323
|
-
const ok = await copy(options.url);
|
|
2324
|
-
return ok ? "copied" : "failed";
|
|
2325
|
-
}
|
|
2326
|
-
|
|
2327
|
-
// src/studio/lib/embed-urls.ts
|
|
2328
|
-
function playerWatchUrl(streamId, origin = "https://player.livo.tv") {
|
|
2329
|
-
return `${origin.replace(/\/$/, "")}/${streamId}`;
|
|
2330
|
-
}
|
|
2331
2506
|
function Dialog({ ...props }) {
|
|
2332
2507
|
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
2333
2508
|
}
|
|
@@ -2797,7 +2972,6 @@ function StudioToolbar({
|
|
|
2797
2972
|
stageEnabled,
|
|
2798
2973
|
canLeaveStage,
|
|
2799
2974
|
reducedMotion,
|
|
2800
|
-
playerOrigin,
|
|
2801
2975
|
onToggleMute,
|
|
2802
2976
|
onToggleCamera,
|
|
2803
2977
|
onToggleScreen,
|
|
@@ -2921,41 +3095,6 @@ function StudioToolbar({
|
|
|
2921
3095
|
children: /* @__PURE__ */ jsx(Users, {})
|
|
2922
3096
|
}
|
|
2923
3097
|
) : null,
|
|
2924
|
-
/* @__PURE__ */ jsx(
|
|
2925
|
-
ControlIcon,
|
|
2926
|
-
{
|
|
2927
|
-
label: labels.share,
|
|
2928
|
-
onClick: () => {
|
|
2929
|
-
void navigator.clipboard.writeText(
|
|
2930
|
-
playerWatchUrl(session.stream.id, playerOrigin)
|
|
2931
|
-
);
|
|
2932
|
-
toast.success(labels.shareCopied);
|
|
2933
|
-
},
|
|
2934
|
-
children: /* @__PURE__ */ jsx(Share2, {})
|
|
2935
|
-
}
|
|
2936
|
-
),
|
|
2937
|
-
session.role === "moderator" && session.guestUrl ? /* @__PURE__ */ jsx(
|
|
2938
|
-
ControlIcon,
|
|
2939
|
-
{
|
|
2940
|
-
label: labels.shareLink,
|
|
2941
|
-
onClick: () => {
|
|
2942
|
-
const url = session.guestUrl;
|
|
2943
|
-
if (!url) return;
|
|
2944
|
-
void shareOrCopyLink({
|
|
2945
|
-
url,
|
|
2946
|
-
title: session.stream.title,
|
|
2947
|
-
text: labels.shareGuestText.replace(
|
|
2948
|
-
"{title}",
|
|
2949
|
-
session.stream.title
|
|
2950
|
-
)
|
|
2951
|
-
}).then((result) => {
|
|
2952
|
-
if (result === "copied") toast.success(labels.copied);
|
|
2953
|
-
if (result === "failed") toast.error(labels.shareFailed);
|
|
2954
|
-
});
|
|
2955
|
-
},
|
|
2956
|
-
children: /* @__PURE__ */ jsx(Link2, {})
|
|
2957
|
-
}
|
|
2958
|
-
) : null,
|
|
2959
3098
|
/* @__PURE__ */ jsx(
|
|
2960
3099
|
ControlIcon,
|
|
2961
3100
|
{
|
|
@@ -3728,6 +3867,22 @@ function StudioRoom({
|
|
|
3728
3867
|
trigger: /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", disabled: pending, children: labels.stop })
|
|
3729
3868
|
}
|
|
3730
3869
|
) : null,
|
|
3870
|
+
session.role === "moderator" ? /* @__PURE__ */ jsx(
|
|
3871
|
+
Button,
|
|
3872
|
+
{
|
|
3873
|
+
type: "button",
|
|
3874
|
+
variant: "outline",
|
|
3875
|
+
size: "icon-sm",
|
|
3876
|
+
"aria-label": labels.share,
|
|
3877
|
+
onClick: () => {
|
|
3878
|
+
void navigator.clipboard.writeText(
|
|
3879
|
+
playerWatchUrl(session.stream.id, playerOrigin)
|
|
3880
|
+
);
|
|
3881
|
+
toast.success(labels.shareCopied);
|
|
3882
|
+
},
|
|
3883
|
+
children: /* @__PURE__ */ jsx(Share2, {})
|
|
3884
|
+
}
|
|
3885
|
+
) : null,
|
|
3731
3886
|
embed ? null : chrome
|
|
3732
3887
|
] }),
|
|
3733
3888
|
!stageEnabled && session.role === "guest" ? /* @__PURE__ */ jsx(
|
|
@@ -3815,6 +3970,22 @@ function StudioRoom({
|
|
|
3815
3970
|
onTakeOffAir: (userId) => {
|
|
3816
3971
|
const tile = participants.find((row) => row.userId === userId);
|
|
3817
3972
|
if (tile) void onSetStage(tile, false);
|
|
3973
|
+
},
|
|
3974
|
+
guestUrl: session.guestUrl,
|
|
3975
|
+
onShareGuestLink: () => {
|
|
3976
|
+
const url = session.guestUrl;
|
|
3977
|
+
if (!url) return;
|
|
3978
|
+
void shareOrCopyLink({
|
|
3979
|
+
url,
|
|
3980
|
+
title: session.stream.title,
|
|
3981
|
+
text: labels.shareGuestText.replace(
|
|
3982
|
+
"{title}",
|
|
3983
|
+
session.stream.title
|
|
3984
|
+
)
|
|
3985
|
+
}).then((result) => {
|
|
3986
|
+
if (result === "copied") toast.success(labels.copied);
|
|
3987
|
+
if (result === "failed") toast.error(labels.shareFailed);
|
|
3988
|
+
});
|
|
3818
3989
|
}
|
|
3819
3990
|
}
|
|
3820
3991
|
) : null
|
|
@@ -3833,7 +4004,6 @@ function StudioRoom({
|
|
|
3833
4004
|
session,
|
|
3834
4005
|
meeting,
|
|
3835
4006
|
labels,
|
|
3836
|
-
playerOrigin,
|
|
3837
4007
|
muted,
|
|
3838
4008
|
cameraOn,
|
|
3839
4009
|
screenOn,
|
|
@@ -3863,9 +4033,9 @@ function StudioRoom({
|
|
|
3863
4033
|
onCancelShareRequest: () => setShareGrant("idle"),
|
|
3864
4034
|
onAskStage: () => {
|
|
3865
4035
|
if (session.role === "moderator") {
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
4036
|
+
void Promise.resolve(meeting.joinStage()).catch(
|
|
4037
|
+
() => toast.error(labels.stageError)
|
|
4038
|
+
);
|
|
3869
4039
|
return;
|
|
3870
4040
|
}
|
|
3871
4041
|
void meeting.requestStageAccess();
|
|
@@ -4613,6 +4783,21 @@ function studioMeetingFromRtk(raw) {
|
|
|
4613
4783
|
await meeting.stage?.requestAccess?.();
|
|
4614
4784
|
notifyStage();
|
|
4615
4785
|
},
|
|
4786
|
+
async joinStage() {
|
|
4787
|
+
if (currentStageStatus() === "ON_STAGE") {
|
|
4788
|
+
notifyStage();
|
|
4789
|
+
return;
|
|
4790
|
+
}
|
|
4791
|
+
if (meeting.self?.permissions?.stageAccess !== "ALLOWED") {
|
|
4792
|
+
await meeting.stage?.requestAccess?.();
|
|
4793
|
+
notifyStage();
|
|
4794
|
+
return;
|
|
4795
|
+
}
|
|
4796
|
+
await meeting.stage?.join?.();
|
|
4797
|
+
joinedStageLocally = true;
|
|
4798
|
+
await republishCameraIfOn();
|
|
4799
|
+
notifyStage();
|
|
4800
|
+
},
|
|
4616
4801
|
async leaveStage() {
|
|
4617
4802
|
await meeting.stage?.leave?.();
|
|
4618
4803
|
notifyStage();
|