@ai-matrx/meet 0.7.3 → 0.7.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 +68 -0
- package/dist/index.cjs +543 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -24
- package/dist/index.d.ts +217 -24
- package/dist/index.js +543 -35
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1879 -163
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +136 -12
- package/dist/react.d.ts +136 -12
- package/dist/react.js +1880 -164
- package/dist/react.js.map +1 -1
- package/dist/styles.css +522 -0
- package/package.json +5 -4
package/dist/react.cjs
CHANGED
|
@@ -51,19 +51,23 @@ __export(react_exports, {
|
|
|
51
51
|
ControlBar: () => ControlBar,
|
|
52
52
|
ControlButton: () => ControlButton,
|
|
53
53
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
54
|
+
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
54
55
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
55
56
|
END_MEETING_NOT_SERVED: () => END_MEETING_NOT_SERVED,
|
|
56
57
|
FILE_SESSION_PATH: () => FILE_SESSION_PATH,
|
|
58
|
+
FlagIcon: () => FlagIcon,
|
|
57
59
|
GUEST_RECORD_ACCESS_CHOICES: () => GUEST_RECORD_ACCESS_CHOICES,
|
|
58
60
|
GUEST_RECORD_ACCESS_LEVELS: () => GUEST_RECORD_ACCESS_LEVELS,
|
|
59
61
|
GUEST_RECORD_ACCESS_METADATA_KEY: () => GUEST_RECORD_ACCESS_METADATA_KEY,
|
|
60
62
|
GUEST_RECORD_NOT_SHARED: () => GUEST_RECORD_NOT_SHARED,
|
|
61
63
|
GuestRecordAccessControl: () => GuestRecordAccessControl,
|
|
62
64
|
HandIcon: () => HandIcon,
|
|
65
|
+
HostMenu: () => HostMenu,
|
|
63
66
|
IDLE_MEDIA: () => IDLE_MEDIA,
|
|
64
67
|
IDLE_TRACK: () => IDLE_TRACK,
|
|
65
68
|
IncomingCallCard: () => IncomingCallCard,
|
|
66
69
|
IncomingCallHost: () => IncomingCallHost,
|
|
70
|
+
KeyboardIcon: () => KeyboardIcon,
|
|
67
71
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
68
72
|
LayoutIcon: () => LayoutIcon,
|
|
69
73
|
LobbyPanel: () => LobbyPanel,
|
|
@@ -73,15 +77,20 @@ __export(react_exports, {
|
|
|
73
77
|
MEET_ANSWER_CONTEXT_EVENT: () => MEET_ANSWER_CONTEXT_EVENT,
|
|
74
78
|
MEET_ANSWER_EVENTS: () => MEET_ANSWER_EVENTS,
|
|
75
79
|
MEET_EVENTS: () => MEET_EVENTS,
|
|
80
|
+
MEET_REACTIONS: () => MEET_REACTIONS,
|
|
76
81
|
MEET_ROUTES: () => MEET_ROUTES,
|
|
77
82
|
MEET_SCHEMA: () => MEET_SCHEMA,
|
|
83
|
+
MEET_SHORTCUTS: () => MEET_SHORTCUTS,
|
|
84
|
+
MODERATION_ACTIONS: () => MODERATION_ACTIONS,
|
|
78
85
|
MeetError: () => MeetError,
|
|
79
86
|
MeetProvider: () => MeetProvider,
|
|
80
87
|
MeetingList: () => MeetingList,
|
|
88
|
+
MeetingOverlays: () => MeetingOverlays,
|
|
81
89
|
MeetingRecordView: () => MeetingRecordView,
|
|
82
90
|
MeetingRoom: () => MeetingRoom,
|
|
83
91
|
MicIcon: () => MicIcon,
|
|
84
92
|
MicOffIcon: () => MicOffIcon,
|
|
93
|
+
MoreIcon: () => MoreIcon,
|
|
85
94
|
NOTE_TAKER_IDENTITY_PREFIX: () => NOTE_TAKER_IDENTITY_PREFIX,
|
|
86
95
|
NOTE_TAKER_METADATA_KEY: () => NOTE_TAKER_METADATA_KEY,
|
|
87
96
|
NO_GRANTS: () => NO_GRANTS,
|
|
@@ -89,12 +98,15 @@ __export(react_exports, {
|
|
|
89
98
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE: () => PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
90
99
|
PARTICIPANT_ROLE_ATTRIBUTE: () => PARTICIPANT_ROLE_ATTRIBUTE,
|
|
91
100
|
ParticipantTile: () => ParticipantTile,
|
|
101
|
+
PencilIcon: () => PencilIcon,
|
|
92
102
|
PeopleIcon: () => PeopleIcon,
|
|
93
103
|
PeoplePanel: () => PeoplePanel,
|
|
104
|
+
PersonMenu: () => PersonMenu,
|
|
94
105
|
PhoneIcon: () => PhoneIcon,
|
|
95
106
|
PhoneOffIcon: () => PhoneOffIcon,
|
|
96
107
|
PinIcon: () => PinIcon,
|
|
97
108
|
PreJoin: () => PreJoin,
|
|
109
|
+
QualityBars: () => QualityBars,
|
|
98
110
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
99
111
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
100
112
|
ROLE_RANK: () => ROLE_RANK,
|
|
@@ -108,8 +120,15 @@ __export(react_exports, {
|
|
|
108
120
|
RecordingPlayer: () => RecordingPlayer,
|
|
109
121
|
ScheduleMeeting: () => ScheduleMeeting,
|
|
110
122
|
ScreenShareIcon: () => ScreenShareIcon,
|
|
123
|
+
SearchIcon: () => SearchIcon,
|
|
111
124
|
SettingsIcon: () => SettingsIcon,
|
|
125
|
+
SettingsPanel: () => SettingsPanel,
|
|
126
|
+
ShortcutsSheet: () => ShortcutsSheet,
|
|
127
|
+
SignalIcon: () => SignalIcon,
|
|
128
|
+
SmileIcon: () => SmileIcon,
|
|
112
129
|
SparklesIcon: () => SparklesIcon,
|
|
130
|
+
SpeakerIcon: () => SpeakerIcon,
|
|
131
|
+
SpotlightIcon: () => SpotlightIcon,
|
|
113
132
|
Stage: () => Stage,
|
|
114
133
|
TABLES: () => TABLES,
|
|
115
134
|
TRANSCRIPTION_ATTRIBUTES: () => TRANSCRIPTION_ATTRIBUTES,
|
|
@@ -137,6 +156,7 @@ __export(react_exports, {
|
|
|
137
156
|
createCallCenter: () => createCallCenter,
|
|
138
157
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
139
158
|
createDeviceManager: () => createDeviceManager,
|
|
159
|
+
createInRoomClient: () => createInRoomClient,
|
|
140
160
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
141
161
|
createMeetAi: () => createMeetAi,
|
|
142
162
|
createMeetApi: () => createMeetApi,
|
|
@@ -149,6 +169,7 @@ __export(react_exports, {
|
|
|
149
169
|
createRoomEngine: () => createRoomEngine,
|
|
150
170
|
createTokenClient: () => createTokenClient,
|
|
151
171
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
172
|
+
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
152
173
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
153
174
|
displayNameFor: () => displayNameFor,
|
|
154
175
|
fileDownloadPath: () => fileDownloadPath,
|
|
@@ -212,6 +233,7 @@ __export(react_exports, {
|
|
|
212
233
|
settingsPatch: () => settingsPatch,
|
|
213
234
|
snapshotParticipant: () => snapshotParticipant,
|
|
214
235
|
unavailableFrom: () => unavailableFrom,
|
|
236
|
+
useAudioLevel: () => useAudioLevel,
|
|
215
237
|
useCalls: () => useCalls,
|
|
216
238
|
useCanModerate: () => useCanModerate,
|
|
217
239
|
useCanRecord: () => useCanRecord,
|
|
@@ -220,19 +242,23 @@ __export(react_exports, {
|
|
|
220
242
|
useElapsed: () => useElapsed,
|
|
221
243
|
useEndMeeting: () => useEndMeeting,
|
|
222
244
|
useGuestRecordAccess: () => useGuestRecordAccess,
|
|
245
|
+
useHandQueue: () => useHandQueue,
|
|
223
246
|
useHostControls: () => useHostControls,
|
|
224
247
|
useIsHost: () => useIsHost,
|
|
225
248
|
useListenOnlyNotice: () => useListenOnlyNotice,
|
|
226
249
|
useLobby: () => useLobby,
|
|
227
250
|
useLocalMedia: () => useLocalMedia,
|
|
228
251
|
useLocalUserId: () => useLocalUserId,
|
|
252
|
+
useMediaEffects: () => useMediaEffects,
|
|
229
253
|
useMeetAi: () => useMeetAi,
|
|
230
254
|
useMeetHost: () => useMeetHost,
|
|
255
|
+
useMeetShortcuts: () => useMeetShortcuts,
|
|
231
256
|
useMeetSnapshot: () => useMeetSnapshot,
|
|
232
257
|
useMeeting: () => useMeeting,
|
|
233
258
|
useMeetingId: () => useMeetingId,
|
|
234
259
|
useMeetingRecord: () => useMeetingRecord,
|
|
235
260
|
useMeetings: () => useMeetings,
|
|
261
|
+
useModerationNotice: () => useModerationNotice,
|
|
236
262
|
useReactions: () => useReactions,
|
|
237
263
|
useRecording: () => useRecording,
|
|
238
264
|
useRequiredMeetHost: () => useRequiredMeetHost,
|
|
@@ -442,7 +468,16 @@ var MEET_ROUTES = {
|
|
|
442
468
|
*/
|
|
443
469
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
444
470
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
445
|
-
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
471
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
472
|
+
/**
|
|
473
|
+
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
474
|
+
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
475
|
+
* Decided by the server from the meeting itself; a guest's pass rides
|
|
476
|
+
* `x-meet-room-token`.
|
|
477
|
+
*/
|
|
478
|
+
moderate: "/api/v1/meet/moderate",
|
|
479
|
+
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
480
|
+
chat: "/api/v1/meet/chat"
|
|
446
481
|
};
|
|
447
482
|
function meetingPath(template, meetingId) {
|
|
448
483
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -1747,6 +1782,11 @@ function captionSegment(args) {
|
|
|
1747
1782
|
}
|
|
1748
1783
|
|
|
1749
1784
|
// src/core/store.ts
|
|
1785
|
+
var DEFAULT_EFFECTS = {
|
|
1786
|
+
noiseSuppression: true,
|
|
1787
|
+
background: "none",
|
|
1788
|
+
backgroundImage: null
|
|
1789
|
+
};
|
|
1750
1790
|
var NO_GRANTS = {
|
|
1751
1791
|
role: "participant",
|
|
1752
1792
|
canRecord: false,
|
|
@@ -1769,7 +1809,12 @@ var EMPTY = {
|
|
|
1769
1809
|
recordingSource: "local",
|
|
1770
1810
|
recordingId: null,
|
|
1771
1811
|
chat: [],
|
|
1812
|
+
chatLoaded: false,
|
|
1772
1813
|
reactions: [],
|
|
1814
|
+
spotlight: null,
|
|
1815
|
+
moderationNotice: null,
|
|
1816
|
+
actionNotice: null,
|
|
1817
|
+
effects: DEFAULT_EFFECTS,
|
|
1773
1818
|
transcript: [],
|
|
1774
1819
|
notes: [],
|
|
1775
1820
|
names: {},
|
|
@@ -1808,6 +1853,7 @@ function createMeetStore(initial = {}) {
|
|
|
1808
1853
|
setParticipants(participants) {
|
|
1809
1854
|
const names = learnNames(state.names, participants);
|
|
1810
1855
|
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
1856
|
+
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
1811
1857
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
1812
1858
|
commit({
|
|
1813
1859
|
...state,
|
|
@@ -1821,6 +1867,7 @@ function createMeetStore(initial = {}) {
|
|
|
1821
1867
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
1822
1868
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
1823
1869
|
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
1870
|
+
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
1824
1871
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
1825
1872
|
}))
|
|
1826
1873
|
});
|
|
@@ -1843,11 +1890,17 @@ function createMeetStore(initial = {}) {
|
|
|
1843
1890
|
)
|
|
1844
1891
|
});
|
|
1845
1892
|
},
|
|
1846
|
-
setHand(identity, raised) {
|
|
1893
|
+
setHand(identity, raised, at) {
|
|
1847
1894
|
commit({
|
|
1848
1895
|
...state,
|
|
1849
1896
|
participants: state.participants.map(
|
|
1850
|
-
(participant) => participant.identity === identity ? {
|
|
1897
|
+
(participant) => participant.identity === identity ? {
|
|
1898
|
+
...participant,
|
|
1899
|
+
handRaised: raised,
|
|
1900
|
+
// A hand that is ALREADY up keeps its place in the queue when
|
|
1901
|
+
// it is re-announced (to a late joiner): the first time wins.
|
|
1902
|
+
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
1903
|
+
} : participant
|
|
1851
1904
|
)
|
|
1852
1905
|
});
|
|
1853
1906
|
},
|
|
@@ -1857,6 +1910,31 @@ function createMeetStore(initial = {}) {
|
|
|
1857
1910
|
const names = learnName(state.names, message.identity, message.displayName);
|
|
1858
1911
|
commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
|
|
1859
1912
|
},
|
|
1913
|
+
loadChat(messages) {
|
|
1914
|
+
const known = new Set(state.chat.map((message) => message.id));
|
|
1915
|
+
const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
|
|
1916
|
+
(a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
|
|
1917
|
+
);
|
|
1918
|
+
const names = learnNames(
|
|
1919
|
+
state.names,
|
|
1920
|
+
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
1921
|
+
);
|
|
1922
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, names });
|
|
1923
|
+
},
|
|
1924
|
+
settleChat(localId, next) {
|
|
1925
|
+
const index = state.chat.findIndex((message) => message.id === localId);
|
|
1926
|
+
if (index === -1) return;
|
|
1927
|
+
const current = state.chat[index];
|
|
1928
|
+
let replacement;
|
|
1929
|
+
if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
|
|
1930
|
+
replacement = { ...current, failed: next.failed };
|
|
1931
|
+
} else {
|
|
1932
|
+
replacement = next;
|
|
1933
|
+
}
|
|
1934
|
+
const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
|
|
1935
|
+
const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
|
|
1936
|
+
commit({ ...state, chat });
|
|
1937
|
+
},
|
|
1860
1938
|
addReaction(reaction) {
|
|
1861
1939
|
commit({ ...state, reactions: [...state.reactions, reaction] });
|
|
1862
1940
|
},
|
|
@@ -1947,7 +2025,7 @@ function createMeetStore(initial = {}) {
|
|
|
1947
2025
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
1948
2026
|
},
|
|
1949
2027
|
reset() {
|
|
1950
|
-
commit({ ...EMPTY });
|
|
2028
|
+
commit({ ...EMPTY, effects: state.effects });
|
|
1951
2029
|
}
|
|
1952
2030
|
};
|
|
1953
2031
|
}
|
|
@@ -3202,7 +3280,19 @@ var ROOM_EVENTS = {
|
|
|
3202
3280
|
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
3203
3281
|
* host recorded (MRI-C8).
|
|
3204
3282
|
*/
|
|
3205
|
-
recording: "matrx.meet.recording"
|
|
3283
|
+
recording: "matrx.meet.recording",
|
|
3284
|
+
/**
|
|
3285
|
+
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
3286
|
+
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
3287
|
+
*/
|
|
3288
|
+
chat: "matrx.meet.chat",
|
|
3289
|
+
/**
|
|
3290
|
+
* A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
|
|
3291
|
+
* `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
|
|
3292
|
+
* person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
|
|
3293
|
+
* role}` — to everyone.
|
|
3294
|
+
*/
|
|
3295
|
+
moderation: "matrx.meet.moderation"
|
|
3206
3296
|
};
|
|
3207
3297
|
function isRoomEventTopic(topic) {
|
|
3208
3298
|
return topic.startsWith(ROOM_EVENT_PREFIX);
|
|
@@ -3238,6 +3328,10 @@ function applyRoomEvent(args) {
|
|
|
3238
3328
|
return applyEnded(store, payload);
|
|
3239
3329
|
case ROOM_EVENTS.recording:
|
|
3240
3330
|
return applyRecording(store, payload);
|
|
3331
|
+
case ROOM_EVENTS.chat:
|
|
3332
|
+
return applyChat(store, payload);
|
|
3333
|
+
case ROOM_EVENTS.moderation:
|
|
3334
|
+
return applyModeration(store, payload, now);
|
|
3241
3335
|
default:
|
|
3242
3336
|
return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
|
|
3243
3337
|
}
|
|
@@ -3329,6 +3423,101 @@ function applyEnded(store, payload) {
|
|
|
3329
3423
|
store.patch({ meeting: { ...meeting, endedAt } });
|
|
3330
3424
|
return HANDLED;
|
|
3331
3425
|
}
|
|
3426
|
+
function chatFromRow(row) {
|
|
3427
|
+
const id = text2(row["id"]);
|
|
3428
|
+
const identity = text2(row["sender_identity"]);
|
|
3429
|
+
const body = text2(row["body"]);
|
|
3430
|
+
if (id === null || identity === null || body === null) return null;
|
|
3431
|
+
return {
|
|
3432
|
+
id,
|
|
3433
|
+
identity: asParticipantIdentity(identity),
|
|
3434
|
+
displayName: text2(row["sender_name"]) ?? "Participant",
|
|
3435
|
+
text: body,
|
|
3436
|
+
at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3437
|
+
// `false` only from a server whose chat table has not landed: the room heard
|
|
3438
|
+
// it, nobody will see it later — and the line says so.
|
|
3439
|
+
saved: row["saved"] !== false
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3442
|
+
function applyChat(store, payload) {
|
|
3443
|
+
const row = object(payload["message"]);
|
|
3444
|
+
if (row === null) return ignored("a chat event with no `message` row");
|
|
3445
|
+
const meeting = store.snapshot().meeting;
|
|
3446
|
+
if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
|
|
3447
|
+
return ignored("a chat message for a different meeting");
|
|
3448
|
+
}
|
|
3449
|
+
const message = chatFromRow(row);
|
|
3450
|
+
if (message === null) return ignored("a chat row with no id, sender or body");
|
|
3451
|
+
store.addChat(message);
|
|
3452
|
+
return HANDLED;
|
|
3453
|
+
}
|
|
3454
|
+
function moderationText(kind, payload) {
|
|
3455
|
+
const by = text2(payload["by"]) ?? "The host";
|
|
3456
|
+
switch (kind) {
|
|
3457
|
+
case "muted": {
|
|
3458
|
+
const source = text2(payload["source"]) ?? "microphone";
|
|
3459
|
+
return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
|
|
3460
|
+
}
|
|
3461
|
+
case "ask-unmute":
|
|
3462
|
+
return `${by} is asking you to unmute.`;
|
|
3463
|
+
case "removed":
|
|
3464
|
+
return `${by} removed you from this meeting.`;
|
|
3465
|
+
default:
|
|
3466
|
+
return null;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
function applyModeration(store, payload, now) {
|
|
3470
|
+
const snapshot = store.snapshot();
|
|
3471
|
+
const meetingId = text2(payload["meeting_id"]);
|
|
3472
|
+
if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
|
|
3473
|
+
return ignored("a moderation event for a different meeting");
|
|
3474
|
+
}
|
|
3475
|
+
const kind = text2(payload["kind"]);
|
|
3476
|
+
switch (kind) {
|
|
3477
|
+
case "lower-hands": {
|
|
3478
|
+
const raw = payload["identities"];
|
|
3479
|
+
const everyone = raw === "*";
|
|
3480
|
+
const named = new Set(
|
|
3481
|
+
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
3482
|
+
);
|
|
3483
|
+
for (const participant of snapshot.participants) {
|
|
3484
|
+
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
3485
|
+
store.setHand(participant.identity, false);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
return HANDLED;
|
|
3489
|
+
}
|
|
3490
|
+
case "role": {
|
|
3491
|
+
const identity = text2(payload["identity"]);
|
|
3492
|
+
const role = text2(payload["role"]);
|
|
3493
|
+
if (identity === null || role === null || !ROLES.includes(role)) {
|
|
3494
|
+
return ignored("a role event with no identity or an unknown role");
|
|
3495
|
+
}
|
|
3496
|
+
if (identity === snapshot.localIdentity) {
|
|
3497
|
+
store.patch({
|
|
3498
|
+
grants: { ...snapshot.grants, role },
|
|
3499
|
+
moderationNotice: {
|
|
3500
|
+
kind: "role",
|
|
3501
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
|
|
3502
|
+
at: now()
|
|
3503
|
+
}
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
return HANDLED;
|
|
3507
|
+
}
|
|
3508
|
+
case "muted":
|
|
3509
|
+
case "ask-unmute":
|
|
3510
|
+
case "removed": {
|
|
3511
|
+
const sentence = moderationText(kind, payload);
|
|
3512
|
+
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
3513
|
+
store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
|
|
3514
|
+
return HANDLED;
|
|
3515
|
+
}
|
|
3516
|
+
default:
|
|
3517
|
+
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
var ROLES = ["host", "cohost", "participant", "guest"];
|
|
3332
3521
|
|
|
3333
3522
|
// src/core/note-taker.ts
|
|
3334
3523
|
var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
|
|
@@ -3393,8 +3582,7 @@ function noteTakerLabel(status) {
|
|
|
3393
3582
|
var DATA_TOPICS = {
|
|
3394
3583
|
hand: "mx.hand",
|
|
3395
3584
|
reaction: "mx.reaction",
|
|
3396
|
-
chat: "mx.chat"
|
|
3397
|
-
hostAction: "mx.host"
|
|
3585
|
+
chat: "mx.chat"
|
|
3398
3586
|
};
|
|
3399
3587
|
var TRANSCRIPTION_TOPIC = "lk.transcription";
|
|
3400
3588
|
var TRANSCRIPTION_ATTRIBUTES = {
|
|
@@ -3530,7 +3718,37 @@ function createRoomEngine(options) {
|
|
|
3530
3718
|
const handlers = {
|
|
3531
3719
|
participants(list) {
|
|
3532
3720
|
const joinedAt = new Date(now()).toISOString();
|
|
3721
|
+
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
3533
3722
|
store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
|
|
3723
|
+
const snapshot = store.snapshot();
|
|
3724
|
+
const newcomer = snapshot.participants.some(
|
|
3725
|
+
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
3726
|
+
);
|
|
3727
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
3728
|
+
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
3729
|
+
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
3730
|
+
() => void 0
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
},
|
|
3734
|
+
roomMetadata(metadata) {
|
|
3735
|
+
let spotlight = null;
|
|
3736
|
+
if (metadata.length > 0) {
|
|
3737
|
+
try {
|
|
3738
|
+
const parsed = JSON.parse(metadata);
|
|
3739
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
3740
|
+
const value = parsed.spotlight;
|
|
3741
|
+
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
3742
|
+
}
|
|
3743
|
+
} catch {
|
|
3744
|
+
report(
|
|
3745
|
+
"warn",
|
|
3746
|
+
"meet: the room's metadata is not JSON, so no spotlight was read from it",
|
|
3747
|
+
"Only aidream writes room metadata; check services/meet/moderation.py."
|
|
3748
|
+
);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
3534
3752
|
},
|
|
3535
3753
|
activeSpeakers(identities) {
|
|
3536
3754
|
store.setSpeaking(identities.map(asParticipantIdentity));
|
|
@@ -3634,7 +3852,11 @@ function createRoomEngine(options) {
|
|
|
3634
3852
|
const sender = asParticipantIdentity(from ?? "");
|
|
3635
3853
|
switch (topic) {
|
|
3636
3854
|
case DATA_TOPICS.hand:
|
|
3637
|
-
store.setHand(
|
|
3855
|
+
store.setHand(
|
|
3856
|
+
sender,
|
|
3857
|
+
envelope.raised === true,
|
|
3858
|
+
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
3859
|
+
);
|
|
3638
3860
|
return;
|
|
3639
3861
|
case DATA_TOPICS.reaction:
|
|
3640
3862
|
if (typeof envelope.emoji === "string") {
|
|
@@ -3648,12 +3870,14 @@ function createRoomEngine(options) {
|
|
|
3648
3870
|
return;
|
|
3649
3871
|
case DATA_TOPICS.chat:
|
|
3650
3872
|
if (typeof envelope.text === "string") {
|
|
3873
|
+
const local = store.snapshot().localIdentity;
|
|
3651
3874
|
store.addChat({
|
|
3652
3875
|
id: envelope.id ?? `${sender}-${now()}`,
|
|
3653
3876
|
identity: sender,
|
|
3654
3877
|
displayName: envelope.name ?? sender,
|
|
3655
3878
|
text: envelope.text,
|
|
3656
|
-
at: envelope.at ?? new Date(now()).toISOString()
|
|
3879
|
+
at: envelope.at ?? new Date(now()).toISOString(),
|
|
3880
|
+
...typeof envelope.to === "string" && local !== null ? { to: local } : {}
|
|
3657
3881
|
});
|
|
3658
3882
|
}
|
|
3659
3883
|
return;
|
|
@@ -3699,6 +3923,24 @@ function createRoomEngine(options) {
|
|
|
3699
3923
|
store.patch({ phase: "connected", failure: null });
|
|
3700
3924
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
3701
3925
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
3926
|
+
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
3927
|
+
store.patch({ effects });
|
|
3928
|
+
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
3929
|
+
try {
|
|
3930
|
+
await applyEffects(created, effects);
|
|
3931
|
+
} catch {
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
|
|
3935
|
+
const chosen = options.resolveDevice?.(kind) ?? null;
|
|
3936
|
+
if (chosen === null || chosen === "default") continue;
|
|
3937
|
+
try {
|
|
3938
|
+
await created.switchDevice(kind, chosen);
|
|
3939
|
+
} catch (error) {
|
|
3940
|
+
const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
|
|
3941
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3702
3944
|
if (request.mode === "video") {
|
|
3703
3945
|
await enableTrack(created, "camera", true);
|
|
3704
3946
|
}
|
|
@@ -3721,9 +3963,30 @@ function createRoomEngine(options) {
|
|
|
3721
3963
|
return state;
|
|
3722
3964
|
}
|
|
3723
3965
|
}
|
|
3724
|
-
async function publish(topic, body) {
|
|
3966
|
+
async function publish(topic, body, to) {
|
|
3725
3967
|
if (driver === null) return;
|
|
3726
|
-
await driver.publishData(topic, JSON.stringify(body));
|
|
3968
|
+
await driver.publishData(topic, JSON.stringify(body), to);
|
|
3969
|
+
}
|
|
3970
|
+
async function applyEffects(target, effects) {
|
|
3971
|
+
try {
|
|
3972
|
+
await target.setAudioProcessing?.({
|
|
3973
|
+
noiseSuppression: effects.noiseSuppression,
|
|
3974
|
+
echoCancellation: true,
|
|
3975
|
+
autoGainControl: true
|
|
3976
|
+
});
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
|
|
3979
|
+
}
|
|
3980
|
+
if (target.setVideoEffect === void 0) return;
|
|
3981
|
+
const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
|
|
3982
|
+
if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
|
|
3983
|
+
try {
|
|
3984
|
+
await target.setVideoEffect(effect);
|
|
3985
|
+
} catch (error) {
|
|
3986
|
+
const normalized = normalizeMeetError(error, "meet.setVideoEffect");
|
|
3987
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3988
|
+
throw normalized;
|
|
3989
|
+
}
|
|
3727
3990
|
}
|
|
3728
3991
|
return {
|
|
3729
3992
|
join(request) {
|
|
@@ -3779,7 +4042,7 @@ function createRoomEngine(options) {
|
|
|
3779
4042
|
async raiseHand(raised) {
|
|
3780
4043
|
const local = store.snapshot().localIdentity;
|
|
3781
4044
|
if (local !== null) store.setHand(local, raised);
|
|
3782
|
-
await publish(DATA_TOPICS.hand, { raised });
|
|
4045
|
+
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
3783
4046
|
},
|
|
3784
4047
|
async react(emoji) {
|
|
3785
4048
|
const local = store.snapshot().localIdentity;
|
|
@@ -3789,32 +4052,41 @@ function createRoomEngine(options) {
|
|
|
3789
4052
|
}
|
|
3790
4053
|
await publish(DATA_TOPICS.reaction, { id, emoji });
|
|
3791
4054
|
},
|
|
3792
|
-
async
|
|
4055
|
+
async sendPrivateChat(to, text3) {
|
|
3793
4056
|
const trimmed = text3.trim();
|
|
3794
4057
|
if (trimmed.length === 0) return;
|
|
3795
4058
|
const local = store.snapshot().localIdentity;
|
|
3796
4059
|
const id = `${local ?? "local"}-${now()}`;
|
|
3797
4060
|
const at = new Date(now()).toISOString();
|
|
3798
4061
|
if (local !== null) {
|
|
3799
|
-
store.addChat({
|
|
3800
|
-
id,
|
|
3801
|
-
identity: local,
|
|
3802
|
-
displayName: identity.displayName,
|
|
3803
|
-
text: trimmed,
|
|
3804
|
-
at
|
|
3805
|
-
});
|
|
4062
|
+
store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
|
|
3806
4063
|
}
|
|
3807
|
-
await publish(
|
|
4064
|
+
await publish(
|
|
4065
|
+
DATA_TOPICS.chat,
|
|
4066
|
+
{ id, text: trimmed, at, name: identity.displayName, to },
|
|
4067
|
+
[to]
|
|
4068
|
+
);
|
|
3808
4069
|
},
|
|
3809
|
-
async
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
4070
|
+
async setEffects(next) {
|
|
4071
|
+
const previous = store.snapshot().effects;
|
|
4072
|
+
const merged = { ...previous, ...next };
|
|
4073
|
+
store.patch({ effects: merged });
|
|
4074
|
+
options.effectsStorage?.save(merged);
|
|
4075
|
+
if (driver === null) return;
|
|
4076
|
+
try {
|
|
4077
|
+
await applyEffects(driver, merged);
|
|
4078
|
+
} catch {
|
|
4079
|
+
const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
|
|
4080
|
+
store.patch({ effects: reverted });
|
|
4081
|
+
options.effectsStorage?.save(reverted);
|
|
3816
4082
|
}
|
|
3817
|
-
|
|
4083
|
+
},
|
|
4084
|
+
supportsVideoEffects() {
|
|
4085
|
+
return driver?.supportsVideoEffects?.() ?? false;
|
|
4086
|
+
},
|
|
4087
|
+
async switchDevice(kind, deviceId2) {
|
|
4088
|
+
if (driver === null) return;
|
|
4089
|
+
await driver.switchDevice(kind, deviceId2);
|
|
3818
4090
|
},
|
|
3819
4091
|
pin(who) {
|
|
3820
4092
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
@@ -3845,6 +4117,192 @@ function hostUserIds(participants) {
|
|
|
3845
4117
|
return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
|
|
3846
4118
|
}
|
|
3847
4119
|
|
|
4120
|
+
// src/core/effects.ts
|
|
4121
|
+
function coerce(value) {
|
|
4122
|
+
if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
|
|
4123
|
+
const row = value;
|
|
4124
|
+
const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
|
|
4125
|
+
const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
|
|
4126
|
+
return {
|
|
4127
|
+
noiseSuppression: row["noiseSuppression"] !== false,
|
|
4128
|
+
background: background === "image" && backgroundImage === null ? "none" : background,
|
|
4129
|
+
backgroundImage
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
function createWebEffectsStorage(key = "mx.meet.effects") {
|
|
4133
|
+
let memory = DEFAULT_EFFECTS;
|
|
4134
|
+
const storage = globalThis.localStorage;
|
|
4135
|
+
return {
|
|
4136
|
+
load() {
|
|
4137
|
+
if (storage === void 0) return memory;
|
|
4138
|
+
try {
|
|
4139
|
+
const raw = storage.getItem(key);
|
|
4140
|
+
return raw === null ? memory : coerce(JSON.parse(raw));
|
|
4141
|
+
} catch {
|
|
4142
|
+
return memory;
|
|
4143
|
+
}
|
|
4144
|
+
},
|
|
4145
|
+
save(effects) {
|
|
4146
|
+
memory = effects;
|
|
4147
|
+
try {
|
|
4148
|
+
storage?.setItem(key, JSON.stringify(effects));
|
|
4149
|
+
} catch {
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
|
|
4155
|
+
// src/core/in-room.ts
|
|
4156
|
+
var MODERATION_ACTIONS = [
|
|
4157
|
+
"mute",
|
|
4158
|
+
"stop_video",
|
|
4159
|
+
"stop_screen",
|
|
4160
|
+
"mute_all",
|
|
4161
|
+
"ask_unmute",
|
|
4162
|
+
"ask_all_unmute",
|
|
4163
|
+
"spotlight",
|
|
4164
|
+
"lower_hand",
|
|
4165
|
+
"lower_all_hands",
|
|
4166
|
+
"make_cohost",
|
|
4167
|
+
"revoke_cohost",
|
|
4168
|
+
"transfer_host",
|
|
4169
|
+
"rename",
|
|
4170
|
+
"remove",
|
|
4171
|
+
"report"
|
|
4172
|
+
];
|
|
4173
|
+
function createInRoomClient(options) {
|
|
4174
|
+
const { api, store } = options;
|
|
4175
|
+
const now = options.now ?? (() => Date.now());
|
|
4176
|
+
function meetingId(operation) {
|
|
4177
|
+
const meeting = store.snapshot().meeting;
|
|
4178
|
+
if (meeting === null) {
|
|
4179
|
+
throw new MeetError(
|
|
4180
|
+
"not-found",
|
|
4181
|
+
`${operation}: this room has no meeting record`,
|
|
4182
|
+
"Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
|
|
4183
|
+
);
|
|
4184
|
+
}
|
|
4185
|
+
return meeting.id;
|
|
4186
|
+
}
|
|
4187
|
+
function headers() {
|
|
4188
|
+
const token = options.resolveRoomToken();
|
|
4189
|
+
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
4190
|
+
}
|
|
4191
|
+
async function refusal2(response, operation, remedy) {
|
|
4192
|
+
let body = null;
|
|
4193
|
+
try {
|
|
4194
|
+
body = await response.json();
|
|
4195
|
+
} catch {
|
|
4196
|
+
body = null;
|
|
4197
|
+
}
|
|
4198
|
+
const said = readServerRefusal(body);
|
|
4199
|
+
return new MeetError(
|
|
4200
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
4201
|
+
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
4202
|
+
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
4203
|
+
);
|
|
4204
|
+
}
|
|
4205
|
+
return {
|
|
4206
|
+
async moderate(request) {
|
|
4207
|
+
const operation = `meet.moderate(${request.action})`;
|
|
4208
|
+
const hdrs = headers();
|
|
4209
|
+
const response = await api.request({
|
|
4210
|
+
path: MEET_ROUTES.moderate,
|
|
4211
|
+
method: "POST",
|
|
4212
|
+
operation,
|
|
4213
|
+
body: {
|
|
4214
|
+
meeting_id: meetingId(operation),
|
|
4215
|
+
action: request.action,
|
|
4216
|
+
...request.identity != null ? { identity: request.identity } : {},
|
|
4217
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
4218
|
+
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
4219
|
+
},
|
|
4220
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4221
|
+
});
|
|
4222
|
+
if (!response.ok) {
|
|
4223
|
+
throw await refusal2(
|
|
4224
|
+
response,
|
|
4225
|
+
operation,
|
|
4226
|
+
"Nothing was changed. The server decides host powers from the meeting itself."
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4229
|
+
const payload = await response.json();
|
|
4230
|
+
return {
|
|
4231
|
+
action: request.action,
|
|
4232
|
+
affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
|
|
4233
|
+
message: typeof payload["message"] === "string" ? payload["message"] : "Done."
|
|
4234
|
+
};
|
|
4235
|
+
},
|
|
4236
|
+
async sendChat(text3) {
|
|
4237
|
+
const body = text3.trim();
|
|
4238
|
+
if (body.length === 0) return;
|
|
4239
|
+
const operation = "meet.sendChat";
|
|
4240
|
+
const id = meetingId(operation);
|
|
4241
|
+
const snapshot = store.snapshot();
|
|
4242
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
4243
|
+
const localId = `pending-${now()}`;
|
|
4244
|
+
const pending = {
|
|
4245
|
+
id: localId,
|
|
4246
|
+
identity: snapshot.localIdentity ?? "local",
|
|
4247
|
+
displayName: me?.displayName ?? "You",
|
|
4248
|
+
text: body,
|
|
4249
|
+
at: new Date(now()).toISOString(),
|
|
4250
|
+
saved: false
|
|
4251
|
+
};
|
|
4252
|
+
store.addChat(pending);
|
|
4253
|
+
const hdrs = headers();
|
|
4254
|
+
let response;
|
|
4255
|
+
try {
|
|
4256
|
+
response = await api.request({
|
|
4257
|
+
path: MEET_ROUTES.chat,
|
|
4258
|
+
method: "POST",
|
|
4259
|
+
operation,
|
|
4260
|
+
body: { meeting_id: id, body },
|
|
4261
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4262
|
+
});
|
|
4263
|
+
} catch (error) {
|
|
4264
|
+
store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
|
|
4265
|
+
throw error;
|
|
4266
|
+
}
|
|
4267
|
+
if (!response.ok) {
|
|
4268
|
+
const error = await refusal2(response, operation, "Your message was not sent.");
|
|
4269
|
+
store.settleChat(localId, { failed: error.message });
|
|
4270
|
+
throw error;
|
|
4271
|
+
}
|
|
4272
|
+
const saved = chatFromRow(await response.json());
|
|
4273
|
+
if (saved === null) {
|
|
4274
|
+
store.settleChat(localId, { failed: "The server's answer could not be read." });
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
store.settleChat(localId, saved);
|
|
4278
|
+
},
|
|
4279
|
+
async loadChat() {
|
|
4280
|
+
const operation = "meet.loadChat";
|
|
4281
|
+
const id = meetingId(operation);
|
|
4282
|
+
const hdrs = headers();
|
|
4283
|
+
const response = await api.request({
|
|
4284
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
|
|
4285
|
+
method: "GET",
|
|
4286
|
+
operation,
|
|
4287
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4288
|
+
});
|
|
4289
|
+
if (!response.ok) {
|
|
4290
|
+
throw await refusal2(
|
|
4291
|
+
response,
|
|
4292
|
+
operation,
|
|
4293
|
+
"Messages sent before you joined could not be read. New messages still arrive."
|
|
4294
|
+
);
|
|
4295
|
+
}
|
|
4296
|
+
const payload = await response.json();
|
|
4297
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4298
|
+
const messages = rows.map(
|
|
4299
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4300
|
+
).filter((message) => message !== null);
|
|
4301
|
+
store.loadChat(messages);
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
|
|
3848
4306
|
// src/core/livekit-driver.ts
|
|
3849
4307
|
function mapQuality(quality) {
|
|
3850
4308
|
switch (quality) {
|
|
@@ -3889,11 +4347,48 @@ function snapshotParticipant(participant, isLocal) {
|
|
|
3889
4347
|
screenSharing: participant.isScreenShareEnabled
|
|
3890
4348
|
};
|
|
3891
4349
|
}
|
|
4350
|
+
var DEFAULT_AUDIO_PROCESSING = {
|
|
4351
|
+
noiseSuppression: true,
|
|
4352
|
+
echoCancellation: true,
|
|
4353
|
+
autoGainControl: true
|
|
4354
|
+
};
|
|
4355
|
+
function videoEffectsSupported() {
|
|
4356
|
+
const scope = globalThis;
|
|
4357
|
+
if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
|
|
4358
|
+
try {
|
|
4359
|
+
return scope.document.createElement("canvas").getContext("webgl2") !== null;
|
|
4360
|
+
} catch {
|
|
4361
|
+
return false;
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
async function applyVideoEffect(target, effect) {
|
|
4365
|
+
const { Track } = await import("livekit-client");
|
|
4366
|
+
const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
|
|
4367
|
+
if (track === void 0) return;
|
|
4368
|
+
try {
|
|
4369
|
+
if (effect.kind === "none") {
|
|
4370
|
+
await track.stopProcessor();
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
const processors = await import("@livekit/track-processors");
|
|
4374
|
+
const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
|
|
4375
|
+
await track.setProcessor(processor);
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
throw new MeetError(
|
|
4378
|
+
"media-unsupported",
|
|
4379
|
+
"The background effect could not start on this camera",
|
|
4380
|
+
"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
|
|
4381
|
+
error
|
|
4382
|
+
);
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
3892
4385
|
var encoder = new TextEncoder();
|
|
3893
4386
|
var decoder = new TextDecoder();
|
|
3894
4387
|
function createLiveKitDriver(handlers, options = {}) {
|
|
3895
4388
|
let room = null;
|
|
3896
4389
|
let projectRoster = null;
|
|
4390
|
+
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
4391
|
+
let videoEffect = { kind: "none" };
|
|
3897
4392
|
async function load() {
|
|
3898
4393
|
if (room !== null) return room;
|
|
3899
4394
|
let module2;
|
|
@@ -3922,7 +4417,9 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3922
4417
|
handlers.participants([local, ...remotes]);
|
|
3923
4418
|
};
|
|
3924
4419
|
projectRoster = roster;
|
|
3925
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.
|
|
4420
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4421
|
+
handlers.roomMetadata?.(metadata ?? "");
|
|
4422
|
+
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
3926
4423
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
3927
4424
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
3928
4425
|
handlers.quality(participant.identity, mapQuality(quality));
|
|
@@ -3999,6 +4496,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3999
4496
|
throw normalizeDeviceError(error, "meet.connect");
|
|
4000
4497
|
}
|
|
4001
4498
|
projectRoster?.();
|
|
4499
|
+
handlers.roomMetadata?.(target.metadata ?? "");
|
|
4002
4500
|
},
|
|
4003
4501
|
async disconnect() {
|
|
4004
4502
|
if (room === null) return;
|
|
@@ -4013,14 +4511,19 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4013
4511
|
},
|
|
4014
4512
|
async setMicrophoneEnabled(enabled) {
|
|
4015
4513
|
try {
|
|
4016
|
-
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4514
|
+
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4515
|
+
enabled,
|
|
4516
|
+
audioCapture
|
|
4517
|
+
);
|
|
4017
4518
|
} catch (error) {
|
|
4018
4519
|
throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
|
|
4019
4520
|
}
|
|
4020
4521
|
},
|
|
4021
4522
|
async setCameraEnabled(enabled) {
|
|
4022
4523
|
try {
|
|
4023
|
-
|
|
4524
|
+
const target = requireRoom("setCameraEnabled");
|
|
4525
|
+
await target.localParticipant.setCameraEnabled(enabled);
|
|
4526
|
+
if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
|
|
4024
4527
|
} catch (error) {
|
|
4025
4528
|
throw normalizeDeviceError(error, "meet.setCameraEnabled");
|
|
4026
4529
|
}
|
|
@@ -4032,14 +4535,40 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4032
4535
|
throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
|
|
4033
4536
|
}
|
|
4034
4537
|
},
|
|
4035
|
-
async publishData(topic, payload) {
|
|
4538
|
+
async publishData(topic, payload, to) {
|
|
4036
4539
|
await requireRoom("publishData").localParticipant.publishData(
|
|
4037
4540
|
// `reliable` is not optional for a control plane: a dropped
|
|
4038
4541
|
// "hand lowered" leaves a hand up on every other screen forever.
|
|
4039
4542
|
encoder.encode(payload),
|
|
4040
|
-
{ reliable: true, topic }
|
|
4543
|
+
to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
|
|
4041
4544
|
);
|
|
4042
4545
|
},
|
|
4546
|
+
async setAudioProcessing(processing) {
|
|
4547
|
+
audioCapture = { ...audioCapture, ...processing };
|
|
4548
|
+
const target = room;
|
|
4549
|
+
if (target === null) return;
|
|
4550
|
+
const { Track } = await import("livekit-client");
|
|
4551
|
+
const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
|
|
4552
|
+
const track = publication?.track;
|
|
4553
|
+
if (track === void 0 || publication?.isMuted === true) return;
|
|
4554
|
+
try {
|
|
4555
|
+
await track.restartTrack({
|
|
4556
|
+
...audioCapture,
|
|
4557
|
+
...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
|
|
4558
|
+
});
|
|
4559
|
+
} catch (error) {
|
|
4560
|
+
throw normalizeDeviceError(error, "meet.setAudioProcessing");
|
|
4561
|
+
}
|
|
4562
|
+
},
|
|
4563
|
+
async setVideoEffect(effect) {
|
|
4564
|
+
videoEffect = effect;
|
|
4565
|
+
const target = room;
|
|
4566
|
+
if (target === null) return;
|
|
4567
|
+
await applyVideoEffect(target, effect);
|
|
4568
|
+
},
|
|
4569
|
+
supportsVideoEffects() {
|
|
4570
|
+
return videoEffectsSupported();
|
|
4571
|
+
},
|
|
4043
4572
|
attach(identity, kind, element) {
|
|
4044
4573
|
const target = requireRoom("attach");
|
|
4045
4574
|
const source = sources?.[kind];
|
|
@@ -4073,6 +4602,52 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4073
4602
|
}
|
|
4074
4603
|
};
|
|
4075
4604
|
}
|
|
4605
|
+
async function createCameraPreview(deviceId2, effect) {
|
|
4606
|
+
let module2;
|
|
4607
|
+
try {
|
|
4608
|
+
module2 = await import("livekit-client");
|
|
4609
|
+
} catch (error) {
|
|
4610
|
+
throw new MeetError(
|
|
4611
|
+
"misconfigured",
|
|
4612
|
+
"livekit-client could not be loaded for the camera preview",
|
|
4613
|
+
"The bundler excluded `livekit-client`; remove it from any `external` list.",
|
|
4614
|
+
error
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
let track;
|
|
4618
|
+
try {
|
|
4619
|
+
track = await module2.createLocalVideoTrack(deviceId2 === null ? {} : { deviceId: deviceId2 });
|
|
4620
|
+
} catch (error) {
|
|
4621
|
+
throw normalizeDeviceError(error, "meet.cameraPreview");
|
|
4622
|
+
}
|
|
4623
|
+
const target = track;
|
|
4624
|
+
async function apply(next) {
|
|
4625
|
+
if (next.kind === "none") {
|
|
4626
|
+
await target.stopProcessor();
|
|
4627
|
+
return;
|
|
4628
|
+
}
|
|
4629
|
+
const processors = await import("@livekit/track-processors");
|
|
4630
|
+
await target.setProcessor(
|
|
4631
|
+
next.kind === "blur" ? processors.BackgroundBlur(next.radius ?? 12) : processors.VirtualBackground(next.imageUrl)
|
|
4632
|
+
);
|
|
4633
|
+
}
|
|
4634
|
+
if (effect.kind !== "none" && videoEffectsSupported()) {
|
|
4635
|
+
try {
|
|
4636
|
+
await apply(effect);
|
|
4637
|
+
} catch {
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
return {
|
|
4641
|
+
attach(element) {
|
|
4642
|
+
track.attach(element);
|
|
4643
|
+
},
|
|
4644
|
+
setEffect: apply,
|
|
4645
|
+
stop() {
|
|
4646
|
+
track.stop();
|
|
4647
|
+
track.detach();
|
|
4648
|
+
}
|
|
4649
|
+
};
|
|
4650
|
+
}
|
|
4076
4651
|
|
|
4077
4652
|
// src/react/provider.tsx
|
|
4078
4653
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -4182,7 +4757,9 @@ function MeetRuntime(props) {
|
|
|
4182
4757
|
avatarUrl: props.avatarUrl ?? null,
|
|
4183
4758
|
deviceId: deviceId()
|
|
4184
4759
|
};
|
|
4185
|
-
const
|
|
4760
|
+
const effectsStorage = createWebEffectsStorage();
|
|
4761
|
+
const store = createMeetStore({ effects: effectsStorage.load() });
|
|
4762
|
+
const devicesHolder = { current: null };
|
|
4186
4763
|
const api = createMeetApi({
|
|
4187
4764
|
baseUrl,
|
|
4188
4765
|
organizationId: identity.organizationId,
|
|
@@ -4201,7 +4778,9 @@ function MeetRuntime(props) {
|
|
|
4201
4778
|
identity,
|
|
4202
4779
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
4203
4780
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
4204
|
-
onDiagnostic: report
|
|
4781
|
+
onDiagnostic: report,
|
|
4782
|
+
effectsStorage,
|
|
4783
|
+
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null
|
|
4205
4784
|
});
|
|
4206
4785
|
const meetings = createMeetingSession({
|
|
4207
4786
|
manager,
|
|
@@ -4216,16 +4795,32 @@ function MeetRuntime(props) {
|
|
|
4216
4795
|
void engine.rejoin();
|
|
4217
4796
|
}
|
|
4218
4797
|
});
|
|
4219
|
-
const
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4798
|
+
const resolveRoomToken = () => {
|
|
4799
|
+
const roomName = store.snapshot().meeting?.roomName;
|
|
4800
|
+
if (roomName === void 0) return null;
|
|
4801
|
+
const active = engine.token();
|
|
4802
|
+
if (active?.roomName === roomName && active.token.length > 0) return active.token;
|
|
4803
|
+
return tokens.remembered(roomName)?.token ?? null;
|
|
4804
|
+
};
|
|
4805
|
+
const ai = createMeetAi({ api, store, resolveRoomToken });
|
|
4806
|
+
const inRoom = createInRoomClient({ api, store, resolveRoomToken });
|
|
4807
|
+
let chatReadFor = null;
|
|
4808
|
+
store.subscribe(() => {
|
|
4809
|
+
const snapshot = store.snapshot();
|
|
4810
|
+
if (snapshot.phase !== "connected" || snapshot.meeting === null) {
|
|
4811
|
+
if (snapshot.phase === "left" || snapshot.phase === "idle") chatReadFor = null;
|
|
4812
|
+
return;
|
|
4228
4813
|
}
|
|
4814
|
+
if (chatReadFor === snapshot.meeting.id) return;
|
|
4815
|
+
chatReadFor = snapshot.meeting.id;
|
|
4816
|
+
void inRoom.loadChat().catch((error) => {
|
|
4817
|
+
const normalized = error;
|
|
4818
|
+
report({
|
|
4819
|
+
level: "warn",
|
|
4820
|
+
message: normalized.message ?? "meet: the saved chat could not be read",
|
|
4821
|
+
...normalized.remedy !== void 0 ? { remedy: normalized.remedy } : {}
|
|
4822
|
+
});
|
|
4823
|
+
});
|
|
4229
4824
|
});
|
|
4230
4825
|
const recording = createRecordingController({
|
|
4231
4826
|
api,
|
|
@@ -4247,6 +4842,7 @@ function MeetRuntime(props) {
|
|
|
4247
4842
|
},
|
|
4248
4843
|
storage: createWebDeviceStorage()
|
|
4249
4844
|
});
|
|
4845
|
+
devicesHolder.current = devices;
|
|
4250
4846
|
const calls = identity.userId === null ? null : createCallCenter({
|
|
4251
4847
|
manager,
|
|
4252
4848
|
repository,
|
|
@@ -4261,6 +4857,7 @@ function MeetRuntime(props) {
|
|
|
4261
4857
|
engine,
|
|
4262
4858
|
calls,
|
|
4263
4859
|
meetings,
|
|
4860
|
+
inRoom,
|
|
4264
4861
|
recording,
|
|
4265
4862
|
devices,
|
|
4266
4863
|
repository,
|
|
@@ -4382,7 +4979,8 @@ function computeStage(args) {
|
|
|
4382
4979
|
const ordered = orderParticipants(args.participants, args.activeSpeakers);
|
|
4383
4980
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
4384
4981
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
4385
|
-
const
|
|
4982
|
+
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
4983
|
+
const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
|
|
4386
4984
|
const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
|
|
4387
4985
|
const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
|
|
4388
4986
|
const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
|
|
@@ -4861,7 +5459,14 @@ function useRoom() {
|
|
|
4861
5459
|
[engine]
|
|
4862
5460
|
),
|
|
4863
5461
|
sendChat: (0, import_react3.useCallback)(
|
|
4864
|
-
async (text3) =>
|
|
5462
|
+
async (text3) => {
|
|
5463
|
+
if (host === null) throw noRuntime("sendChat");
|
|
5464
|
+
await host.inRoom.sendChat(text3);
|
|
5465
|
+
},
|
|
5466
|
+
[host]
|
|
5467
|
+
),
|
|
5468
|
+
sendPrivateChat: (0, import_react3.useCallback)(
|
|
5469
|
+
async (to, text3) => requireEngine(engine, "sendPrivateChat").sendPrivateChat(to, text3),
|
|
4865
5470
|
[engine]
|
|
4866
5471
|
),
|
|
4867
5472
|
pin: (0, import_react3.useCallback)(
|
|
@@ -4882,6 +5487,7 @@ function useStage(pageSize = DEFAULT_PAGE_SIZE) {
|
|
|
4882
5487
|
participants: snapshot?.participants ?? [],
|
|
4883
5488
|
layout: snapshot?.layout ?? "grid",
|
|
4884
5489
|
pinned: snapshot?.pinned ?? null,
|
|
5490
|
+
spotlight: snapshot?.spotlight ?? null,
|
|
4885
5491
|
activeSpeakers: snapshot?.activeSpeakers ?? [],
|
|
4886
5492
|
pageSize,
|
|
4887
5493
|
page
|
|
@@ -5214,33 +5820,160 @@ function useHostControls() {
|
|
|
5214
5820
|
const host = useMeetHost();
|
|
5215
5821
|
const snapshot = useMeetSnapshot();
|
|
5216
5822
|
const role = useRole();
|
|
5217
|
-
const engine = host?.engine ?? null;
|
|
5218
5823
|
const me = snapshot?.participants.find(
|
|
5219
5824
|
(participant) => participant.identity === snapshot.localIdentity
|
|
5220
5825
|
);
|
|
5221
5826
|
const actor = me === void 0 ? void 0 : { ...me, role };
|
|
5827
|
+
const [busy, setBusy] = (0, import_react3.useState)(false);
|
|
5828
|
+
const inRoom = host?.inRoom ?? null;
|
|
5829
|
+
const store = host?.store ?? null;
|
|
5830
|
+
const notice = snapshot?.actionNotice ?? null;
|
|
5831
|
+
const setNotice = (0, import_react3.useCallback)(
|
|
5832
|
+
(next) => store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),
|
|
5833
|
+
[store]
|
|
5834
|
+
);
|
|
5835
|
+
const run = (0, import_react3.useCallback)(
|
|
5836
|
+
async (request) => {
|
|
5837
|
+
if (inRoom === null) throw noRuntime(`moderate(${request.action})`);
|
|
5838
|
+
setBusy(true);
|
|
5839
|
+
try {
|
|
5840
|
+
const result = await inRoom.moderate(request);
|
|
5841
|
+
setNotice({ text: result.message, failed: false });
|
|
5842
|
+
return result;
|
|
5843
|
+
} catch (thrown) {
|
|
5844
|
+
const error = thrown;
|
|
5845
|
+
setNotice({
|
|
5846
|
+
text: [error.message, error.remedy].filter(Boolean).join(" "),
|
|
5847
|
+
failed: true
|
|
5848
|
+
});
|
|
5849
|
+
return null;
|
|
5850
|
+
} finally {
|
|
5851
|
+
setBusy(false);
|
|
5852
|
+
}
|
|
5853
|
+
},
|
|
5854
|
+
[inRoom, setNotice]
|
|
5855
|
+
);
|
|
5222
5856
|
return {
|
|
5223
5857
|
isHost: isHostRole(role),
|
|
5858
|
+
isOwner: role === "host",
|
|
5859
|
+
notice,
|
|
5860
|
+
busy,
|
|
5861
|
+
clearNotice: (0, import_react3.useCallback)(() => setNotice(null), [setNotice]),
|
|
5224
5862
|
canModerate: (0, import_react3.useCallback)(
|
|
5225
5863
|
(target) => canModerateParticipant(actor, target),
|
|
5226
5864
|
[actor]
|
|
5227
5865
|
),
|
|
5866
|
+
run,
|
|
5228
5867
|
mute: (0, import_react3.useCallback)(
|
|
5229
|
-
async (target) =>
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
[engine]
|
|
5868
|
+
async (target) => {
|
|
5869
|
+
await run({ action: "mute", identity: target.identity });
|
|
5870
|
+
},
|
|
5871
|
+
[run]
|
|
5234
5872
|
),
|
|
5235
5873
|
remove: (0, import_react3.useCallback)(
|
|
5236
|
-
async (target) =>
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5874
|
+
async (target) => {
|
|
5875
|
+
await run({ action: "remove", identity: target.identity });
|
|
5876
|
+
},
|
|
5877
|
+
[run]
|
|
5878
|
+
)
|
|
5879
|
+
};
|
|
5880
|
+
}
|
|
5881
|
+
function useHandQueue() {
|
|
5882
|
+
const snapshot = useMeetSnapshot();
|
|
5883
|
+
return (0, import_react3.useMemo)(
|
|
5884
|
+
() => (snapshot?.participants ?? []).filter((participant) => participant.handRaised).sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),
|
|
5885
|
+
[snapshot?.participants]
|
|
5886
|
+
);
|
|
5887
|
+
}
|
|
5888
|
+
function useMediaEffects() {
|
|
5889
|
+
const host = useMeetHost();
|
|
5890
|
+
const snapshot = useMeetSnapshot();
|
|
5891
|
+
const engine = host?.engine ?? null;
|
|
5892
|
+
return {
|
|
5893
|
+
effects: snapshot?.effects ?? DEFAULT_EFFECTS,
|
|
5894
|
+
backgroundsSupported: engine?.supportsVideoEffects() ?? false,
|
|
5895
|
+
set: (0, import_react3.useCallback)(
|
|
5896
|
+
async (next) => requireEngine(engine, "setEffects").setEffects(next),
|
|
5240
5897
|
[engine]
|
|
5241
5898
|
)
|
|
5242
5899
|
};
|
|
5243
5900
|
}
|
|
5901
|
+
function useModerationNotice() {
|
|
5902
|
+
const host = useMeetHost();
|
|
5903
|
+
const snapshot = useMeetSnapshot();
|
|
5904
|
+
return {
|
|
5905
|
+
notice: snapshot?.moderationNotice ?? null,
|
|
5906
|
+
dismiss: (0, import_react3.useCallback)(() => host?.store.patch({ moderationNotice: null }), [host])
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5909
|
+
function useAudioLevel(stream) {
|
|
5910
|
+
const [level, setLevel] = (0, import_react3.useState)(0);
|
|
5911
|
+
(0, import_react3.useEffect)(() => {
|
|
5912
|
+
if (stream === null || stream.getAudioTracks().length === 0) {
|
|
5913
|
+
setLevel(0);
|
|
5914
|
+
return void 0;
|
|
5915
|
+
}
|
|
5916
|
+
const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
|
|
5917
|
+
if (Context === void 0) return void 0;
|
|
5918
|
+
const context = new Context();
|
|
5919
|
+
const source = context.createMediaStreamSource(stream);
|
|
5920
|
+
const analyser = context.createAnalyser();
|
|
5921
|
+
analyser.fftSize = 512;
|
|
5922
|
+
source.connect(analyser);
|
|
5923
|
+
const samples = new Uint8Array(analyser.fftSize);
|
|
5924
|
+
let frame = 0;
|
|
5925
|
+
const tick = () => {
|
|
5926
|
+
analyser.getByteTimeDomainData(samples);
|
|
5927
|
+
let peak = 0;
|
|
5928
|
+
for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);
|
|
5929
|
+
setLevel((previous) => Math.abs(previous - peak) > 0.02 ? peak : previous);
|
|
5930
|
+
frame = requestAnimationFrame(tick);
|
|
5931
|
+
};
|
|
5932
|
+
frame = requestAnimationFrame(tick);
|
|
5933
|
+
return () => {
|
|
5934
|
+
cancelAnimationFrame(frame);
|
|
5935
|
+
source.disconnect();
|
|
5936
|
+
void context.close();
|
|
5937
|
+
};
|
|
5938
|
+
}, [stream]);
|
|
5939
|
+
return level;
|
|
5940
|
+
}
|
|
5941
|
+
var MEET_SHORTCUTS = [
|
|
5942
|
+
{ id: "mic", keys: "\u2318/Ctrl + D", label: "Mute or unmute your microphone" },
|
|
5943
|
+
{ id: "camera", keys: "\u2318/Ctrl + E", label: "Turn your camera on or off" },
|
|
5944
|
+
{ id: "hand", keys: "\u2318/Ctrl + Alt + H", label: "Raise or lower your hand" },
|
|
5945
|
+
{ id: "chat", keys: "\u2318/Ctrl + Alt + C", label: "Open or close chat" },
|
|
5946
|
+
{ id: "people", keys: "\u2318/Ctrl + Alt + P", label: "Open or close people" },
|
|
5947
|
+
{ id: "help", keys: "Shift + ?", label: "Show keyboard shortcuts" }
|
|
5948
|
+
];
|
|
5949
|
+
function useMeetShortcuts(handlers) {
|
|
5950
|
+
const ref = (0, import_react3.useRef)(handlers);
|
|
5951
|
+
ref.current = handlers;
|
|
5952
|
+
(0, import_react3.useEffect)(() => {
|
|
5953
|
+
const target = globalThis.window;
|
|
5954
|
+
if (target === void 0) return void 0;
|
|
5955
|
+
const onKey = (event) => {
|
|
5956
|
+
const element = event.target;
|
|
5957
|
+
const typing = element !== null && (element.isContentEditable || element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT");
|
|
5958
|
+
const mod = event.metaKey || event.ctrlKey;
|
|
5959
|
+
const key = event.key.toLowerCase();
|
|
5960
|
+
let id = null;
|
|
5961
|
+
if (mod && !event.altKey && !event.shiftKey && key === "d") id = "mic";
|
|
5962
|
+
else if (mod && !event.altKey && !event.shiftKey && key === "e") id = "camera";
|
|
5963
|
+
else if (mod && event.altKey && (event.code === "KeyH" || key === "h")) id = "hand";
|
|
5964
|
+
else if (mod && event.altKey && (event.code === "KeyC" || key === "c")) id = "chat";
|
|
5965
|
+
else if (mod && event.altKey && (event.code === "KeyP" || key === "p")) id = "people";
|
|
5966
|
+
else if (!mod && !typing && event.key === "?") id = "help";
|
|
5967
|
+
if (id === null) return;
|
|
5968
|
+
const handler = ref.current[id];
|
|
5969
|
+
if (handler === void 0) return;
|
|
5970
|
+
event.preventDefault();
|
|
5971
|
+
handler();
|
|
5972
|
+
};
|
|
5973
|
+
target.addEventListener("keydown", onKey);
|
|
5974
|
+
return () => target.removeEventListener("keydown", onKey);
|
|
5975
|
+
}, []);
|
|
5976
|
+
}
|
|
5244
5977
|
function useMeeting() {
|
|
5245
5978
|
return useMeetSnapshot()?.meeting ?? null;
|
|
5246
5979
|
}
|
|
@@ -5587,13 +6320,67 @@ function WifiLowIcon(props) {
|
|
|
5587
6320
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
|
|
5588
6321
|
] });
|
|
5589
6322
|
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
/* @__PURE__ */ (0,
|
|
5596
|
-
|
|
6323
|
+
function SmileIcon(props) {
|
|
6324
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6325
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
6326
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
6327
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
|
|
6328
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
|
|
6329
|
+
] });
|
|
6330
|
+
}
|
|
6331
|
+
function MoreIcon(props) {
|
|
6332
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6333
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "1" }),
|
|
6334
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "19", cy: "12", r: "1" }),
|
|
6335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "5", cy: "12", r: "1" })
|
|
6336
|
+
] });
|
|
6337
|
+
}
|
|
6338
|
+
function SearchIcon(props) {
|
|
6339
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6340
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "11", cy: "11", r: "8" }),
|
|
6341
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "m21 21-4.3-4.3" })
|
|
6342
|
+
] });
|
|
6343
|
+
}
|
|
6344
|
+
function KeyboardIcon(props) {
|
|
6345
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6346
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
|
|
6347
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
|
|
6348
|
+
] });
|
|
6349
|
+
}
|
|
6350
|
+
function SpotlightIcon(props) {
|
|
6351
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2 15 8.5 22 9.3 17 14 18.2 21 12 17.6 5.8 21 7 14 2 9.3 9 8.5Z" }) });
|
|
6352
|
+
}
|
|
6353
|
+
function SignalIcon({ bars, ...props }) {
|
|
6354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6355
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
|
|
6356
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
|
|
6357
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
|
|
6358
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
|
|
6359
|
+
] });
|
|
6360
|
+
}
|
|
6361
|
+
function SpeakerIcon(props) {
|
|
6362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6363
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
|
|
6364
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
6365
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
6366
|
+
] });
|
|
6367
|
+
}
|
|
6368
|
+
function FlagIcon(props) {
|
|
6369
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Icon, { ...props, children: [
|
|
6370
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z" }),
|
|
6371
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "4", x2: "4", y1: "22", y2: "15" })
|
|
6372
|
+
] });
|
|
6373
|
+
}
|
|
6374
|
+
function PencilIcon(props) {
|
|
6375
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
|
|
6376
|
+
}
|
|
6377
|
+
|
|
6378
|
+
// src/react/components.tsx
|
|
6379
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
6380
|
+
function InertNotice({ what }) {
|
|
6381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__empty", role: "status", children: [
|
|
6382
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mx-meet__empty-title", children: [
|
|
6383
|
+
what,
|
|
5597
6384
|
" is not ready yet"
|
|
5598
6385
|
] }),
|
|
5599
6386
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__empty-body", children: "Calls and meetings need a signed-in identity and an active organization. This will appear as soon as they resolve." })
|
|
@@ -5687,12 +6474,17 @@ function ParticipantTile({
|
|
|
5687
6474
|
featured = false
|
|
5688
6475
|
}) {
|
|
5689
6476
|
const host = useMeetHost();
|
|
5690
|
-
const
|
|
6477
|
+
const snapshot = useMeetSnapshot();
|
|
6478
|
+
const queue = useHandQueue();
|
|
6479
|
+
const [menu, setMenu] = (0, import_react4.useState)(false);
|
|
5691
6480
|
const videoRef = useTrack(
|
|
5692
6481
|
participant.identity,
|
|
5693
6482
|
participant.screenSharing ? "screen" : "camera"
|
|
5694
6483
|
);
|
|
5695
6484
|
const showVideo = participant.cameraEnabled || participant.screenSharing;
|
|
6485
|
+
const pinnedHere = snapshot?.pinned === participant.identity;
|
|
6486
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
6487
|
+
const handPosition = participant.handRaised ? queue.findIndex((entry) => entry.identity === participant.identity) + 1 : 0;
|
|
5696
6488
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5697
6489
|
"div",
|
|
5698
6490
|
{
|
|
@@ -5713,44 +6505,59 @@ function ParticipantTile({
|
|
|
5713
6505
|
muted: participant.isLocal
|
|
5714
6506
|
}
|
|
5715
6507
|
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: featured ? 96 : 56 }) }),
|
|
6508
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__tile-hand", title: `Hand raised \xB7 ${ordinal(handPosition)} in line`, children: [
|
|
6509
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 14, height: 14 }),
|
|
6510
|
+
handPosition > 0 ? handPosition : null
|
|
6511
|
+
] }) : null,
|
|
5716
6512
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__tile-bar", children: [
|
|
5717
6513
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__tile-name", children: [
|
|
6514
|
+
spotlit ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { title: "Spotlighted for everyone", className: "mx-meet__tile-flag", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 12, height: 12 }) }) : null,
|
|
5718
6515
|
participant.displayName,
|
|
5719
6516
|
participant.isLocal ? " (you)" : "",
|
|
5720
6517
|
participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
5721
6518
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RoleBadge, { role: participant.role })
|
|
5722
6519
|
] }),
|
|
5723
6520
|
!participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
5724
|
-
|
|
5725
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ConnectionDot, { quality: participant.quality })
|
|
6521
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(QualityBars, { quality: participant.quality })
|
|
5726
6522
|
] }),
|
|
5727
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__tile-actions", children: [
|
|
6523
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: menu ? "mx-meet__tile-actions mx-meet__tile-actions--open" : "mx-meet__tile-actions", children: [
|
|
5728
6524
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5729
6525
|
ControlButton,
|
|
5730
6526
|
{
|
|
5731
|
-
label:
|
|
5732
|
-
|
|
6527
|
+
label: pinnedHere ? "Unpin" : `Pin ${participant.displayName} for me`,
|
|
6528
|
+
active: pinnedHere,
|
|
6529
|
+
onClick: () => host?.engine.pin(pinnedHere ? null : participant.identity),
|
|
5733
6530
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PinIcon, { width: 16, height: 16 })
|
|
5734
6531
|
}
|
|
5735
6532
|
),
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
) : null
|
|
6533
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6534
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6535
|
+
ControlButton,
|
|
6536
|
+
{
|
|
6537
|
+
label: `More for ${participant.displayName}`,
|
|
6538
|
+
active: menu,
|
|
6539
|
+
onClick: () => setMenu(!menu),
|
|
6540
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, { width: 16, height: 16 })
|
|
6541
|
+
}
|
|
6542
|
+
),
|
|
6543
|
+
menu ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PersonMenu, { participant, onClose: () => setMenu(false), placement: "down" }) : null
|
|
6544
|
+
] })
|
|
5749
6545
|
] })
|
|
5750
6546
|
]
|
|
5751
6547
|
}
|
|
5752
6548
|
);
|
|
5753
6549
|
}
|
|
6550
|
+
function ordinal(position) {
|
|
6551
|
+
if (position <= 0) return "";
|
|
6552
|
+
const suffix = position % 100 >= 11 && position % 100 <= 13 ? "th" : { 1: "st", 2: "nd", 3: "rd" }[position % 10] ?? "th";
|
|
6553
|
+
return `${position}${suffix}`;
|
|
6554
|
+
}
|
|
6555
|
+
function QualityBars({ quality }) {
|
|
6556
|
+
if (quality === "unknown") return null;
|
|
6557
|
+
const bars = quality === "excellent" ? 3 : quality === "good" ? 2 : quality === "poor" ? 1 : 0;
|
|
6558
|
+
const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
|
|
6559
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SignalIcon, { bars, width: 14, height: 14 }) });
|
|
6560
|
+
}
|
|
5754
6561
|
function Stage({ pageSize }) {
|
|
5755
6562
|
const stage = useStage(pageSize);
|
|
5756
6563
|
const columns = gridColumns(stage.tiles.length);
|
|
@@ -5779,22 +6586,52 @@ function Stage({ pageSize }) {
|
|
|
5779
6586
|
] }) : null
|
|
5780
6587
|
] });
|
|
5781
6588
|
}
|
|
6589
|
+
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
5782
6590
|
function ControlBar({ onLeave }) {
|
|
5783
6591
|
const room = useRoom();
|
|
5784
6592
|
const recording = useRecording();
|
|
5785
6593
|
const [panel, setPanel] = (0, import_react4.useState)("none");
|
|
6594
|
+
const [chatTo, setChatTo] = (0, import_react4.useState)(null);
|
|
6595
|
+
const [popover, setPopover] = (0, import_react4.useState)("none");
|
|
6596
|
+
const [shortcuts, setShortcuts] = (0, import_react4.useState)(false);
|
|
5786
6597
|
const snapshot = useMeetSnapshot();
|
|
5787
6598
|
const lobby = useLobby();
|
|
6599
|
+
const controls = useHostControls();
|
|
6600
|
+
const queue = useHandQueue();
|
|
5788
6601
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6602
|
+
const unread = useUnreadChat(panel === "chat");
|
|
6603
|
+
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
6604
|
+
useMeetShortcuts({
|
|
6605
|
+
mic: () => void room.toggleMicrophone(),
|
|
6606
|
+
camera: () => void room.toggleCamera(),
|
|
6607
|
+
hand: () => void room.toggleHand(),
|
|
6608
|
+
chat: () => toggle("chat"),
|
|
6609
|
+
people: () => toggle("people"),
|
|
6610
|
+
help: () => setShortcuts(true)
|
|
6611
|
+
});
|
|
6612
|
+
const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== void 0;
|
|
5789
6613
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
5790
|
-
|
|
5791
|
-
|
|
6614
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MeetingOverlays, {}),
|
|
6615
|
+
shortcuts ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShortcutsSheet, { onClose: () => setShortcuts(false) }) : null,
|
|
6616
|
+
panel === "chat" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatPanel, { onClose: () => setPanel("none"), to: chatTo, onTo: setChatTo }) : null,
|
|
6617
|
+
panel === "people" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6618
|
+
PeoplePanel,
|
|
6619
|
+
{
|
|
6620
|
+
onClose: () => setPanel("none"),
|
|
6621
|
+
onMessage: (identity) => {
|
|
6622
|
+
setChatTo(identity);
|
|
6623
|
+
setPanel("chat");
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
) : null,
|
|
5792
6627
|
panel === "ai" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiPanel, { onClose: () => setPanel("none") }) : null,
|
|
6628
|
+
panel === "settings" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingsPanel, { onClose: () => setPanel("none") }) : null,
|
|
6629
|
+
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
5793
6630
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
5794
6631
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5795
6632
|
ControlButton,
|
|
5796
6633
|
{
|
|
5797
|
-
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute" : "Unmute",
|
|
6634
|
+
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
|
|
5798
6635
|
active: room.local?.micEnabled === true,
|
|
5799
6636
|
onClick: () => void room.toggleMicrophone(),
|
|
5800
6637
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, {})
|
|
@@ -5803,7 +6640,7 @@ function ControlBar({ onLeave }) {
|
|
|
5803
6640
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5804
6641
|
ControlButton,
|
|
5805
6642
|
{
|
|
5806
|
-
label: room.local?.cameraEnabled === true ? "Stop video" : "Start video",
|
|
6643
|
+
label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
|
|
5807
6644
|
active: room.local?.cameraEnabled === true,
|
|
5808
6645
|
onClick: () => void room.toggleCamera(),
|
|
5809
6646
|
children: room.local?.cameraEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraOffIcon, {})
|
|
@@ -5818,31 +6655,152 @@ function ControlBar({ onLeave }) {
|
|
|
5818
6655
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScreenShareIcon, {})
|
|
5819
6656
|
}
|
|
5820
6657
|
),
|
|
6658
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6659
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6660
|
+
ControlButton,
|
|
6661
|
+
{
|
|
6662
|
+
label: "Send a reaction",
|
|
6663
|
+
active: popover === "reactions",
|
|
6664
|
+
onClick: () => setPopover(popover === "reactions" ? "none" : "reactions"),
|
|
6665
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SmileIcon, {})
|
|
6666
|
+
}
|
|
6667
|
+
),
|
|
6668
|
+
popover === "reactions" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: "Reactions", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__emoji-bar", children: MEET_REACTIONS.map((emoji) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6669
|
+
"button",
|
|
6670
|
+
{
|
|
6671
|
+
type: "button",
|
|
6672
|
+
className: "mx-meet__emoji",
|
|
6673
|
+
"aria-label": `React ${emoji}`,
|
|
6674
|
+
onClick: () => void room.react(emoji),
|
|
6675
|
+
children: emoji
|
|
6676
|
+
},
|
|
6677
|
+
emoji
|
|
6678
|
+
)) }) }) : null
|
|
6679
|
+
] }),
|
|
5821
6680
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5822
6681
|
ControlButton,
|
|
5823
6682
|
{
|
|
5824
|
-
label: room.local?.handRaised === true ? "Lower hand" : "Raise hand",
|
|
6683
|
+
label: room.local?.handRaised === true ? "Lower hand (\u2318\u2325H)" : "Raise hand (\u2318\u2325H)",
|
|
5825
6684
|
active: room.local?.handRaised === true,
|
|
5826
6685
|
onClick: () => void room.toggleHand(),
|
|
5827
6686
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, {})
|
|
5828
6687
|
}
|
|
5829
6688
|
),
|
|
5830
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
6689
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(ControlButton, { label: "Chat (\u2318\u2325C)", active: panel === "chat", onClick: () => toggle("chat"), children: [
|
|
6690
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatIcon, {}),
|
|
6691
|
+
unread > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6692
|
+
] }),
|
|
5831
6693
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5832
6694
|
ControlButton,
|
|
5833
6695
|
{
|
|
5834
6696
|
label: lobby.waiting.length > 0 ? `People \u2014 ${lobby.waiting.length} waiting` : `People \u2014 ${(snapshot?.participants ?? []).length} in the meeting`,
|
|
5835
6697
|
active: panel === "people",
|
|
5836
|
-
onClick: () =>
|
|
6698
|
+
onClick: () => toggle("people"),
|
|
5837
6699
|
children: [
|
|
5838
6700
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, {}),
|
|
5839
|
-
lobby.waiting.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6701
|
+
lobby.waiting.length > 0 || controls.isHost && queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
5840
6702
|
]
|
|
5841
6703
|
}
|
|
5842
6704
|
),
|
|
5843
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiControl, { active: panel === "ai", onToggle: () =>
|
|
6705
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
|
|
5844
6706
|
recording.canStart ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Start recording", onClick: () => void recording.start(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordIcon, {}) }) : null,
|
|
5845
6707
|
recording.canStop ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Stop recording", active: true, onClick: () => void recording.stop(), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RecordIcon, {}) }) : null,
|
|
6708
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
6709
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6710
|
+
ControlButton,
|
|
6711
|
+
{
|
|
6712
|
+
label: "More options",
|
|
6713
|
+
active: popover === "more" || panel === "settings",
|
|
6714
|
+
onClick: () => setPopover(popover === "more" ? "none" : "more"),
|
|
6715
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, {})
|
|
6716
|
+
}
|
|
6717
|
+
),
|
|
6718
|
+
popover === "more" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: "More options", onClose: () => setPopover("none"), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
6719
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6720
|
+
MenuItem,
|
|
6721
|
+
{
|
|
6722
|
+
onSelect: () => {
|
|
6723
|
+
setPopover("none");
|
|
6724
|
+
setPanel("settings");
|
|
6725
|
+
},
|
|
6726
|
+
children: [
|
|
6727
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SettingsIcon, { width: 16, height: 16 }),
|
|
6728
|
+
" Audio, video and effects"
|
|
6729
|
+
]
|
|
6730
|
+
}
|
|
6731
|
+
),
|
|
6732
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6733
|
+
MenuItem,
|
|
6734
|
+
{
|
|
6735
|
+
onSelect: () => {
|
|
6736
|
+
setPopover("none");
|
|
6737
|
+
setShortcuts(true);
|
|
6738
|
+
},
|
|
6739
|
+
children: [
|
|
6740
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KeyboardIcon, { width: 16, height: 16 }),
|
|
6741
|
+
" Keyboard shortcuts"
|
|
6742
|
+
]
|
|
6743
|
+
}
|
|
6744
|
+
),
|
|
6745
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6746
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
6747
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6748
|
+
MenuItem,
|
|
6749
|
+
{
|
|
6750
|
+
onSelect: () => {
|
|
6751
|
+
setPopover("none");
|
|
6752
|
+
void controls.run({ action: "mute_all" });
|
|
6753
|
+
},
|
|
6754
|
+
children: [
|
|
6755
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
6756
|
+
" Mute everyone"
|
|
6757
|
+
]
|
|
6758
|
+
}
|
|
6759
|
+
),
|
|
6760
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6761
|
+
MenuItem,
|
|
6762
|
+
{
|
|
6763
|
+
onSelect: () => {
|
|
6764
|
+
setPopover("none");
|
|
6765
|
+
void controls.run({ action: "ask_all_unmute" });
|
|
6766
|
+
},
|
|
6767
|
+
children: [
|
|
6768
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 16, height: 16 }),
|
|
6769
|
+
" Ask everyone to unmute"
|
|
6770
|
+
]
|
|
6771
|
+
}
|
|
6772
|
+
),
|
|
6773
|
+
queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6774
|
+
MenuItem,
|
|
6775
|
+
{
|
|
6776
|
+
onSelect: () => {
|
|
6777
|
+
setPopover("none");
|
|
6778
|
+
void controls.run({ action: "lower_all_hands" });
|
|
6779
|
+
},
|
|
6780
|
+
children: [
|
|
6781
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 16, height: 16 }),
|
|
6782
|
+
" Lower all hands (",
|
|
6783
|
+
queue.length,
|
|
6784
|
+
")"
|
|
6785
|
+
]
|
|
6786
|
+
}
|
|
6787
|
+
) : null,
|
|
6788
|
+
hasSpotlight ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6789
|
+
MenuItem,
|
|
6790
|
+
{
|
|
6791
|
+
onSelect: () => {
|
|
6792
|
+
setPopover("none");
|
|
6793
|
+
void controls.run({ action: "spotlight", identity: null });
|
|
6794
|
+
},
|
|
6795
|
+
children: [
|
|
6796
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
6797
|
+
" Remove spotlight"
|
|
6798
|
+
]
|
|
6799
|
+
}
|
|
6800
|
+
) : null
|
|
6801
|
+
] }) : null
|
|
6802
|
+
] }) }) : null
|
|
6803
|
+
] }),
|
|
5846
6804
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5847
6805
|
ControlButton,
|
|
5848
6806
|
{
|
|
@@ -5858,6 +6816,150 @@ function ControlBar({ onLeave }) {
|
|
|
5858
6816
|
] })
|
|
5859
6817
|
] });
|
|
5860
6818
|
}
|
|
6819
|
+
function useUnreadChat(open) {
|
|
6820
|
+
const snapshot = useMeetSnapshot();
|
|
6821
|
+
const chat = snapshot?.chat ?? [];
|
|
6822
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6823
|
+
const [seen, setSeen] = (0, import_react4.useState)(0);
|
|
6824
|
+
(0, import_react4.useEffect)(() => {
|
|
6825
|
+
if (open) setSeen(chat.length);
|
|
6826
|
+
}, [open, chat.length]);
|
|
6827
|
+
const loaded = snapshot?.chatLoaded === true;
|
|
6828
|
+
const [baseline, setBaseline] = (0, import_react4.useState)(null);
|
|
6829
|
+
(0, import_react4.useEffect)(() => {
|
|
6830
|
+
if (loaded && baseline === null) setBaseline(chat.length);
|
|
6831
|
+
}, [loaded, baseline, chat.length]);
|
|
6832
|
+
if (open) return 0;
|
|
6833
|
+
const from = Math.max(seen, baseline ?? chat.length);
|
|
6834
|
+
return chat.slice(from).filter((message) => message.identity !== local).length;
|
|
6835
|
+
}
|
|
6836
|
+
function Popover({
|
|
6837
|
+
label,
|
|
6838
|
+
onClose,
|
|
6839
|
+
children,
|
|
6840
|
+
placement = "up"
|
|
6841
|
+
}) {
|
|
6842
|
+
const ref = (0, import_react4.useRef)(null);
|
|
6843
|
+
const [style, setStyle] = (0, import_react4.useState)({ visibility: "hidden" });
|
|
6844
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
6845
|
+
const element = ref.current;
|
|
6846
|
+
const anchor = element?.parentElement ?? null;
|
|
6847
|
+
if (element === null || anchor === null) return;
|
|
6848
|
+
const place = () => {
|
|
6849
|
+
const rect = anchor.getBoundingClientRect();
|
|
6850
|
+
const width = element.offsetWidth;
|
|
6851
|
+
const height = element.offsetHeight;
|
|
6852
|
+
const gutter = 8;
|
|
6853
|
+
const left = Math.min(
|
|
6854
|
+
Math.max(gutter, rect.left + rect.width / 2 - width / 2),
|
|
6855
|
+
window.innerWidth - width - gutter
|
|
6856
|
+
);
|
|
6857
|
+
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6858
|
+
const fitsAbove = rect.top - gutter - height >= 0;
|
|
6859
|
+
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6860
|
+
setStyle(
|
|
6861
|
+
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6862
|
+
);
|
|
6863
|
+
};
|
|
6864
|
+
place();
|
|
6865
|
+
window.addEventListener("resize", place);
|
|
6866
|
+
return () => window.removeEventListener("resize", place);
|
|
6867
|
+
}, [placement]);
|
|
6868
|
+
(0, import_react4.useEffect)(() => {
|
|
6869
|
+
const onKey = (event) => {
|
|
6870
|
+
if (event.key === "Escape") onClose();
|
|
6871
|
+
};
|
|
6872
|
+
const onPointer = (event) => {
|
|
6873
|
+
const anchor = ref.current?.parentElement ?? null;
|
|
6874
|
+
if (anchor !== null && !anchor.contains(event.target)) onClose();
|
|
6875
|
+
};
|
|
6876
|
+
window.addEventListener("keydown", onKey);
|
|
6877
|
+
window.addEventListener("pointerdown", onPointer);
|
|
6878
|
+
return () => {
|
|
6879
|
+
window.removeEventListener("keydown", onKey);
|
|
6880
|
+
window.removeEventListener("pointerdown", onPointer);
|
|
6881
|
+
};
|
|
6882
|
+
}, [onClose]);
|
|
6883
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref, className: "mx-meet__popover", style, role: "dialog", "aria-label": label, children });
|
|
6884
|
+
}
|
|
6885
|
+
function MenuItem({
|
|
6886
|
+
onSelect,
|
|
6887
|
+
danger = false,
|
|
6888
|
+
children
|
|
6889
|
+
}) {
|
|
6890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6891
|
+
"button",
|
|
6892
|
+
{
|
|
6893
|
+
type: "button",
|
|
6894
|
+
role: "menuitem",
|
|
6895
|
+
className: danger ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
|
|
6896
|
+
onClick: onSelect,
|
|
6897
|
+
children
|
|
6898
|
+
}
|
|
6899
|
+
);
|
|
6900
|
+
}
|
|
6901
|
+
function Toast({ text: text3, failed, onDone }) {
|
|
6902
|
+
(0, import_react4.useEffect)(() => {
|
|
6903
|
+
const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
|
|
6904
|
+
return () => clearTimeout(timer);
|
|
6905
|
+
}, [text3, failed, onDone]);
|
|
6906
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: failed ? "mx-meet__toast mx-meet__toast--failed" : "mx-meet__toast", role: "status", children: [
|
|
6907
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: text3 }),
|
|
6908
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
6909
|
+
] });
|
|
6910
|
+
}
|
|
6911
|
+
function MeetingOverlays() {
|
|
6912
|
+
const reactions = useReactions();
|
|
6913
|
+
const { notice, dismiss } = useModerationNotice();
|
|
6914
|
+
const room = useRoom();
|
|
6915
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
6916
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReactionOverlay, { reactions }),
|
|
6917
|
+
notice !== null && notice.kind !== "removed" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__notice-bar", role: "alert", children: [
|
|
6918
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: notice.text }),
|
|
6919
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
6920
|
+
"button",
|
|
6921
|
+
{
|
|
6922
|
+
type: "button",
|
|
6923
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
6924
|
+
onClick: () => {
|
|
6925
|
+
dismiss();
|
|
6926
|
+
void room.toggleMicrophone();
|
|
6927
|
+
},
|
|
6928
|
+
children: "Unmute"
|
|
6929
|
+
}
|
|
6930
|
+
) : null,
|
|
6931
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
6932
|
+
] }) : null
|
|
6933
|
+
] });
|
|
6934
|
+
}
|
|
6935
|
+
function ShortcutsSheet({ onClose }) {
|
|
6936
|
+
(0, import_react4.useEffect)(() => {
|
|
6937
|
+
const onKey = (event) => {
|
|
6938
|
+
if (event.key === "Escape") onClose();
|
|
6939
|
+
};
|
|
6940
|
+
window.addEventListener("keydown", onKey);
|
|
6941
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
6942
|
+
}, [onClose]);
|
|
6943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__sheet-backdrop", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
6944
|
+
"div",
|
|
6945
|
+
{
|
|
6946
|
+
className: "mx-meet__sheet",
|
|
6947
|
+
role: "dialog",
|
|
6948
|
+
"aria-label": "Keyboard shortcuts",
|
|
6949
|
+
onClick: (event) => event.stopPropagation(),
|
|
6950
|
+
children: [
|
|
6951
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "mx-meet__panel-head", children: [
|
|
6952
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "mx-meet__panel-title", children: "Keyboard shortcuts" }),
|
|
6953
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ControlButton, { label: "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 16, height: 16 }) })
|
|
6954
|
+
] }),
|
|
6955
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dl", { className: "mx-meet__shortcuts", children: MEET_SHORTCUTS.map((shortcut) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__shortcut", children: [
|
|
6956
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dt", { children: shortcut.label }),
|
|
6957
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("dd", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("kbd", { className: "mx-meet__kbd", children: shortcut.keys }) })
|
|
6958
|
+
] }, shortcut.id)) })
|
|
6959
|
+
]
|
|
6960
|
+
}
|
|
6961
|
+
) });
|
|
6962
|
+
}
|
|
5861
6963
|
function AiControl({ active, onToggle }) {
|
|
5862
6964
|
const snapshot = useMeetSnapshot();
|
|
5863
6965
|
const meeting = snapshot?.meeting ?? null;
|
|
@@ -5877,117 +6979,640 @@ function PanelShell({
|
|
|
5877
6979
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__panel-body", children })
|
|
5878
6980
|
] });
|
|
5879
6981
|
}
|
|
5880
|
-
function ChatPanel({
|
|
6982
|
+
function ChatPanel({
|
|
6983
|
+
onClose,
|
|
6984
|
+
to = null,
|
|
6985
|
+
onTo
|
|
6986
|
+
}) {
|
|
5881
6987
|
const snapshot = useMeetSnapshot();
|
|
5882
6988
|
const room = useRoom();
|
|
5883
6989
|
const [draft, setDraft] = (0, import_react4.useState)("");
|
|
6990
|
+
const [recipient, setRecipient] = (0, import_react4.useState)(to);
|
|
6991
|
+
const listRef = (0, import_react4.useRef)(null);
|
|
5884
6992
|
const messages = snapshot?.chat ?? [];
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
6993
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6994
|
+
const people = (snapshot?.participants ?? []).filter(
|
|
6995
|
+
(participant) => !participant.isLocal && !participant.isAgent
|
|
6996
|
+
);
|
|
6997
|
+
const recipientName = recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;
|
|
6998
|
+
(0, import_react4.useEffect)(() => setRecipient(to), [to]);
|
|
6999
|
+
(0, import_react4.useEffect)(() => {
|
|
7000
|
+
const list = listRef.current;
|
|
7001
|
+
if (list !== null) list.scrollTop = list.scrollHeight;
|
|
7002
|
+
}, [messages.length]);
|
|
7003
|
+
const choose = (next) => {
|
|
7004
|
+
setRecipient(next);
|
|
7005
|
+
onTo?.(next);
|
|
7006
|
+
};
|
|
7007
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: "Chat", onClose, children: [
|
|
7008
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "mx-meet__chat", ref: listRef, children: [
|
|
7009
|
+
snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: "mx-meet__panel-note", children: "No messages yet. Messages to everyone stay with the meeting, so people who join later see them." }) : null,
|
|
7010
|
+
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatLine, { message, local, people }, message.id))
|
|
7011
|
+
] }),
|
|
5891
7012
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
5892
7013
|
"form",
|
|
5893
7014
|
{
|
|
5894
|
-
className: "mx-meet__composer",
|
|
7015
|
+
className: "mx-meet__composer mx-meet__composer--chat",
|
|
5895
7016
|
onSubmit: (event) => {
|
|
5896
7017
|
event.preventDefault();
|
|
5897
7018
|
const text3 = draft;
|
|
7019
|
+
if (text3.trim().length === 0) return;
|
|
5898
7020
|
setDraft("");
|
|
5899
|
-
void room.sendChat(text3);
|
|
7021
|
+
if (recipient === null) void room.sendChat(text3).catch(() => void 0);
|
|
7022
|
+
else void room.sendPrivateChat(recipient, text3);
|
|
5900
7023
|
},
|
|
5901
7024
|
children: [
|
|
5902
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
5903
|
-
"
|
|
7025
|
+
people.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7026
|
+
"select",
|
|
5904
7027
|
{
|
|
5905
|
-
className: "mx-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
7028
|
+
className: "mx-meet__select mx-meet__select--compact",
|
|
7029
|
+
"aria-label": "Send to",
|
|
7030
|
+
value: recipient ?? "",
|
|
7031
|
+
onChange: (event) => choose(event.target.value === "" ? null : event.target.value),
|
|
7032
|
+
children: [
|
|
7033
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Everyone" }),
|
|
7034
|
+
people.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("option", { value: participant.identity, children: [
|
|
7035
|
+
participant.displayName,
|
|
7036
|
+
" (private)"
|
|
7037
|
+
] }, participant.identity))
|
|
7038
|
+
]
|
|
5910
7039
|
}
|
|
5911
|
-
),
|
|
5912
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
7040
|
+
) : null,
|
|
7041
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__composer-row", children: [
|
|
7042
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7043
|
+
"input",
|
|
7044
|
+
{
|
|
7045
|
+
className: "mx-meet__input",
|
|
7046
|
+
value: draft,
|
|
7047
|
+
onChange: (event) => setDraft(event.target.value),
|
|
7048
|
+
placeholder: recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
7049
|
+
"aria-label": recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
7050
|
+
maxLength: 4e3
|
|
7051
|
+
}
|
|
7052
|
+
),
|
|
7053
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: draft.trim().length === 0, children: "Send" })
|
|
7054
|
+
] }),
|
|
7055
|
+
recipientName !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
7056
|
+
"Only ",
|
|
7057
|
+
recipientName,
|
|
7058
|
+
" sees this. Private messages are not saved."
|
|
7059
|
+
] }) : null
|
|
5913
7060
|
]
|
|
5914
7061
|
}
|
|
5915
7062
|
)
|
|
5916
7063
|
] });
|
|
5917
7064
|
}
|
|
5918
|
-
function
|
|
7065
|
+
function ChatLine({
|
|
7066
|
+
message,
|
|
7067
|
+
local,
|
|
7068
|
+
people
|
|
7069
|
+
}) {
|
|
7070
|
+
const mine = message.identity === local;
|
|
7071
|
+
const privateTo = message.to ?? null;
|
|
7072
|
+
const counterpart = privateTo === null ? null : mine ? people.find((p) => p.identity === privateTo)?.displayName ?? "them" : "you";
|
|
7073
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7074
|
+
"li",
|
|
7075
|
+
{
|
|
7076
|
+
className: [
|
|
7077
|
+
"mx-meet__chat-line",
|
|
7078
|
+
privateTo !== null ? "mx-meet__chat-line--private" : "",
|
|
7079
|
+
message.failed !== void 0 ? "mx-meet__chat-line--failed" : ""
|
|
7080
|
+
].filter(Boolean).join(" "),
|
|
7081
|
+
children: [
|
|
7082
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__chat-meta", children: [
|
|
7083
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: mine ? "You" : message.displayName }),
|
|
7084
|
+
counterpart !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__chat-private", children: [
|
|
7085
|
+
"to ",
|
|
7086
|
+
counterpart,
|
|
7087
|
+
" \xB7 private"
|
|
7088
|
+
] }) : null,
|
|
7089
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("time", { dateTime: message.at, children: clockOf(message.at) })
|
|
7090
|
+
] }),
|
|
7091
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__chat-text", children: message.text }),
|
|
7092
|
+
message.failed !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__chat-failed", children: message.failed }) : privateTo === null && message.saved === false ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__chat-pending", children: message.id.startsWith("pending-") ? "Sending\u2026" : "Not saved \u2014 people who join later will not see this" }) : null
|
|
7093
|
+
]
|
|
7094
|
+
}
|
|
7095
|
+
);
|
|
7096
|
+
}
|
|
7097
|
+
function PeoplePanel({
|
|
7098
|
+
onClose,
|
|
7099
|
+
onMessage
|
|
7100
|
+
}) {
|
|
5919
7101
|
const snapshot = useMeetSnapshot();
|
|
5920
7102
|
const controls = useHostControls();
|
|
7103
|
+
const queue = useHandQueue();
|
|
7104
|
+
const [query, setQuery] = (0, import_react4.useState)("");
|
|
7105
|
+
const [menuFor, setMenuFor] = (0, import_react4.useState)(null);
|
|
5921
7106
|
const myRole = useRole();
|
|
5922
7107
|
const local = snapshot?.localIdentity ?? null;
|
|
5923
|
-
|
|
7108
|
+
const everyone = snapshot?.participants ?? [];
|
|
7109
|
+
const needle = query.trim().toLowerCase();
|
|
7110
|
+
const shown = needle.length === 0 ? everyone : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));
|
|
7111
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: `People (${everyone.length})`, onClose, children: [
|
|
5924
7112
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LobbyPanel, {}),
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
7113
|
+
queue.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Raised hands", children: [
|
|
7114
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "mx-meet__section-head", children: [
|
|
7115
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("h3", { className: "mx-meet__section-title", children: [
|
|
7116
|
+
"Raised hands \xB7 ",
|
|
7117
|
+
queue.length
|
|
7118
|
+
] }),
|
|
7119
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7120
|
+
"button",
|
|
5932
7121
|
{
|
|
5933
|
-
|
|
7122
|
+
type: "button",
|
|
7123
|
+
className: "mx-meet__link",
|
|
7124
|
+
onClick: () => void controls.run({ action: "lower_all_hands" }),
|
|
7125
|
+
children: "Lower all"
|
|
5934
7126
|
}
|
|
5935
|
-
)
|
|
5936
|
-
isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null
|
|
7127
|
+
) : null
|
|
5937
7128
|
] }),
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
7129
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ol", { className: "mx-meet__queue", children: queue.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__person", children: [
|
|
7130
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 28 }),
|
|
7131
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
7132
|
+
participant.displayName,
|
|
7133
|
+
participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null
|
|
7134
|
+
] }),
|
|
7135
|
+
controls.isHost || participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7136
|
+
"button",
|
|
7137
|
+
{
|
|
7138
|
+
type: "button",
|
|
7139
|
+
className: "mx-meet__link",
|
|
7140
|
+
onClick: () => void controls.run({ action: "lower_hand", identity: participant.identity }),
|
|
7141
|
+
children: "Lower"
|
|
7142
|
+
}
|
|
7143
|
+
) : null
|
|
7144
|
+
] }, participant.identity)) })
|
|
7145
|
+
] }) : null,
|
|
7146
|
+
controls.isHost && everyone.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__bulk", children: [
|
|
7147
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("button", { type: "button", className: "mx-meet__chip", onClick: () => void controls.run({ action: "mute_all" }), children: [
|
|
7148
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }),
|
|
7149
|
+
" Mute all"
|
|
7150
|
+
] }),
|
|
7151
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7152
|
+
"button",
|
|
7153
|
+
{
|
|
7154
|
+
type: "button",
|
|
7155
|
+
className: "mx-meet__chip",
|
|
7156
|
+
onClick: () => void controls.run({ action: "ask_all_unmute" }),
|
|
7157
|
+
children: [
|
|
7158
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 14, height: 14 }),
|
|
7159
|
+
" Ask all to unmute"
|
|
7160
|
+
]
|
|
7161
|
+
}
|
|
7162
|
+
)
|
|
7163
|
+
] }) : null,
|
|
7164
|
+
everyone.length > 4 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__search", children: [
|
|
7165
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SearchIcon, { width: 16, height: 16 }),
|
|
7166
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7167
|
+
"input",
|
|
7168
|
+
{
|
|
7169
|
+
className: "mx-meet__input mx-meet__input--bare",
|
|
7170
|
+
value: query,
|
|
7171
|
+
onChange: (event) => setQuery(event.target.value),
|
|
7172
|
+
placeholder: "Search people",
|
|
7173
|
+
"aria-label": "Search people"
|
|
7174
|
+
}
|
|
7175
|
+
)
|
|
7176
|
+
] }) : null,
|
|
7177
|
+
controls.notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: controls.notice.failed ? "mx-meet__error" : "mx-meet__notice", role: "status", children: controls.notice.text }) : null,
|
|
7178
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("ul", { className: "mx-meet__people", children: [
|
|
7179
|
+
shown.map((participant) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__person", children: [
|
|
7180
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 32 }),
|
|
7181
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__person-name", children: [
|
|
7182
|
+
participant.displayName,
|
|
7183
|
+
participant.identity === local ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__you", children: " (you)" }) : null,
|
|
7184
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
|
|
7185
|
+
isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
|
|
7186
|
+
snapshot?.spotlight === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
|
|
7187
|
+
] }),
|
|
7188
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 14, height: 14 }) : null,
|
|
7189
|
+
!participant.micEnabled && !isNoteTaker(participant) ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
7190
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(QualityBars, { quality: participant.quality }),
|
|
7191
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
7192
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7193
|
+
ControlButton,
|
|
7194
|
+
{
|
|
7195
|
+
label: `More for ${participant.displayName}`,
|
|
7196
|
+
active: menuFor === participant.identity,
|
|
7197
|
+
onClick: () => setMenuFor(menuFor === participant.identity ? null : participant.identity),
|
|
7198
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreIcon, { width: 16, height: 16 })
|
|
7199
|
+
}
|
|
7200
|
+
),
|
|
7201
|
+
menuFor === participant.identity ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7202
|
+
PersonMenu,
|
|
7203
|
+
{
|
|
7204
|
+
participant,
|
|
7205
|
+
placement: "down",
|
|
7206
|
+
onClose: () => setMenuFor(null),
|
|
7207
|
+
...onMessage !== void 0 ? { onMessage } : {}
|
|
7208
|
+
}
|
|
7209
|
+
) : null
|
|
7210
|
+
] })
|
|
7211
|
+
] }, participant.identity)),
|
|
7212
|
+
shown.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("li", { className: "mx-meet__panel-note", children: [
|
|
7213
|
+
"Nobody matches \u201C",
|
|
7214
|
+
query,
|
|
7215
|
+
"\u201D."
|
|
7216
|
+
] }) : null
|
|
7217
|
+
] }),
|
|
7218
|
+
snapshot?.meeting !== null && snapshot?.meeting !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GuestRecordAccessControl, { meeting: snapshot.meeting }) : null
|
|
5942
7219
|
] });
|
|
5943
7220
|
}
|
|
5944
|
-
function
|
|
7221
|
+
function PersonMenu({
|
|
5945
7222
|
participant,
|
|
5946
|
-
|
|
7223
|
+
onClose,
|
|
7224
|
+
onMessage,
|
|
7225
|
+
placement = "down"
|
|
5947
7226
|
}) {
|
|
5948
|
-
const
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
7227
|
+
const host = useMeetHost();
|
|
7228
|
+
const snapshot = useMeetSnapshot();
|
|
7229
|
+
const controls = useHostControls();
|
|
7230
|
+
const [mode, setMode] = (0, import_react4.useState)("menu");
|
|
7231
|
+
const [text3, setText] = (0, import_react4.useState)("");
|
|
7232
|
+
const self = participant.identity === snapshot?.localIdentity;
|
|
7233
|
+
const moderate = controls.canModerate(participant);
|
|
7234
|
+
const pinned = snapshot?.pinned === participant.identity;
|
|
7235
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
7236
|
+
const hasAccount = participant.userId !== null;
|
|
7237
|
+
const act = (request) => {
|
|
7238
|
+
void controls.run(request).then((result) => {
|
|
7239
|
+
if (result !== null) onClose();
|
|
7240
|
+
});
|
|
7241
|
+
};
|
|
7242
|
+
if (mode === "rename" || mode === "report") {
|
|
7243
|
+
const renaming = mode === "rename";
|
|
7244
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7245
|
+
"form",
|
|
7246
|
+
{
|
|
7247
|
+
className: "mx-meet__menu-form",
|
|
7248
|
+
onSubmit: (event) => {
|
|
7249
|
+
event.preventDefault();
|
|
7250
|
+
if (text3.trim().length === 0) return;
|
|
7251
|
+
act(
|
|
7252
|
+
renaming ? { action: "rename", identity: participant.identity, name: text3.trim() } : { action: "report", identity: participant.identity, reason: text3.trim() }
|
|
7253
|
+
);
|
|
7254
|
+
},
|
|
7255
|
+
children: [
|
|
7256
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__field", children: [
|
|
7257
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__field-label", children: renaming ? "Name shown in this meeting" : `What did ${participant.displayName} do?` }),
|
|
7258
|
+
renaming ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7259
|
+
"input",
|
|
7260
|
+
{
|
|
7261
|
+
className: "mx-meet__input",
|
|
7262
|
+
autoFocus: true,
|
|
7263
|
+
maxLength: 80,
|
|
7264
|
+
value: text3,
|
|
7265
|
+
onChange: (event) => setText(event.target.value)
|
|
7266
|
+
}
|
|
7267
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7268
|
+
"textarea",
|
|
7269
|
+
{
|
|
7270
|
+
className: "mx-meet__input",
|
|
7271
|
+
autoFocus: true,
|
|
7272
|
+
rows: 3,
|
|
7273
|
+
maxLength: 1e3,
|
|
7274
|
+
value: text3,
|
|
7275
|
+
onChange: (event) => setText(event.target.value)
|
|
7276
|
+
}
|
|
7277
|
+
)
|
|
7278
|
+
] }),
|
|
7279
|
+
controls.notice?.failed === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__error", children: controls.notice.text }) : null,
|
|
7280
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7281
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7282
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
7283
|
+
] })
|
|
7284
|
+
]
|
|
7285
|
+
}
|
|
7286
|
+
) });
|
|
7287
|
+
}
|
|
7288
|
+
if (mode === "remove" || mode === "transfer") {
|
|
7289
|
+
const removing = mode === "remove";
|
|
7290
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-form", children: [
|
|
7291
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__confirm-text", children: removing ? `Remove ${participant.displayName}? They leave immediately and cannot rejoin this meeting.` : `Make ${participant.displayName} the host? They get every host control and own this meeting. You stay on as a co-host.` }),
|
|
7292
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
7293
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7294
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7295
|
+
"button",
|
|
7296
|
+
{
|
|
7297
|
+
type: "button",
|
|
7298
|
+
className: removing ? "mx-meet__send mx-meet__send--danger" : "mx-meet__send",
|
|
7299
|
+
disabled: controls.busy,
|
|
7300
|
+
onClick: () => act({ action: removing ? "remove" : "transfer_host", identity: participant.identity }),
|
|
7301
|
+
children: removing ? "Remove" : "Make host"
|
|
7302
|
+
}
|
|
7303
|
+
)
|
|
7304
|
+
] })
|
|
7305
|
+
] }) });
|
|
7306
|
+
}
|
|
7307
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Popover, { label: `Options for ${participant.displayName}`, onClose, placement, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
7308
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7309
|
+
MenuItem,
|
|
7310
|
+
{
|
|
7311
|
+
onSelect: () => {
|
|
7312
|
+
host?.engine.pin(pinned ? null : participant.identity);
|
|
7313
|
+
onClose();
|
|
7314
|
+
},
|
|
7315
|
+
children: [
|
|
7316
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PinIcon, { width: 16, height: 16 }),
|
|
7317
|
+
" ",
|
|
7318
|
+
pinned ? "Unpin" : "Pin for me"
|
|
7319
|
+
]
|
|
7320
|
+
}
|
|
7321
|
+
),
|
|
7322
|
+
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7323
|
+
MenuItem,
|
|
7324
|
+
{
|
|
7325
|
+
onSelect: () => {
|
|
7326
|
+
onMessage(participant.identity);
|
|
7327
|
+
onClose();
|
|
7328
|
+
},
|
|
7329
|
+
children: [
|
|
7330
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ChatIcon, { width: 16, height: 16 }),
|
|
7331
|
+
" Message privately"
|
|
7332
|
+
]
|
|
7333
|
+
}
|
|
7334
|
+
) : null,
|
|
7335
|
+
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "spotlight", identity: spotlit ? null : participant.identity }), children: [
|
|
7336
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
7337
|
+
" ",
|
|
7338
|
+
spotlit ? "Remove spotlight" : "Spotlight for everyone"
|
|
7339
|
+
] }) : null,
|
|
7340
|
+
moderate ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
7341
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
7342
|
+
participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "mute", identity: participant.identity }), children: [
|
|
7343
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
7344
|
+
" Mute"
|
|
7345
|
+
] }) : !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "ask_unmute", identity: participant.identity }), children: [
|
|
7346
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MicIcon, { width: 16, height: 16 }),
|
|
7347
|
+
" Ask to unmute"
|
|
7348
|
+
] }) : null,
|
|
7349
|
+
participant.cameraEnabled ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_video", identity: participant.identity }), children: [
|
|
7350
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CameraOffIcon, { width: 16, height: 16 }),
|
|
7351
|
+
" Stop video"
|
|
7352
|
+
] }) : null,
|
|
7353
|
+
participant.screenSharing ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "stop_screen", identity: participant.identity }), children: [
|
|
7354
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
7355
|
+
" Stop screen share"
|
|
7356
|
+
] }) : null,
|
|
7357
|
+
participant.handRaised ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "lower_hand", identity: participant.identity }), children: [
|
|
7358
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(HandIcon, { width: 16, height: 16 }),
|
|
7359
|
+
" Lower hand"
|
|
7360
|
+
] }) : null,
|
|
7361
|
+
hasAccount && !participant.isAgent && participant.role !== "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "make_cohost", identity: participant.identity }), children: [
|
|
7362
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
7363
|
+
" Make co-host"
|
|
7364
|
+
] }) : null,
|
|
7365
|
+
!participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7366
|
+
MenuItem,
|
|
5958
7367
|
{
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
setConfirming(false);
|
|
5963
|
-
void controls.remove(participant);
|
|
7368
|
+
onSelect: () => {
|
|
7369
|
+
setText(participant.displayName);
|
|
7370
|
+
setMode("rename");
|
|
5964
7371
|
},
|
|
5965
|
-
children:
|
|
7372
|
+
children: [
|
|
7373
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PencilIcon, { width: 16, height: 16 }),
|
|
7374
|
+
" Rename"
|
|
7375
|
+
]
|
|
5966
7376
|
}
|
|
5967
|
-
)
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
7377
|
+
) : null
|
|
7378
|
+
] }) : null,
|
|
7379
|
+
controls.isOwner && participant.role === "cohost" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => act({ action: "revoke_cohost", identity: participant.identity }), children: [
|
|
7380
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
7381
|
+
" Remove co-host"
|
|
7382
|
+
] }) : null,
|
|
7383
|
+
controls.isOwner && !self && hasAccount && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { onSelect: () => setMode("transfer"), children: [
|
|
7384
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpotlightIcon, { width: 16, height: 16 }),
|
|
7385
|
+
" Make host"
|
|
7386
|
+
] }) : null,
|
|
7387
|
+
self ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7388
|
+
MenuItem,
|
|
5974
7389
|
{
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
7390
|
+
onSelect: () => {
|
|
7391
|
+
setText(participant.displayName);
|
|
7392
|
+
setMode("rename");
|
|
7393
|
+
},
|
|
7394
|
+
children: [
|
|
7395
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PencilIcon, { width: 16, height: 16 }),
|
|
7396
|
+
" Rename yourself"
|
|
7397
|
+
]
|
|
5978
7398
|
}
|
|
5979
7399
|
) : null,
|
|
7400
|
+
!self && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7401
|
+
MenuItem,
|
|
7402
|
+
{
|
|
7403
|
+
onSelect: () => {
|
|
7404
|
+
setText("");
|
|
7405
|
+
setMode("report");
|
|
7406
|
+
},
|
|
7407
|
+
children: [
|
|
7408
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FlagIcon, { width: 16, height: 16 }),
|
|
7409
|
+
" Report"
|
|
7410
|
+
]
|
|
7411
|
+
}
|
|
7412
|
+
) : null,
|
|
7413
|
+
moderate && !participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MenuItem, { danger: true, onSelect: () => setMode("remove"), children: [
|
|
7414
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CloseIcon, { width: 16, height: 16 }),
|
|
7415
|
+
" Remove from meeting"
|
|
7416
|
+
] }) : null
|
|
7417
|
+
] }) });
|
|
7418
|
+
}
|
|
7419
|
+
function SettingsPanel({ onClose }) {
|
|
7420
|
+
const host = useRequiredMeetHost();
|
|
7421
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PanelShell, { title: "Audio, video and effects", onClose, children: [
|
|
5980
7422
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5981
|
-
|
|
7423
|
+
DeviceSettings,
|
|
5982
7424
|
{
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
7425
|
+
onPick: (kind, deviceId2) => {
|
|
7426
|
+
host.devices.select(kind, deviceId2);
|
|
7427
|
+
void host.engine.switchDevice(kind, deviceId2);
|
|
7428
|
+
}
|
|
5987
7429
|
}
|
|
5988
|
-
)
|
|
7430
|
+
),
|
|
7431
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EffectsSettings, {})
|
|
7432
|
+
] });
|
|
7433
|
+
}
|
|
7434
|
+
function DeviceSettings({
|
|
7435
|
+
onPick
|
|
7436
|
+
}) {
|
|
7437
|
+
const host = useRequiredMeetHost();
|
|
7438
|
+
const devices = useDeviceSnapshot();
|
|
7439
|
+
const microphone = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7440
|
+
const speaker = devices?.selected.audiooutput ?? host.devices.resolve("audiooutput");
|
|
7441
|
+
const stream = useMicrophoneStream(microphone);
|
|
7442
|
+
const level = useAudioLevel(stream);
|
|
7443
|
+
const [testing, setTesting] = (0, import_react4.useState)(false);
|
|
7444
|
+
(0, import_react4.useEffect)(() => {
|
|
7445
|
+
void host.devices.refresh();
|
|
7446
|
+
}, [host]);
|
|
7447
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Devices", children: [
|
|
7448
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone", onPick }),
|
|
7449
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__meter", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }),
|
|
7450
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker", onPick }),
|
|
7451
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7452
|
+
"button",
|
|
7453
|
+
{
|
|
7454
|
+
type: "button",
|
|
7455
|
+
className: "mx-meet__chip",
|
|
7456
|
+
disabled: testing,
|
|
7457
|
+
onClick: () => {
|
|
7458
|
+
setTesting(true);
|
|
7459
|
+
void playTestSound(speaker).finally(() => setTesting(false));
|
|
7460
|
+
},
|
|
7461
|
+
children: [
|
|
7462
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpeakerIcon, { width: 14, height: 14 }),
|
|
7463
|
+
" ",
|
|
7464
|
+
testing ? "Playing\u2026" : "Test speaker"
|
|
7465
|
+
]
|
|
7466
|
+
}
|
|
7467
|
+
),
|
|
7468
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "videoinput", label: "Camera", onPick })
|
|
7469
|
+
] });
|
|
7470
|
+
}
|
|
7471
|
+
function EffectsSettings({ preview }) {
|
|
7472
|
+
const effects = useMediaEffects();
|
|
7473
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
7474
|
+
const fileRef = (0, import_react4.useRef)(null);
|
|
7475
|
+
const supported = effects.backgroundsSupported || preview != null;
|
|
7476
|
+
const apply = (next) => {
|
|
7477
|
+
setError(null);
|
|
7478
|
+
void effects.set(next);
|
|
7479
|
+
if (preview != null) {
|
|
7480
|
+
const merged = { ...effects.effects, ...next };
|
|
7481
|
+
void preview.setEffect(toVideoEffect(merged)).catch(
|
|
7482
|
+
() => setError("This browser could not run the background effect.")
|
|
7483
|
+
);
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
7486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "mx-meet__section", "aria-label": "Effects", children: [
|
|
7487
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__toggle", children: [
|
|
7488
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7489
|
+
"input",
|
|
7490
|
+
{
|
|
7491
|
+
type: "checkbox",
|
|
7492
|
+
checked: effects.effects.noiseSuppression,
|
|
7493
|
+
onChange: (event) => apply({ noiseSuppression: event.target.checked })
|
|
7494
|
+
}
|
|
7495
|
+
),
|
|
7496
|
+
"Noise suppression",
|
|
7497
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
|
|
7498
|
+
] }),
|
|
7499
|
+
supported ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__field", children: [
|
|
7500
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-meet__field-label", children: "Background" }),
|
|
7501
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__pills", role: "radiogroup", "aria-label": "Background", children: ["none", "blur", "image"].map((kind) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7502
|
+
"button",
|
|
7503
|
+
{
|
|
7504
|
+
type: "button",
|
|
7505
|
+
role: "radio",
|
|
7506
|
+
"aria-checked": effects.effects.background === kind,
|
|
7507
|
+
className: effects.effects.background === kind ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
|
|
7508
|
+
onClick: () => {
|
|
7509
|
+
if (kind === "image") {
|
|
7510
|
+
fileRef.current?.click();
|
|
7511
|
+
return;
|
|
7512
|
+
}
|
|
7513
|
+
apply({ background: kind });
|
|
7514
|
+
},
|
|
7515
|
+
children: kind === "none" ? "None" : kind === "blur" ? "Blur" : "Picture\u2026"
|
|
7516
|
+
},
|
|
7517
|
+
kind
|
|
7518
|
+
)) }),
|
|
7519
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7520
|
+
"input",
|
|
7521
|
+
{
|
|
7522
|
+
ref: fileRef,
|
|
7523
|
+
type: "file",
|
|
7524
|
+
accept: "image/*",
|
|
7525
|
+
hidden: true,
|
|
7526
|
+
onChange: (event) => {
|
|
7527
|
+
const file = event.target.files?.[0];
|
|
7528
|
+
event.target.value = "";
|
|
7529
|
+
if (file === void 0) return;
|
|
7530
|
+
void shrinkImage(file).then((url) => apply({ background: "image", backgroundImage: url })).catch(() => setError("That picture could not be read. Try a JPEG or PNG."));
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
),
|
|
7534
|
+
error !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__error", children: error }) : null
|
|
7535
|
+
] }) : null
|
|
5989
7536
|
] });
|
|
5990
7537
|
}
|
|
7538
|
+
function toVideoEffect(effects) {
|
|
7539
|
+
if (effects.background === "blur") return { kind: "blur" };
|
|
7540
|
+
if (effects.background === "image" && effects.backgroundImage !== null) {
|
|
7541
|
+
return { kind: "image", imageUrl: effects.backgroundImage };
|
|
7542
|
+
}
|
|
7543
|
+
return { kind: "none" };
|
|
7544
|
+
}
|
|
7545
|
+
async function shrinkImage(file) {
|
|
7546
|
+
const url = URL.createObjectURL(file);
|
|
7547
|
+
try {
|
|
7548
|
+
const image = new Image();
|
|
7549
|
+
image.src = url;
|
|
7550
|
+
await image.decode();
|
|
7551
|
+
const scale = Math.min(1, 1280 / image.naturalWidth);
|
|
7552
|
+
const canvas = document.createElement("canvas");
|
|
7553
|
+
canvas.width = Math.round(image.naturalWidth * scale);
|
|
7554
|
+
canvas.height = Math.round(image.naturalHeight * scale);
|
|
7555
|
+
const context = canvas.getContext("2d");
|
|
7556
|
+
if (context === null) throw new Error("no 2d context");
|
|
7557
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
7558
|
+
return canvas.toDataURL("image/jpeg", 0.85);
|
|
7559
|
+
} finally {
|
|
7560
|
+
URL.revokeObjectURL(url);
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7563
|
+
async function playTestSound(sinkId) {
|
|
7564
|
+
const Context = globalThis.AudioContext;
|
|
7565
|
+
if (Context === void 0) return;
|
|
7566
|
+
const context = new Context();
|
|
7567
|
+
const destination = context.createMediaStreamDestination();
|
|
7568
|
+
const gain = context.createGain();
|
|
7569
|
+
gain.gain.value = 0.2;
|
|
7570
|
+
gain.connect(destination);
|
|
7571
|
+
for (const [frequency, start] of [
|
|
7572
|
+
[660, 0],
|
|
7573
|
+
[880, 0.25]
|
|
7574
|
+
]) {
|
|
7575
|
+
const oscillator = context.createOscillator();
|
|
7576
|
+
oscillator.frequency.value = frequency;
|
|
7577
|
+
oscillator.connect(gain);
|
|
7578
|
+
oscillator.start(context.currentTime + start);
|
|
7579
|
+
oscillator.stop(context.currentTime + start + 0.22);
|
|
7580
|
+
}
|
|
7581
|
+
const audio = new Audio();
|
|
7582
|
+
audio.srcObject = destination.stream;
|
|
7583
|
+
const sinkable = audio;
|
|
7584
|
+
if (sinkId !== null && sinkId !== "default" && sinkable.setSinkId !== void 0) {
|
|
7585
|
+
await sinkable.setSinkId(sinkId).catch(() => void 0);
|
|
7586
|
+
}
|
|
7587
|
+
await audio.play().catch(() => void 0);
|
|
7588
|
+
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
7589
|
+
audio.pause();
|
|
7590
|
+
await context.close();
|
|
7591
|
+
}
|
|
7592
|
+
function useMicrophoneStream(deviceId2) {
|
|
7593
|
+
const [stream, setStream] = (0, import_react4.useState)(null);
|
|
7594
|
+
(0, import_react4.useEffect)(() => {
|
|
7595
|
+
if (deviceId2 === "off") return void 0;
|
|
7596
|
+
let live = true;
|
|
7597
|
+
let opened = null;
|
|
7598
|
+
const media = globalThis.navigator?.mediaDevices;
|
|
7599
|
+
if (media === void 0) return void 0;
|
|
7600
|
+
void media.getUserMedia({ audio: deviceId2 === null ? true : { deviceId: { exact: deviceId2 } } }).then((next) => {
|
|
7601
|
+
if (!live) {
|
|
7602
|
+
next.getTracks().forEach((track) => track.stop());
|
|
7603
|
+
return;
|
|
7604
|
+
}
|
|
7605
|
+
opened = next;
|
|
7606
|
+
setStream(next);
|
|
7607
|
+
}).catch(() => setStream(null));
|
|
7608
|
+
return () => {
|
|
7609
|
+
live = false;
|
|
7610
|
+
opened?.getTracks().forEach((track) => track.stop());
|
|
7611
|
+
setStream(null);
|
|
7612
|
+
};
|
|
7613
|
+
}, [deviceId2]);
|
|
7614
|
+
return stream;
|
|
7615
|
+
}
|
|
5991
7616
|
function LobbyPanel() {
|
|
5992
7617
|
const lobby = useLobby();
|
|
5993
7618
|
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
@@ -6213,16 +7838,40 @@ function PreJoinBody({
|
|
|
6213
7838
|
const room = useRoom();
|
|
6214
7839
|
const devices = useDeviceSnapshot();
|
|
6215
7840
|
const recording = useRecording();
|
|
7841
|
+
const effects = useMediaEffects();
|
|
7842
|
+
const { notice } = useModerationNotice();
|
|
6216
7843
|
const [mode, setMode] = (0, import_react4.useState)("video");
|
|
6217
7844
|
const [microphone, setMicrophone] = (0, import_react4.useState)(true);
|
|
6218
7845
|
const [joining, setJoining] = (0, import_react4.useState)(false);
|
|
7846
|
+
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
7847
|
+
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7848
|
+
const preview = useCameraPreview(mode === "video" ? camera : void 0, effects.effects);
|
|
7849
|
+
const micStream = useMicrophoneStream(microphone ? mic : "off");
|
|
7850
|
+
const level = useAudioLevel(micStream);
|
|
7851
|
+
const [speakerTesting, setSpeakerTesting] = (0, import_react4.useState)(false);
|
|
6219
7852
|
(0, import_react4.useEffect)(() => {
|
|
6220
7853
|
void host.devices.refresh();
|
|
6221
7854
|
}, [host]);
|
|
6222
7855
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin", children: [
|
|
6223
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
7856
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__preview", children: [
|
|
7857
|
+
mode === "video" && preview.preview !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
7858
|
+
"video",
|
|
7859
|
+
{
|
|
7860
|
+
className: "mx-meet__video mx-meet__video--mirror",
|
|
7861
|
+
autoPlay: true,
|
|
7862
|
+
playsInline: true,
|
|
7863
|
+
muted: true,
|
|
7864
|
+
ref: (element) => {
|
|
7865
|
+
if (element !== null) preview.preview?.attach(element);
|
|
7866
|
+
}
|
|
7867
|
+
}
|
|
7868
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 96 }) }),
|
|
7869
|
+
mode === "video" && preview.error !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__preview-note", children: preview.error }) : null,
|
|
7870
|
+
microphone ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mx-meet__meter mx-meet__meter--preview", role: "meter", "aria-label": "Microphone level", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(level * 100), children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
|
|
7871
|
+
] }),
|
|
6224
7872
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
6225
7873
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
7874
|
+
notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
6226
7875
|
devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
6227
7876
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
6228
7877
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -6239,7 +7888,30 @@ function PreJoinBody({
|
|
|
6239
7888
|
] }) : null,
|
|
6240
7889
|
devices?.fallbackNotice != null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__warning", children: devices.fallbackNotice }) : null,
|
|
6241
7890
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audioinput", label: "Microphone" }),
|
|
7891
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__field-row", children: [
|
|
7892
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "audiooutput", label: "Speaker" }),
|
|
7893
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
7894
|
+
"button",
|
|
7895
|
+
{
|
|
7896
|
+
type: "button",
|
|
7897
|
+
className: "mx-meet__chip",
|
|
7898
|
+
disabled: speakerTesting,
|
|
7899
|
+
onClick: () => {
|
|
7900
|
+
setSpeakerTesting(true);
|
|
7901
|
+
void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve("audiooutput")).finally(
|
|
7902
|
+
() => setSpeakerTesting(false)
|
|
7903
|
+
);
|
|
7904
|
+
},
|
|
7905
|
+
children: [
|
|
7906
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SpeakerIcon, { width: 14, height: 14 }),
|
|
7907
|
+
" ",
|
|
7908
|
+
speakerTesting ? "Playing\u2026" : "Test"
|
|
7909
|
+
]
|
|
7910
|
+
}
|
|
7911
|
+
)
|
|
7912
|
+
] }),
|
|
6242
7913
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DevicePicker, { kind: "videoinput", label: "Camera" }),
|
|
7914
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EffectsSettings, { preview: preview.preview }),
|
|
6243
7915
|
recording.consentNotice !== null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mx-meet__consent", children: recording.consentNotice }) : null,
|
|
6244
7916
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mx-meet__prejoin-actions", children: [
|
|
6245
7917
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "mx-meet__toggle", children: [
|
|
@@ -6271,6 +7943,7 @@ function PreJoinBody({
|
|
|
6271
7943
|
className: "mx-meet__primary",
|
|
6272
7944
|
onClick: () => {
|
|
6273
7945
|
setJoining(true);
|
|
7946
|
+
preview.release();
|
|
6274
7947
|
void room.join({
|
|
6275
7948
|
roomName,
|
|
6276
7949
|
mode,
|
|
@@ -6290,9 +7963,49 @@ function PreJoinBody({
|
|
|
6290
7963
|
] })
|
|
6291
7964
|
] });
|
|
6292
7965
|
}
|
|
7966
|
+
function useCameraPreview(deviceId2, effects) {
|
|
7967
|
+
const [preview, setPreview] = (0, import_react4.useState)(null);
|
|
7968
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
7969
|
+
const current = (0, import_react4.useRef)(null);
|
|
7970
|
+
const initialEffect = (0, import_react4.useRef)(toVideoEffect(effects));
|
|
7971
|
+
initialEffect.current = toVideoEffect(effects);
|
|
7972
|
+
(0, import_react4.useEffect)(() => {
|
|
7973
|
+
if (deviceId2 === void 0) return void 0;
|
|
7974
|
+
let live = true;
|
|
7975
|
+
setError(null);
|
|
7976
|
+
void createCameraPreview(deviceId2, initialEffect.current).then((created) => {
|
|
7977
|
+
if (!live) {
|
|
7978
|
+
created.stop();
|
|
7979
|
+
return;
|
|
7980
|
+
}
|
|
7981
|
+
current.current = created;
|
|
7982
|
+
setPreview(created);
|
|
7983
|
+
}).catch((thrown) => {
|
|
7984
|
+
if (!live) return;
|
|
7985
|
+
const failure = thrown;
|
|
7986
|
+
setError([failure.message, failure.remedy].filter(Boolean).join(". ") || "The camera did not start.");
|
|
7987
|
+
});
|
|
7988
|
+
return () => {
|
|
7989
|
+
live = false;
|
|
7990
|
+
current.current?.stop();
|
|
7991
|
+
current.current = null;
|
|
7992
|
+
setPreview(null);
|
|
7993
|
+
};
|
|
7994
|
+
}, [deviceId2]);
|
|
7995
|
+
return {
|
|
7996
|
+
preview,
|
|
7997
|
+
error,
|
|
7998
|
+
release: () => {
|
|
7999
|
+
current.current?.stop();
|
|
8000
|
+
current.current = null;
|
|
8001
|
+
setPreview(null);
|
|
8002
|
+
}
|
|
8003
|
+
};
|
|
8004
|
+
}
|
|
6293
8005
|
function DevicePicker({
|
|
6294
8006
|
kind,
|
|
6295
|
-
label
|
|
8007
|
+
label,
|
|
8008
|
+
onPick
|
|
6296
8009
|
}) {
|
|
6297
8010
|
const host = useRequiredMeetHost();
|
|
6298
8011
|
const devices = useDeviceSnapshot();
|
|
@@ -6305,7 +8018,10 @@ function DevicePicker({
|
|
|
6305
8018
|
{
|
|
6306
8019
|
className: "mx-meet__select",
|
|
6307
8020
|
value: devices?.selected[kind] ?? host.devices.resolve(kind) ?? "",
|
|
6308
|
-
onChange: (event) =>
|
|
8021
|
+
onChange: (event) => {
|
|
8022
|
+
if (onPick !== void 0) onPick(kind, event.target.value);
|
|
8023
|
+
else host.devices.select(kind, event.target.value);
|
|
8024
|
+
},
|
|
6309
8025
|
children: list.map((device) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: device.deviceId, children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId))
|
|
6310
8026
|
}
|
|
6311
8027
|
)
|