@meshagent/meshagent-tailwind 0.41.4 → 0.41.5
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/CHANGELOG.md +3 -0
- package/README.md +33 -0
- package/dist/cjs/chat/chat-thread.js +15 -10
- package/dist/cjs/chat/dataset-chat-thread.d.ts +4 -1
- package/dist/cjs/chat/dataset-chat-thread.js +130 -157
- package/dist/cjs/chat/multi-thread-view.d.ts +4 -1
- package/dist/cjs/chat/multi-thread-view.js +4 -0
- package/dist/cjs/chat/new-chat-thread.d.ts +4 -1
- package/dist/cjs/chat/new-chat-thread.js +43 -87
- package/dist/cjs/file-preview/file-preview.d.ts +6 -0
- package/dist/cjs/file-preview/file-preview.js +220 -0
- package/dist/cjs/meetings/camera-grid.d.ts +46 -0
- package/dist/cjs/meetings/camera-grid.js +435 -0
- package/dist/cjs/meetings/controls.d.ts +4 -2
- package/dist/cjs/meetings/controls.js +9 -3
- package/dist/cjs/meetings/lobby.d.ts +17 -0
- package/dist/cjs/meetings/lobby.js +595 -0
- package/dist/cjs/meetings/meeting-scope.d.ts +7 -6
- package/dist/cjs/meetings/meeting-scope.js +64 -15
- package/dist/cjs/meetings/meeting-view.d.ts +6 -0
- package/dist/cjs/meetings/meeting-view.js +635 -0
- package/dist/cjs/meetings/meetings.d.ts +3 -0
- package/dist/cjs/meetings/meetings.js +3 -0
- package/dist/cjs/meetings/wake-lock.js +2 -2
- package/dist/esm/chat/chat-thread.js +15 -10
- package/dist/esm/chat/dataset-chat-thread.d.ts +4 -1
- package/dist/esm/chat/dataset-chat-thread.js +129 -133
- package/dist/esm/chat/multi-thread-view.d.ts +4 -1
- package/dist/esm/chat/multi-thread-view.js +4 -0
- package/dist/esm/chat/new-chat-thread.d.ts +4 -1
- package/dist/esm/chat/new-chat-thread.js +43 -87
- package/dist/esm/file-preview/file-preview.d.ts +6 -0
- package/dist/esm/file-preview/file-preview.js +220 -0
- package/dist/esm/meetings/camera-grid.d.ts +46 -0
- package/dist/esm/meetings/camera-grid.js +405 -0
- package/dist/esm/meetings/controls.d.ts +4 -2
- package/dist/esm/meetings/controls.js +9 -3
- package/dist/esm/meetings/lobby.d.ts +17 -0
- package/dist/esm/meetings/lobby.js +595 -0
- package/dist/esm/meetings/meeting-scope.d.ts +7 -6
- package/dist/esm/meetings/meeting-scope.js +71 -16
- package/dist/esm/meetings/meeting-view.d.ts +6 -0
- package/dist/esm/meetings/meeting-view.js +630 -0
- package/dist/esm/meetings/meetings.d.ts +3 -0
- package/dist/esm/meetings/meetings.js +3 -0
- package/dist/esm/meetings/wake-lock.js +2 -2
- package/dist/index.css +1 -1
- package/package.json +9 -5
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {
|
|
3
|
+
LocalAudioTrack,
|
|
4
|
+
LocalVideoTrack,
|
|
5
|
+
Room
|
|
6
|
+
} from "livekit-client";
|
|
7
|
+
import { Video, VideoOff, Mic, MicOff, Settings } from "lucide-react";
|
|
8
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
9
|
+
import { Button } from "../components/ui/button";
|
|
10
|
+
import {
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogContent,
|
|
13
|
+
DialogDescription,
|
|
14
|
+
DialogFooter,
|
|
15
|
+
DialogHeader,
|
|
16
|
+
DialogTitle,
|
|
17
|
+
DialogTrigger
|
|
18
|
+
} from "../components/ui/dialog";
|
|
19
|
+
import {
|
|
20
|
+
Select,
|
|
21
|
+
SelectContent,
|
|
22
|
+
SelectItem,
|
|
23
|
+
SelectTrigger,
|
|
24
|
+
SelectValue
|
|
25
|
+
} from "../components/ui/select";
|
|
26
|
+
import { Spinner } from "../components/ui/spinner";
|
|
27
|
+
import { cn } from "../lib/utils";
|
|
28
|
+
import {
|
|
29
|
+
useMeetingController
|
|
30
|
+
} from "./meeting-scope";
|
|
31
|
+
const audioInputStorageKey = "audioInput";
|
|
32
|
+
const audioOutputStorageKey = "audioOutput";
|
|
33
|
+
const videoInputStorageKey = "videoInput";
|
|
34
|
+
const minimumLobbyPendingDuration = 350;
|
|
35
|
+
function storedDeviceId(key) {
|
|
36
|
+
if (typeof window === "undefined") {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return window.localStorage.getItem(key) ?? "";
|
|
40
|
+
}
|
|
41
|
+
function storeDeviceId(key, value) {
|
|
42
|
+
if (typeof window === "undefined") {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (value === "") {
|
|
46
|
+
window.localStorage.removeItem(key);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
window.localStorage.setItem(key, value);
|
|
50
|
+
}
|
|
51
|
+
function deviceLabel(device, fallbackPrefix) {
|
|
52
|
+
const label = device?.label.trim();
|
|
53
|
+
return label != null && label !== "" ? label.replace(/^Default - /u, "") : `Default ${fallbackPrefix}`;
|
|
54
|
+
}
|
|
55
|
+
function devicesForKind(devices, kind) {
|
|
56
|
+
return devices.filter(
|
|
57
|
+
(device) => device.kind === kind && device.deviceId !== ""
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function captureDeviceConstraint(deviceId) {
|
|
61
|
+
return deviceId === "" ? void 0 : { exact: deviceId };
|
|
62
|
+
}
|
|
63
|
+
function videoCaptureOptions(deviceId) {
|
|
64
|
+
const constraint = captureDeviceConstraint(deviceId);
|
|
65
|
+
return constraint == null ? void 0 : { deviceId: constraint };
|
|
66
|
+
}
|
|
67
|
+
function audioCaptureOptions(deviceId) {
|
|
68
|
+
const constraint = captureDeviceConstraint(deviceId);
|
|
69
|
+
return constraint == null ? void 0 : { deviceId: constraint };
|
|
70
|
+
}
|
|
71
|
+
function stopLocalVideoTrack(track) {
|
|
72
|
+
track?.stop();
|
|
73
|
+
}
|
|
74
|
+
function stopLocalAudioTrack(track) {
|
|
75
|
+
track?.stop();
|
|
76
|
+
}
|
|
77
|
+
async function createPreviewVideoTrack(deviceId) {
|
|
78
|
+
const constraints = deviceId === "" ? true : { deviceId: { exact: deviceId } };
|
|
79
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
80
|
+
video: constraints,
|
|
81
|
+
audio: false
|
|
82
|
+
});
|
|
83
|
+
const track = stream.getVideoTracks()[0];
|
|
84
|
+
if (track == null) {
|
|
85
|
+
throw new Error("No video track was created");
|
|
86
|
+
}
|
|
87
|
+
return new LocalVideoTrack(
|
|
88
|
+
track,
|
|
89
|
+
constraints === true ? void 0 : constraints
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
async function createPreviewAudioTrack(deviceId) {
|
|
93
|
+
const constraints = deviceId === "" ? true : { deviceId: { exact: deviceId } };
|
|
94
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
95
|
+
video: false,
|
|
96
|
+
audio: constraints
|
|
97
|
+
});
|
|
98
|
+
const track = stream.getAudioTracks()[0];
|
|
99
|
+
if (track == null) {
|
|
100
|
+
throw new Error("No audio track was created");
|
|
101
|
+
}
|
|
102
|
+
return new LocalAudioTrack(
|
|
103
|
+
track,
|
|
104
|
+
constraints === true ? void 0 : constraints
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
function useLobbyDevices() {
|
|
108
|
+
const [devices, setDevices] = useState([]);
|
|
109
|
+
const refreshDevices = useCallback(() => {
|
|
110
|
+
void Room.getLocalDevices(void 0, false).then(setDevices).catch(() => setDevices([]));
|
|
111
|
+
}, []);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
refreshDevices();
|
|
114
|
+
navigator.mediaDevices?.addEventListener?.("devicechange", refreshDevices);
|
|
115
|
+
return () => navigator.mediaDevices?.removeEventListener?.(
|
|
116
|
+
"devicechange",
|
|
117
|
+
refreshDevices
|
|
118
|
+
);
|
|
119
|
+
}, [refreshDevices]);
|
|
120
|
+
return { devices, refreshDevices };
|
|
121
|
+
}
|
|
122
|
+
async function runWithMinimumPendingDuration(action) {
|
|
123
|
+
const startedAt = Date.now();
|
|
124
|
+
await action();
|
|
125
|
+
const remaining = minimumLobbyPendingDuration - (Date.now() - startedAt);
|
|
126
|
+
if (remaining > 0) {
|
|
127
|
+
await new Promise((resolve) => window.setTimeout(resolve, remaining));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function useMeetingLobbyState() {
|
|
131
|
+
const { devices, refreshDevices } = useLobbyDevices();
|
|
132
|
+
const [loaded, setLoaded] = useState(false);
|
|
133
|
+
const [audioOn, setAudioOn] = useState(true);
|
|
134
|
+
const [videoOn, setVideoOn] = useState(true);
|
|
135
|
+
const [audioProcessing, setAudioProcessing] = useState(false);
|
|
136
|
+
const [videoProcessing, setVideoProcessing] = useState(false);
|
|
137
|
+
const [audioUnavailable, setAudioUnavailable] = useState(false);
|
|
138
|
+
const [videoUnavailable, setVideoUnavailable] = useState(false);
|
|
139
|
+
const [audioDeviceId, setAudioDeviceIdState] = useState(() => storedDeviceId(audioInputStorageKey));
|
|
140
|
+
const [audioOutputDeviceId, setAudioOutputDeviceIdState] = useState(() => storedDeviceId(audioOutputStorageKey));
|
|
141
|
+
const [videoDeviceId, setVideoDeviceIdState] = useState(() => storedDeviceId(videoInputStorageKey));
|
|
142
|
+
const [audioTrack, setAudioTrack] = useState(null);
|
|
143
|
+
const [videoTrack, setVideoTrack] = useState(null);
|
|
144
|
+
const disposedRef = useRef(false);
|
|
145
|
+
const audioTrackRef = useRef(null);
|
|
146
|
+
const videoTrackRef = useRef(null);
|
|
147
|
+
const audioTrackRequestRef = useRef(0);
|
|
148
|
+
const videoTrackRequestRef = useRef(0);
|
|
149
|
+
const audioDeviceIdRef = useRef(audioDeviceId);
|
|
150
|
+
const videoDeviceIdRef = useRef(videoDeviceId);
|
|
151
|
+
const replaceAudioTrackState = useCallback(
|
|
152
|
+
(nextTrack) => {
|
|
153
|
+
const currentTrack = audioTrackRef.current;
|
|
154
|
+
if (currentTrack !== nextTrack) {
|
|
155
|
+
stopLocalAudioTrack(currentTrack);
|
|
156
|
+
}
|
|
157
|
+
if (disposedRef.current) {
|
|
158
|
+
stopLocalAudioTrack(nextTrack);
|
|
159
|
+
audioTrackRef.current = null;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
audioTrackRef.current = nextTrack;
|
|
163
|
+
setAudioTrack(nextTrack);
|
|
164
|
+
},
|
|
165
|
+
[]
|
|
166
|
+
);
|
|
167
|
+
const replaceVideoTrackState = useCallback(
|
|
168
|
+
(nextTrack) => {
|
|
169
|
+
const currentTrack = videoTrackRef.current;
|
|
170
|
+
if (currentTrack !== nextTrack) {
|
|
171
|
+
stopLocalVideoTrack(currentTrack);
|
|
172
|
+
}
|
|
173
|
+
if (disposedRef.current) {
|
|
174
|
+
stopLocalVideoTrack(nextTrack);
|
|
175
|
+
videoTrackRef.current = null;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
videoTrackRef.current = nextTrack;
|
|
179
|
+
setVideoTrack(nextTrack);
|
|
180
|
+
},
|
|
181
|
+
[]
|
|
182
|
+
);
|
|
183
|
+
const replaceAudioTrack = useCallback(
|
|
184
|
+
async (deviceId) => {
|
|
185
|
+
const requestId = audioTrackRequestRef.current + 1;
|
|
186
|
+
audioTrackRequestRef.current = requestId;
|
|
187
|
+
setAudioProcessing(true);
|
|
188
|
+
await runWithMinimumPendingDuration(async () => {
|
|
189
|
+
try {
|
|
190
|
+
const nextTrack = await createPreviewAudioTrack(deviceId);
|
|
191
|
+
if (disposedRef.current || requestId !== audioTrackRequestRef.current) {
|
|
192
|
+
stopLocalAudioTrack(nextTrack);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
replaceAudioTrackState(nextTrack);
|
|
196
|
+
setAudioUnavailable(false);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (!disposedRef.current && requestId === audioTrackRequestRef.current) {
|
|
199
|
+
replaceAudioTrackState(null);
|
|
200
|
+
setAudioUnavailable(true);
|
|
201
|
+
console.warn("Unable to start microphone preview", error);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
if (!disposedRef.current && requestId === audioTrackRequestRef.current) {
|
|
206
|
+
setAudioProcessing(false);
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
[replaceAudioTrackState]
|
|
210
|
+
);
|
|
211
|
+
const replaceVideoTrack = useCallback(
|
|
212
|
+
async (deviceId) => {
|
|
213
|
+
const requestId = videoTrackRequestRef.current + 1;
|
|
214
|
+
videoTrackRequestRef.current = requestId;
|
|
215
|
+
setVideoProcessing(true);
|
|
216
|
+
await runWithMinimumPendingDuration(async () => {
|
|
217
|
+
try {
|
|
218
|
+
const nextTrack = await createPreviewVideoTrack(deviceId);
|
|
219
|
+
if (disposedRef.current || requestId !== videoTrackRequestRef.current) {
|
|
220
|
+
stopLocalVideoTrack(nextTrack);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
replaceVideoTrackState(nextTrack);
|
|
224
|
+
setVideoUnavailable(false);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
if (!disposedRef.current && requestId === videoTrackRequestRef.current) {
|
|
227
|
+
replaceVideoTrackState(null);
|
|
228
|
+
setVideoUnavailable(true);
|
|
229
|
+
console.warn("Unable to start camera preview", error);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
if (!disposedRef.current && requestId === videoTrackRequestRef.current) {
|
|
234
|
+
setVideoProcessing(false);
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
[replaceVideoTrackState]
|
|
238
|
+
);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
audioDeviceIdRef.current = audioDeviceId;
|
|
241
|
+
}, [audioDeviceId]);
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
videoDeviceIdRef.current = videoDeviceId;
|
|
244
|
+
}, [videoDeviceId]);
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
let cancelled = false;
|
|
247
|
+
disposedRef.current = false;
|
|
248
|
+
void Promise.all([
|
|
249
|
+
replaceAudioTrack(audioDeviceIdRef.current),
|
|
250
|
+
replaceVideoTrack(videoDeviceIdRef.current)
|
|
251
|
+
]).finally(() => {
|
|
252
|
+
if (!cancelled) {
|
|
253
|
+
setLoaded(true);
|
|
254
|
+
refreshDevices();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
return () => {
|
|
258
|
+
cancelled = true;
|
|
259
|
+
disposedRef.current = true;
|
|
260
|
+
audioTrackRequestRef.current += 1;
|
|
261
|
+
videoTrackRequestRef.current += 1;
|
|
262
|
+
replaceAudioTrackState(null);
|
|
263
|
+
replaceVideoTrackState(null);
|
|
264
|
+
};
|
|
265
|
+
}, [
|
|
266
|
+
refreshDevices,
|
|
267
|
+
replaceAudioTrack,
|
|
268
|
+
replaceAudioTrackState,
|
|
269
|
+
replaceVideoTrack,
|
|
270
|
+
replaceVideoTrackState
|
|
271
|
+
]);
|
|
272
|
+
const setAudioDeviceId = useCallback(
|
|
273
|
+
(deviceId) => {
|
|
274
|
+
setAudioDeviceIdState(deviceId);
|
|
275
|
+
storeDeviceId(audioInputStorageKey, deviceId);
|
|
276
|
+
void replaceAudioTrack(deviceId);
|
|
277
|
+
},
|
|
278
|
+
[replaceAudioTrack]
|
|
279
|
+
);
|
|
280
|
+
const setAudioOutputDeviceId = useCallback((deviceId) => {
|
|
281
|
+
setAudioOutputDeviceIdState(deviceId);
|
|
282
|
+
storeDeviceId(audioOutputStorageKey, deviceId);
|
|
283
|
+
}, []);
|
|
284
|
+
const setVideoDeviceId = useCallback(
|
|
285
|
+
(deviceId) => {
|
|
286
|
+
setVideoDeviceIdState(deviceId);
|
|
287
|
+
storeDeviceId(videoInputStorageKey, deviceId);
|
|
288
|
+
void replaceVideoTrack(deviceId);
|
|
289
|
+
},
|
|
290
|
+
[replaceVideoTrack]
|
|
291
|
+
);
|
|
292
|
+
const toggleAudio = useCallback(() => {
|
|
293
|
+
if (audioProcessing) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (audioTrack != null) {
|
|
297
|
+
replaceAudioTrackState(null);
|
|
298
|
+
setAudioOn(false);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
setAudioOn(true);
|
|
302
|
+
void replaceAudioTrack(audioDeviceId);
|
|
303
|
+
}, [
|
|
304
|
+
audioDeviceId,
|
|
305
|
+
audioProcessing,
|
|
306
|
+
audioTrack,
|
|
307
|
+
replaceAudioTrack,
|
|
308
|
+
replaceAudioTrackState
|
|
309
|
+
]);
|
|
310
|
+
const toggleVideo = useCallback(() => {
|
|
311
|
+
if (videoProcessing) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (videoTrack != null) {
|
|
315
|
+
replaceVideoTrackState(null);
|
|
316
|
+
setVideoOn(false);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
setVideoOn(true);
|
|
320
|
+
void replaceVideoTrack(videoDeviceId);
|
|
321
|
+
}, [
|
|
322
|
+
replaceVideoTrack,
|
|
323
|
+
replaceVideoTrackState,
|
|
324
|
+
videoDeviceId,
|
|
325
|
+
videoProcessing,
|
|
326
|
+
videoTrack
|
|
327
|
+
]);
|
|
328
|
+
return {
|
|
329
|
+
loaded,
|
|
330
|
+
videoTrack,
|
|
331
|
+
audioTrack,
|
|
332
|
+
audioOn,
|
|
333
|
+
videoOn,
|
|
334
|
+
audioProcessing,
|
|
335
|
+
videoProcessing,
|
|
336
|
+
audioUnavailable,
|
|
337
|
+
videoUnavailable,
|
|
338
|
+
audioDeviceId,
|
|
339
|
+
audioOutputDeviceId,
|
|
340
|
+
videoDeviceId,
|
|
341
|
+
devices,
|
|
342
|
+
setAudioDeviceId,
|
|
343
|
+
setAudioOutputDeviceId,
|
|
344
|
+
setVideoDeviceId,
|
|
345
|
+
refreshDevices,
|
|
346
|
+
toggleAudio,
|
|
347
|
+
toggleVideo
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function useAttachedPreviewVideo(track) {
|
|
351
|
+
const attachedElementRef = useRef(null);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
const element = attachedElementRef.current;
|
|
354
|
+
if (element == null || track == null) {
|
|
355
|
+
return void 0;
|
|
356
|
+
}
|
|
357
|
+
track.attach(element);
|
|
358
|
+
return () => {
|
|
359
|
+
track.detach(element);
|
|
360
|
+
};
|
|
361
|
+
}, [track]);
|
|
362
|
+
return useCallback((element) => {
|
|
363
|
+
attachedElementRef.current = element;
|
|
364
|
+
}, []);
|
|
365
|
+
}
|
|
366
|
+
function LobbyDeviceSelect({
|
|
367
|
+
label,
|
|
368
|
+
devices,
|
|
369
|
+
kind,
|
|
370
|
+
value,
|
|
371
|
+
onValueChange
|
|
372
|
+
}) {
|
|
373
|
+
const options = devicesForKind(devices, kind);
|
|
374
|
+
const selectedValue = value === "" ? "default" : value;
|
|
375
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
376
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: label }),
|
|
377
|
+
/* @__PURE__ */ jsxs(
|
|
378
|
+
Select,
|
|
379
|
+
{
|
|
380
|
+
value: selectedValue,
|
|
381
|
+
onValueChange: (nextValue) => onValueChange(nextValue === "default" ? "" : nextValue),
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: deviceLabel(options[0] ?? null, label) }) }),
|
|
384
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
385
|
+
/* @__PURE__ */ jsxs(SelectItem, { value: "default", children: [
|
|
386
|
+
"Default ",
|
|
387
|
+
label.toLowerCase()
|
|
388
|
+
] }),
|
|
389
|
+
options.map((device) => /* @__PURE__ */ jsx(SelectItem, { value: device.deviceId, children: deviceLabel(device, label) }, device.deviceId))
|
|
390
|
+
] })
|
|
391
|
+
]
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
] });
|
|
395
|
+
}
|
|
396
|
+
function LobbyDeviceSettings({ state }) {
|
|
397
|
+
const hasAudioOutput = useMemo(
|
|
398
|
+
() => state.devices.some((device) => device.kind === "audiooutput"),
|
|
399
|
+
[state.devices]
|
|
400
|
+
);
|
|
401
|
+
return /* @__PURE__ */ jsxs(Dialog, { children: [
|
|
402
|
+
/* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
403
|
+
Button,
|
|
404
|
+
{
|
|
405
|
+
type: "button",
|
|
406
|
+
title: "Device settings",
|
|
407
|
+
"aria-label": "Device settings",
|
|
408
|
+
variant: "outline",
|
|
409
|
+
className: "h-10",
|
|
410
|
+
children: [
|
|
411
|
+
/* @__PURE__ */ jsx(Settings, {}),
|
|
412
|
+
"Device settings"
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
) }),
|
|
416
|
+
/* @__PURE__ */ jsxs(DialogContent, { className: "max-w-[min(92vw,560px)]", children: [
|
|
417
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
418
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Device settings" }),
|
|
419
|
+
/* @__PURE__ */ jsx(DialogDescription, { children: "Choose the devices used for this meeting." })
|
|
420
|
+
] }),
|
|
421
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
422
|
+
/* @__PURE__ */ jsx(
|
|
423
|
+
LobbyDeviceSelect,
|
|
424
|
+
{
|
|
425
|
+
label: "Camera",
|
|
426
|
+
devices: state.devices,
|
|
427
|
+
kind: "videoinput",
|
|
428
|
+
value: state.videoDeviceId,
|
|
429
|
+
onValueChange: state.setVideoDeviceId
|
|
430
|
+
}
|
|
431
|
+
),
|
|
432
|
+
/* @__PURE__ */ jsx(
|
|
433
|
+
LobbyDeviceSelect,
|
|
434
|
+
{
|
|
435
|
+
label: "Microphone",
|
|
436
|
+
devices: state.devices,
|
|
437
|
+
kind: "audioinput",
|
|
438
|
+
value: state.audioDeviceId,
|
|
439
|
+
onValueChange: state.setAudioDeviceId
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
hasAudioOutput && /* @__PURE__ */ jsx(
|
|
443
|
+
LobbyDeviceSelect,
|
|
444
|
+
{
|
|
445
|
+
label: "Speaker",
|
|
446
|
+
devices: state.devices,
|
|
447
|
+
kind: "audiooutput",
|
|
448
|
+
value: state.audioOutputDeviceId,
|
|
449
|
+
onValueChange: state.setAudioOutputDeviceId
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
] }),
|
|
453
|
+
/* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: state.refreshDevices, children: "Refresh" }) })
|
|
454
|
+
] })
|
|
455
|
+
] });
|
|
456
|
+
}
|
|
457
|
+
function LobbyToggleButton({
|
|
458
|
+
text,
|
|
459
|
+
on,
|
|
460
|
+
unavailable,
|
|
461
|
+
loading,
|
|
462
|
+
icon,
|
|
463
|
+
offIcon,
|
|
464
|
+
onClick
|
|
465
|
+
}) {
|
|
466
|
+
return /* @__PURE__ */ jsx(
|
|
467
|
+
Button,
|
|
468
|
+
{
|
|
469
|
+
type: "button",
|
|
470
|
+
title: text,
|
|
471
|
+
"aria-label": text,
|
|
472
|
+
size: "icon",
|
|
473
|
+
variant: unavailable ? "destructive" : on ? "default" : "outline",
|
|
474
|
+
disabled: loading,
|
|
475
|
+
onClick,
|
|
476
|
+
className: cn(
|
|
477
|
+
"h-10 w-10",
|
|
478
|
+
on && !unavailable ? "bg-emerald-600 text-white hover:bg-emerald-700" : null
|
|
479
|
+
),
|
|
480
|
+
children: loading ? /* @__PURE__ */ jsx(Spinner, { className: "h-5 w-5" }) : on ? icon : offIcon
|
|
481
|
+
}
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
function joinOptions(state) {
|
|
485
|
+
const enableVideo = state.videoTrack != null && !state.videoUnavailable;
|
|
486
|
+
const enableAudio = state.audioTrack != null && !state.audioUnavailable;
|
|
487
|
+
return {
|
|
488
|
+
enableVideo,
|
|
489
|
+
enableAudio,
|
|
490
|
+
videoUnavailable: state.videoUnavailable,
|
|
491
|
+
audioUnavailable: state.audioUnavailable,
|
|
492
|
+
videoDeviceId: state.videoDeviceId === "" ? void 0 : state.videoDeviceId,
|
|
493
|
+
audioDeviceId: state.audioDeviceId === "" ? void 0 : state.audioDeviceId,
|
|
494
|
+
audioOutputDeviceId: state.audioOutputDeviceId === "" ? void 0 : state.audioOutputDeviceId
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
function meetingFastConnectOptions(options) {
|
|
498
|
+
return {
|
|
499
|
+
camera: {
|
|
500
|
+
enabled: options.enableVideo,
|
|
501
|
+
options: videoCaptureOptions(options.videoDeviceId ?? "")
|
|
502
|
+
},
|
|
503
|
+
microphone: {
|
|
504
|
+
enabled: options.enableAudio,
|
|
505
|
+
options: audioCaptureOptions(options.audioDeviceId ?? "")
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
function MeetingLobby({
|
|
510
|
+
controller: providedController,
|
|
511
|
+
onCancel,
|
|
512
|
+
onJoin
|
|
513
|
+
}) {
|
|
514
|
+
const controller = useMeetingController(providedController);
|
|
515
|
+
const state = useMeetingLobbyState();
|
|
516
|
+
const previewRef = useAttachedPreviewVideo(state.videoTrack);
|
|
517
|
+
const videoPending = state.videoOn && state.videoTrack == null && !state.videoUnavailable;
|
|
518
|
+
const audioPending = state.audioOn && state.audioTrack == null && !state.audioUnavailable;
|
|
519
|
+
const starting = videoPending || audioPending || state.videoProcessing || state.audioProcessing;
|
|
520
|
+
const canJoin = controller.config != null && !starting;
|
|
521
|
+
const statusText = state.loaded ? "Get ready to meet" : "Preparing devices";
|
|
522
|
+
return /* @__PURE__ */ jsx("div", { className: "flex h-full min-h-0 flex-col px-6 py-5", children: /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-1 items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "grid w-full max-w-[800px] gap-5", children: [
|
|
523
|
+
/* @__PURE__ */ jsx("div", { className: "text-center text-base font-semibold", children: statusText }),
|
|
524
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-[3/2] overflow-hidden rounded-md bg-[#222]", children: state.videoTrack != null ? /* @__PURE__ */ jsx(
|
|
525
|
+
"video",
|
|
526
|
+
{
|
|
527
|
+
ref: previewRef,
|
|
528
|
+
autoPlay: true,
|
|
529
|
+
muted: true,
|
|
530
|
+
playsInline: true,
|
|
531
|
+
className: "h-full w-full object-cover"
|
|
532
|
+
}
|
|
533
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center text-sm font-medium text-white/70", children: "Camera off" }) }),
|
|
534
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between gap-2", children: [
|
|
535
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
536
|
+
/* @__PURE__ */ jsx(
|
|
537
|
+
LobbyToggleButton,
|
|
538
|
+
{
|
|
539
|
+
text: audioPending ? "Starting microphone" : state.audioTrack != null ? "Turn off microphone" : "Turn on microphone",
|
|
540
|
+
on: state.audioTrack != null || audioPending,
|
|
541
|
+
unavailable: state.audioUnavailable,
|
|
542
|
+
loading: state.audioProcessing || audioPending,
|
|
543
|
+
icon: /* @__PURE__ */ jsx(Mic, {}),
|
|
544
|
+
offIcon: /* @__PURE__ */ jsx(MicOff, {}),
|
|
545
|
+
onClick: state.toggleAudio
|
|
546
|
+
}
|
|
547
|
+
),
|
|
548
|
+
/* @__PURE__ */ jsx(
|
|
549
|
+
LobbyToggleButton,
|
|
550
|
+
{
|
|
551
|
+
text: videoPending ? "Starting camera" : state.videoTrack != null ? "Turn off camera" : "Turn on camera",
|
|
552
|
+
on: state.videoTrack != null || videoPending,
|
|
553
|
+
unavailable: state.videoUnavailable,
|
|
554
|
+
loading: state.videoProcessing || videoPending,
|
|
555
|
+
icon: /* @__PURE__ */ jsx(Video, {}),
|
|
556
|
+
offIcon: /* @__PURE__ */ jsx(VideoOff, {}),
|
|
557
|
+
onClick: state.toggleVideo
|
|
558
|
+
}
|
|
559
|
+
),
|
|
560
|
+
/* @__PURE__ */ jsx(LobbyDeviceSettings, { state })
|
|
561
|
+
] }),
|
|
562
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
|
|
563
|
+
onCancel != null && /* @__PURE__ */ jsx(
|
|
564
|
+
Button,
|
|
565
|
+
{
|
|
566
|
+
type: "button",
|
|
567
|
+
variant: "outline",
|
|
568
|
+
className: "h-10 sm:w-[120px]",
|
|
569
|
+
onClick: onCancel,
|
|
570
|
+
children: "Cancel"
|
|
571
|
+
}
|
|
572
|
+
),
|
|
573
|
+
/* @__PURE__ */ jsx(
|
|
574
|
+
Button,
|
|
575
|
+
{
|
|
576
|
+
type: "button",
|
|
577
|
+
className: "h-10 bg-emerald-600 text-white hover:bg-emerald-700 sm:w-[120px]",
|
|
578
|
+
disabled: !canJoin,
|
|
579
|
+
onClick: () => {
|
|
580
|
+
onJoin?.(joinOptions(state));
|
|
581
|
+
},
|
|
582
|
+
children: starting ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
583
|
+
/* @__PURE__ */ jsx(Spinner, { className: "h-4 w-4" }),
|
|
584
|
+
"Starting"
|
|
585
|
+
] }) : "Meet now"
|
|
586
|
+
}
|
|
587
|
+
)
|
|
588
|
+
] })
|
|
589
|
+
] })
|
|
590
|
+
] }) }) });
|
|
591
|
+
}
|
|
592
|
+
export {
|
|
593
|
+
MeetingLobby,
|
|
594
|
+
meetingFastConnectOptions
|
|
595
|
+
};
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import type { ReactElement, ReactNode } from "react";
|
|
2
|
-
import { RoomClient } from "@meshagent/meshagent";
|
|
3
|
-
import
|
|
2
|
+
import type { RoomClient } from "@meshagent/meshagent";
|
|
3
|
+
import "@meshagent/meshagent-react";
|
|
4
|
+
import type { LivekitConnectionInfo } from "@meshagent/meshagent-react";
|
|
5
|
+
import { Room } from "livekit-client";
|
|
6
|
+
import type { AudioCaptureOptions, LocalParticipant, Participant, RoomConnectOptions, RoomOptions, TrackPublication, VideoCaptureOptions } from "livekit-client";
|
|
4
7
|
type Listener = () => void;
|
|
5
|
-
export interface LivekitConnectionInfo {
|
|
6
|
-
url: string;
|
|
7
|
-
token: string;
|
|
8
|
-
}
|
|
9
8
|
export type MeetingFastConnectOptions = RoomConnectOptions & {
|
|
10
9
|
camera?: {
|
|
11
10
|
enabled?: boolean;
|
|
11
|
+
options?: VideoCaptureOptions;
|
|
12
12
|
};
|
|
13
13
|
microphone?: {
|
|
14
14
|
enabled?: boolean;
|
|
15
|
+
options?: AudioCaptureOptions;
|
|
15
16
|
};
|
|
16
17
|
};
|
|
17
18
|
export declare class PendingLocalMediaState {
|