@ai-matrx/meet 0.7.3 → 0.7.6
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 +74 -0
- package/dist/index.cjs +544 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +219 -24
- package/dist/index.d.ts +219 -24
- package/dist/index.js +544 -35
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1886 -162
- 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 +1887 -163
- package/dist/react.js.map +1 -1
- package/dist/styles.css +530 -0
- package/package.json +4 -3
package/dist/react.js
CHANGED
|
@@ -211,7 +211,16 @@ var MEET_ROUTES = {
|
|
|
211
211
|
*/
|
|
212
212
|
meetingAnnounce: "/api/v1/meet/meetings/{meeting_id}/announce",
|
|
213
213
|
/** Put a one-off meeting on the caller's own Google Calendar (gated by the server). */
|
|
214
|
-
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar"
|
|
214
|
+
meetingGoogleCalendar: "/api/v1/meet/meetings/{meeting_id}/google-calendar",
|
|
215
|
+
/**
|
|
216
|
+
* Meet wave 2: EVERY in-meeting host power (mute, stop video, mute all, ask to
|
|
217
|
+
* unmute, spotlight, lower hands, co-host, hand over, rename, remove, report).
|
|
218
|
+
* Decided by the server from the meeting itself; a guest's pass rides
|
|
219
|
+
* `x-meet-room-token`.
|
|
220
|
+
*/
|
|
221
|
+
moderate: "/api/v1/meet/moderate",
|
|
222
|
+
/** Meet wave 2: the SAVED chat — `POST` sends to everyone, `GET ?meeting_id=` reads history. */
|
|
223
|
+
chat: "/api/v1/meet/chat"
|
|
215
224
|
};
|
|
216
225
|
function meetingPath(template, meetingId) {
|
|
217
226
|
return template.replace("{meeting_id}", encodeURIComponent(meetingId));
|
|
@@ -1516,6 +1525,11 @@ function captionSegment(args) {
|
|
|
1516
1525
|
}
|
|
1517
1526
|
|
|
1518
1527
|
// src/core/store.ts
|
|
1528
|
+
var DEFAULT_EFFECTS = {
|
|
1529
|
+
noiseSuppression: true,
|
|
1530
|
+
background: "none",
|
|
1531
|
+
backgroundImage: null
|
|
1532
|
+
};
|
|
1519
1533
|
var NO_GRANTS = {
|
|
1520
1534
|
role: "participant",
|
|
1521
1535
|
canRecord: false,
|
|
@@ -1538,7 +1552,13 @@ var EMPTY = {
|
|
|
1538
1552
|
recordingSource: "local",
|
|
1539
1553
|
recordingId: null,
|
|
1540
1554
|
chat: [],
|
|
1555
|
+
chatLoaded: false,
|
|
1556
|
+
chatHistoryError: null,
|
|
1541
1557
|
reactions: [],
|
|
1558
|
+
spotlight: null,
|
|
1559
|
+
moderationNotice: null,
|
|
1560
|
+
actionNotice: null,
|
|
1561
|
+
effects: DEFAULT_EFFECTS,
|
|
1542
1562
|
transcript: [],
|
|
1543
1563
|
notes: [],
|
|
1544
1564
|
names: {},
|
|
@@ -1577,6 +1597,7 @@ function createMeetStore(initial = {}) {
|
|
|
1577
1597
|
setParticipants(participants) {
|
|
1578
1598
|
const names = learnNames(state.names, participants);
|
|
1579
1599
|
const hands = new Map(state.participants.map((p) => [p.identity, p.handRaised]));
|
|
1600
|
+
const handTimes = new Map(state.participants.map((p) => [p.identity, p.handRaisedAt ?? null]));
|
|
1580
1601
|
const quality = new Map(state.participants.map((p) => [p.identity, p.quality]));
|
|
1581
1602
|
commit({
|
|
1582
1603
|
...state,
|
|
@@ -1590,6 +1611,7 @@ function createMeetStore(initial = {}) {
|
|
|
1590
1611
|
// name book. `roster-names.test.ts` fails if a token ever gets past it.
|
|
1591
1612
|
displayName: displayNameFor(names, participant.identity, participant.displayName),
|
|
1592
1613
|
handRaised: hands.get(participant.identity) ?? participant.handRaised,
|
|
1614
|
+
handRaisedAt: handTimes.get(participant.identity) ?? participant.handRaisedAt ?? null,
|
|
1593
1615
|
quality: participant.quality === "unknown" ? quality.get(participant.identity) ?? "unknown" : participant.quality
|
|
1594
1616
|
}))
|
|
1595
1617
|
});
|
|
@@ -1612,11 +1634,17 @@ function createMeetStore(initial = {}) {
|
|
|
1612
1634
|
)
|
|
1613
1635
|
});
|
|
1614
1636
|
},
|
|
1615
|
-
setHand(identity, raised) {
|
|
1637
|
+
setHand(identity, raised, at) {
|
|
1616
1638
|
commit({
|
|
1617
1639
|
...state,
|
|
1618
1640
|
participants: state.participants.map(
|
|
1619
|
-
(participant) => participant.identity === identity ? {
|
|
1641
|
+
(participant) => participant.identity === identity ? {
|
|
1642
|
+
...participant,
|
|
1643
|
+
handRaised: raised,
|
|
1644
|
+
// A hand that is ALREADY up keeps its place in the queue when
|
|
1645
|
+
// it is re-announced (to a late joiner): the first time wins.
|
|
1646
|
+
handRaisedAt: raised ? participant.handRaised && participant.handRaisedAt != null ? participant.handRaisedAt : at ?? Date.now() : null
|
|
1647
|
+
} : participant
|
|
1620
1648
|
)
|
|
1621
1649
|
});
|
|
1622
1650
|
},
|
|
@@ -1626,6 +1654,31 @@ function createMeetStore(initial = {}) {
|
|
|
1626
1654
|
const names = learnName(state.names, message.identity, message.displayName);
|
|
1627
1655
|
commit({ ...state, chat: chat.slice(-CHAT_LIMIT), names });
|
|
1628
1656
|
},
|
|
1657
|
+
loadChat(messages) {
|
|
1658
|
+
const known = new Set(state.chat.map((message) => message.id));
|
|
1659
|
+
const merged = [...messages.filter((message) => !known.has(message.id)), ...state.chat].sort(
|
|
1660
|
+
(a, b) => a.at < b.at ? -1 : a.at > b.at ? 1 : 0
|
|
1661
|
+
);
|
|
1662
|
+
const names = learnNames(
|
|
1663
|
+
state.names,
|
|
1664
|
+
messages.map((message) => ({ identity: message.identity, displayName: message.displayName }))
|
|
1665
|
+
);
|
|
1666
|
+
commit({ ...state, chat: merged.slice(-CHAT_LIMIT), chatLoaded: true, chatHistoryError: null, names });
|
|
1667
|
+
},
|
|
1668
|
+
settleChat(localId, next) {
|
|
1669
|
+
const index = state.chat.findIndex((message) => message.id === localId);
|
|
1670
|
+
if (index === -1) return;
|
|
1671
|
+
const current = state.chat[index];
|
|
1672
|
+
let replacement;
|
|
1673
|
+
if ("failed" in next && typeof next.failed === "string" && !("id" in next)) {
|
|
1674
|
+
replacement = { ...current, failed: next.failed };
|
|
1675
|
+
} else {
|
|
1676
|
+
replacement = next;
|
|
1677
|
+
}
|
|
1678
|
+
const duplicate = replacement.id !== localId && state.chat.some((message) => message.id === replacement.id);
|
|
1679
|
+
const chat = duplicate ? state.chat.filter((message) => message.id !== localId) : state.chat.map((message, position) => position === index ? replacement : message);
|
|
1680
|
+
commit({ ...state, chat });
|
|
1681
|
+
},
|
|
1629
1682
|
addReaction(reaction) {
|
|
1630
1683
|
commit({ ...state, reactions: [...state.reactions, reaction] });
|
|
1631
1684
|
},
|
|
@@ -1716,7 +1769,7 @@ function createMeetStore(initial = {}) {
|
|
|
1716
1769
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
1717
1770
|
},
|
|
1718
1771
|
reset() {
|
|
1719
|
-
commit({ ...EMPTY });
|
|
1772
|
+
commit({ ...EMPTY, effects: state.effects });
|
|
1720
1773
|
}
|
|
1721
1774
|
};
|
|
1722
1775
|
}
|
|
@@ -2971,7 +3024,19 @@ var ROOM_EVENTS = {
|
|
|
2971
3024
|
* production 2026-09-09 the guest lane showed no indicator at all while the
|
|
2972
3025
|
* host recorded (MRI-C8).
|
|
2973
3026
|
*/
|
|
2974
|
-
recording: "matrx.meet.recording"
|
|
3027
|
+
recording: "matrx.meet.recording",
|
|
3028
|
+
/**
|
|
3029
|
+
* A chat message to everyone was SAVED (Meet wave 2). Payload
|
|
3030
|
+
* `{ message: { id, meeting_id, sender_identity, sender_name, body, created_at } }`.
|
|
3031
|
+
*/
|
|
3032
|
+
chat: "matrx.meet.chat",
|
|
3033
|
+
/**
|
|
3034
|
+
* A host or co-host acted (Meet wave 2). Payload `{ meeting_id, kind, … }`:
|
|
3035
|
+
* `muted {source, by}` · `ask-unmute {by}` · `removed {by}` — addressed to the
|
|
3036
|
+
* person concerned; `lower-hands {identities: [...] | "*"}` · `role {identity,
|
|
3037
|
+
* role}` — to everyone.
|
|
3038
|
+
*/
|
|
3039
|
+
moderation: "matrx.meet.moderation"
|
|
2975
3040
|
};
|
|
2976
3041
|
function isRoomEventTopic(topic) {
|
|
2977
3042
|
return topic.startsWith(ROOM_EVENT_PREFIX);
|
|
@@ -3007,6 +3072,10 @@ function applyRoomEvent(args) {
|
|
|
3007
3072
|
return applyEnded(store, payload);
|
|
3008
3073
|
case ROOM_EVENTS.recording:
|
|
3009
3074
|
return applyRecording(store, payload);
|
|
3075
|
+
case ROOM_EVENTS.chat:
|
|
3076
|
+
return applyChat(store, payload);
|
|
3077
|
+
case ROOM_EVENTS.moderation:
|
|
3078
|
+
return applyModeration(store, payload, now);
|
|
3010
3079
|
default:
|
|
3011
3080
|
return ignored(`${topic} is not known to this version of @ai-matrx/meet`);
|
|
3012
3081
|
}
|
|
@@ -3098,6 +3167,101 @@ function applyEnded(store, payload) {
|
|
|
3098
3167
|
store.patch({ meeting: { ...meeting, endedAt } });
|
|
3099
3168
|
return HANDLED;
|
|
3100
3169
|
}
|
|
3170
|
+
function chatFromRow(row) {
|
|
3171
|
+
const id = text2(row["id"]);
|
|
3172
|
+
const identity = text2(row["sender_identity"]);
|
|
3173
|
+
const body = text2(row["body"]);
|
|
3174
|
+
if (id === null || identity === null || body === null) return null;
|
|
3175
|
+
return {
|
|
3176
|
+
id,
|
|
3177
|
+
identity: asParticipantIdentity(identity),
|
|
3178
|
+
displayName: text2(row["sender_name"]) ?? "Participant",
|
|
3179
|
+
text: body,
|
|
3180
|
+
at: text2(row["created_at"]) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3181
|
+
// `false` only from a server whose chat table has not landed: the room heard
|
|
3182
|
+
// it, nobody will see it later — and the line says so.
|
|
3183
|
+
saved: row["saved"] !== false
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
function applyChat(store, payload) {
|
|
3187
|
+
const row = object(payload["message"]);
|
|
3188
|
+
if (row === null) return ignored("a chat event with no `message` row");
|
|
3189
|
+
const meeting = store.snapshot().meeting;
|
|
3190
|
+
if (meeting !== null && text2(row["meeting_id"]) !== null && row["meeting_id"] !== meeting.id) {
|
|
3191
|
+
return ignored("a chat message for a different meeting");
|
|
3192
|
+
}
|
|
3193
|
+
const message = chatFromRow(row);
|
|
3194
|
+
if (message === null) return ignored("a chat row with no id, sender or body");
|
|
3195
|
+
store.addChat(message);
|
|
3196
|
+
return HANDLED;
|
|
3197
|
+
}
|
|
3198
|
+
function moderationText(kind, payload) {
|
|
3199
|
+
const by = text2(payload["by"]) ?? "The host";
|
|
3200
|
+
switch (kind) {
|
|
3201
|
+
case "muted": {
|
|
3202
|
+
const source = text2(payload["source"]) ?? "microphone";
|
|
3203
|
+
return source === "microphone" ? `${by} muted you.` : source === "camera" ? `${by} turned off your camera.` : `${by} stopped your screen share.`;
|
|
3204
|
+
}
|
|
3205
|
+
case "ask-unmute":
|
|
3206
|
+
return `${by} is asking you to unmute.`;
|
|
3207
|
+
case "removed":
|
|
3208
|
+
return `${by} removed you from this meeting.`;
|
|
3209
|
+
default:
|
|
3210
|
+
return null;
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
function applyModeration(store, payload, now) {
|
|
3214
|
+
const snapshot = store.snapshot();
|
|
3215
|
+
const meetingId = text2(payload["meeting_id"]);
|
|
3216
|
+
if (snapshot.meeting !== null && meetingId !== null && meetingId !== snapshot.meeting.id) {
|
|
3217
|
+
return ignored("a moderation event for a different meeting");
|
|
3218
|
+
}
|
|
3219
|
+
const kind = text2(payload["kind"]);
|
|
3220
|
+
switch (kind) {
|
|
3221
|
+
case "lower-hands": {
|
|
3222
|
+
const raw = payload["identities"];
|
|
3223
|
+
const everyone = raw === "*";
|
|
3224
|
+
const named = new Set(
|
|
3225
|
+
Array.isArray(raw) ? raw.filter((value) => typeof value === "string") : []
|
|
3226
|
+
);
|
|
3227
|
+
for (const participant of snapshot.participants) {
|
|
3228
|
+
if (participant.handRaised && (everyone || named.has(participant.identity))) {
|
|
3229
|
+
store.setHand(participant.identity, false);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return HANDLED;
|
|
3233
|
+
}
|
|
3234
|
+
case "role": {
|
|
3235
|
+
const identity = text2(payload["identity"]);
|
|
3236
|
+
const role = text2(payload["role"]);
|
|
3237
|
+
if (identity === null || role === null || !ROLES.includes(role)) {
|
|
3238
|
+
return ignored("a role event with no identity or an unknown role");
|
|
3239
|
+
}
|
|
3240
|
+
if (identity === snapshot.localIdentity) {
|
|
3241
|
+
store.patch({
|
|
3242
|
+
grants: { ...snapshot.grants, role },
|
|
3243
|
+
moderationNotice: {
|
|
3244
|
+
kind: "role",
|
|
3245
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You are now a co-host." : "You are no longer a co-host.",
|
|
3246
|
+
at: now()
|
|
3247
|
+
}
|
|
3248
|
+
});
|
|
3249
|
+
}
|
|
3250
|
+
return HANDLED;
|
|
3251
|
+
}
|
|
3252
|
+
case "muted":
|
|
3253
|
+
case "ask-unmute":
|
|
3254
|
+
case "removed": {
|
|
3255
|
+
const sentence = moderationText(kind, payload);
|
|
3256
|
+
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
3257
|
+
store.patch({ moderationNotice: { kind, text: sentence, at: now() } });
|
|
3258
|
+
return HANDLED;
|
|
3259
|
+
}
|
|
3260
|
+
default:
|
|
3261
|
+
return ignored(`a moderation kind this version does not know: ${String(kind)}`);
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
var ROLES = ["host", "cohost", "participant", "guest"];
|
|
3101
3265
|
|
|
3102
3266
|
// src/core/note-taker.ts
|
|
3103
3267
|
var NOTE_TAKER_IDENTITY_PREFIX = "agent:note_taker:";
|
|
@@ -3162,8 +3326,7 @@ function noteTakerLabel(status) {
|
|
|
3162
3326
|
var DATA_TOPICS = {
|
|
3163
3327
|
hand: "mx.hand",
|
|
3164
3328
|
reaction: "mx.reaction",
|
|
3165
|
-
chat: "mx.chat"
|
|
3166
|
-
hostAction: "mx.host"
|
|
3329
|
+
chat: "mx.chat"
|
|
3167
3330
|
};
|
|
3168
3331
|
var TRANSCRIPTION_TOPIC = "lk.transcription";
|
|
3169
3332
|
var TRANSCRIPTION_ATTRIBUTES = {
|
|
@@ -3299,7 +3462,37 @@ function createRoomEngine(options) {
|
|
|
3299
3462
|
const handlers = {
|
|
3300
3463
|
participants(list) {
|
|
3301
3464
|
const joinedAt = new Date(now()).toISOString();
|
|
3465
|
+
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
3302
3466
|
store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
|
|
3467
|
+
const snapshot = store.snapshot();
|
|
3468
|
+
const newcomer = snapshot.participants.some(
|
|
3469
|
+
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
3470
|
+
);
|
|
3471
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
3472
|
+
if (newcomer && before.size > 0 && me?.handRaised === true) {
|
|
3473
|
+
void publish(DATA_TOPICS.hand, { raised: true, raisedAt: me.handRaisedAt ?? now() }).catch(
|
|
3474
|
+
() => void 0
|
|
3475
|
+
);
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
roomMetadata(metadata) {
|
|
3479
|
+
let spotlight = null;
|
|
3480
|
+
if (metadata.length > 0) {
|
|
3481
|
+
try {
|
|
3482
|
+
const parsed = JSON.parse(metadata);
|
|
3483
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
3484
|
+
const value = parsed.spotlight;
|
|
3485
|
+
if (typeof value === "string" && value.length > 0) spotlight = asParticipantIdentity(value);
|
|
3486
|
+
}
|
|
3487
|
+
} catch {
|
|
3488
|
+
report(
|
|
3489
|
+
"warn",
|
|
3490
|
+
"meet: the room's metadata is not JSON, so no spotlight was read from it",
|
|
3491
|
+
"Only aidream writes room metadata; check services/meet/moderation.py."
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
if (store.snapshot().spotlight !== spotlight) store.patch({ spotlight });
|
|
3303
3496
|
},
|
|
3304
3497
|
activeSpeakers(identities) {
|
|
3305
3498
|
store.setSpeaking(identities.map(asParticipantIdentity));
|
|
@@ -3403,7 +3596,11 @@ function createRoomEngine(options) {
|
|
|
3403
3596
|
const sender = asParticipantIdentity(from ?? "");
|
|
3404
3597
|
switch (topic) {
|
|
3405
3598
|
case DATA_TOPICS.hand:
|
|
3406
|
-
store.setHand(
|
|
3599
|
+
store.setHand(
|
|
3600
|
+
sender,
|
|
3601
|
+
envelope.raised === true,
|
|
3602
|
+
typeof envelope.raisedAt === "number" ? envelope.raisedAt : now()
|
|
3603
|
+
);
|
|
3407
3604
|
return;
|
|
3408
3605
|
case DATA_TOPICS.reaction:
|
|
3409
3606
|
if (typeof envelope.emoji === "string") {
|
|
@@ -3417,12 +3614,14 @@ function createRoomEngine(options) {
|
|
|
3417
3614
|
return;
|
|
3418
3615
|
case DATA_TOPICS.chat:
|
|
3419
3616
|
if (typeof envelope.text === "string") {
|
|
3617
|
+
const local = store.snapshot().localIdentity;
|
|
3420
3618
|
store.addChat({
|
|
3421
3619
|
id: envelope.id ?? `${sender}-${now()}`,
|
|
3422
3620
|
identity: sender,
|
|
3423
3621
|
displayName: envelope.name ?? sender,
|
|
3424
3622
|
text: envelope.text,
|
|
3425
|
-
at: envelope.at ?? new Date(now()).toISOString()
|
|
3623
|
+
at: envelope.at ?? new Date(now()).toISOString(),
|
|
3624
|
+
...typeof envelope.to === "string" && local !== null ? { to: local } : {}
|
|
3426
3625
|
});
|
|
3427
3626
|
}
|
|
3428
3627
|
return;
|
|
@@ -3468,6 +3667,24 @@ function createRoomEngine(options) {
|
|
|
3468
3667
|
store.patch({ phase: "connected", failure: null });
|
|
3469
3668
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
3470
3669
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
3670
|
+
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
3671
|
+
store.patch({ effects });
|
|
3672
|
+
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
3673
|
+
try {
|
|
3674
|
+
await applyEffects(created, effects);
|
|
3675
|
+
} catch {
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
for (const kind of ["audioinput", "videoinput", "audiooutput"]) {
|
|
3679
|
+
const chosen = options.resolveDevice?.(kind) ?? null;
|
|
3680
|
+
if (chosen === null || chosen === "default") continue;
|
|
3681
|
+
try {
|
|
3682
|
+
await created.switchDevice(kind, chosen);
|
|
3683
|
+
} catch (error) {
|
|
3684
|
+
const normalized = normalizeDeviceError(error, `meet.switchDevice(${kind})`);
|
|
3685
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3471
3688
|
if (request.mode === "video") {
|
|
3472
3689
|
await enableTrack(created, "camera", true);
|
|
3473
3690
|
}
|
|
@@ -3490,9 +3707,30 @@ function createRoomEngine(options) {
|
|
|
3490
3707
|
return state;
|
|
3491
3708
|
}
|
|
3492
3709
|
}
|
|
3493
|
-
async function publish(topic, body) {
|
|
3710
|
+
async function publish(topic, body, to) {
|
|
3494
3711
|
if (driver === null) return;
|
|
3495
|
-
await driver.publishData(topic, JSON.stringify(body));
|
|
3712
|
+
await driver.publishData(topic, JSON.stringify(body), to);
|
|
3713
|
+
}
|
|
3714
|
+
async function applyEffects(target, effects) {
|
|
3715
|
+
try {
|
|
3716
|
+
await target.setAudioProcessing?.({
|
|
3717
|
+
noiseSuppression: effects.noiseSuppression,
|
|
3718
|
+
echoCancellation: true,
|
|
3719
|
+
autoGainControl: true
|
|
3720
|
+
});
|
|
3721
|
+
} catch (error) {
|
|
3722
|
+
report("warn", `meet: noise suppression could not be applied: ${String(error)}`, "The microphone keeps working without it.");
|
|
3723
|
+
}
|
|
3724
|
+
if (target.setVideoEffect === void 0) return;
|
|
3725
|
+
const effect = effects.background === "blur" ? { kind: "blur" } : effects.background === "image" && effects.backgroundImage !== null ? { kind: "image", imageUrl: effects.backgroundImage } : { kind: "none" };
|
|
3726
|
+
if (effect.kind !== "none" && target.supportsVideoEffects?.() === false) return;
|
|
3727
|
+
try {
|
|
3728
|
+
await target.setVideoEffect(effect);
|
|
3729
|
+
} catch (error) {
|
|
3730
|
+
const normalized = normalizeMeetError(error, "meet.setVideoEffect");
|
|
3731
|
+
report("warn", normalized.message, normalized.remedy);
|
|
3732
|
+
throw normalized;
|
|
3733
|
+
}
|
|
3496
3734
|
}
|
|
3497
3735
|
return {
|
|
3498
3736
|
join(request) {
|
|
@@ -3548,7 +3786,7 @@ function createRoomEngine(options) {
|
|
|
3548
3786
|
async raiseHand(raised) {
|
|
3549
3787
|
const local = store.snapshot().localIdentity;
|
|
3550
3788
|
if (local !== null) store.setHand(local, raised);
|
|
3551
|
-
await publish(DATA_TOPICS.hand, { raised });
|
|
3789
|
+
await publish(DATA_TOPICS.hand, { raised, raisedAt: now() });
|
|
3552
3790
|
},
|
|
3553
3791
|
async react(emoji) {
|
|
3554
3792
|
const local = store.snapshot().localIdentity;
|
|
@@ -3558,32 +3796,41 @@ function createRoomEngine(options) {
|
|
|
3558
3796
|
}
|
|
3559
3797
|
await publish(DATA_TOPICS.reaction, { id, emoji });
|
|
3560
3798
|
},
|
|
3561
|
-
async
|
|
3799
|
+
async sendPrivateChat(to, text3) {
|
|
3562
3800
|
const trimmed = text3.trim();
|
|
3563
3801
|
if (trimmed.length === 0) return;
|
|
3564
3802
|
const local = store.snapshot().localIdentity;
|
|
3565
3803
|
const id = `${local ?? "local"}-${now()}`;
|
|
3566
3804
|
const at = new Date(now()).toISOString();
|
|
3567
3805
|
if (local !== null) {
|
|
3568
|
-
store.addChat({
|
|
3569
|
-
id,
|
|
3570
|
-
identity: local,
|
|
3571
|
-
displayName: identity.displayName,
|
|
3572
|
-
text: trimmed,
|
|
3573
|
-
at
|
|
3574
|
-
});
|
|
3806
|
+
store.addChat({ id, identity: local, displayName: identity.displayName, text: trimmed, at, to });
|
|
3575
3807
|
}
|
|
3576
|
-
await publish(
|
|
3808
|
+
await publish(
|
|
3809
|
+
DATA_TOPICS.chat,
|
|
3810
|
+
{ id, text: trimmed, at, name: identity.displayName, to },
|
|
3811
|
+
[to]
|
|
3812
|
+
);
|
|
3577
3813
|
},
|
|
3578
|
-
async
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3814
|
+
async setEffects(next) {
|
|
3815
|
+
const previous = store.snapshot().effects;
|
|
3816
|
+
const merged = { ...previous, ...next };
|
|
3817
|
+
store.patch({ effects: merged });
|
|
3818
|
+
options.effectsStorage?.save(merged);
|
|
3819
|
+
if (driver === null) return;
|
|
3820
|
+
try {
|
|
3821
|
+
await applyEffects(driver, merged);
|
|
3822
|
+
} catch {
|
|
3823
|
+
const reverted = { ...merged, background: previous.background, backgroundImage: previous.backgroundImage };
|
|
3824
|
+
store.patch({ effects: reverted });
|
|
3825
|
+
options.effectsStorage?.save(reverted);
|
|
3585
3826
|
}
|
|
3586
|
-
|
|
3827
|
+
},
|
|
3828
|
+
supportsVideoEffects() {
|
|
3829
|
+
return driver?.supportsVideoEffects?.() ?? false;
|
|
3830
|
+
},
|
|
3831
|
+
async switchDevice(kind, deviceId2) {
|
|
3832
|
+
if (driver === null) return;
|
|
3833
|
+
await driver.switchDevice(kind, deviceId2);
|
|
3587
3834
|
},
|
|
3588
3835
|
pin(who) {
|
|
3589
3836
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
@@ -3614,6 +3861,192 @@ function hostUserIds(participants) {
|
|
|
3614
3861
|
return participants.filter((participant) => participant.role === "host" && participant.userId !== null).map((participant) => participant.userId);
|
|
3615
3862
|
}
|
|
3616
3863
|
|
|
3864
|
+
// src/core/effects.ts
|
|
3865
|
+
function coerce(value) {
|
|
3866
|
+
if (typeof value !== "object" || value === null) return DEFAULT_EFFECTS;
|
|
3867
|
+
const row = value;
|
|
3868
|
+
const background = row["background"] === "blur" || row["background"] === "image" ? row["background"] : "none";
|
|
3869
|
+
const backgroundImage = typeof row["backgroundImage"] === "string" ? row["backgroundImage"] : null;
|
|
3870
|
+
return {
|
|
3871
|
+
noiseSuppression: row["noiseSuppression"] !== false,
|
|
3872
|
+
background: background === "image" && backgroundImage === null ? "none" : background,
|
|
3873
|
+
backgroundImage
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
function createWebEffectsStorage(key = "mx.meet.effects") {
|
|
3877
|
+
let memory = DEFAULT_EFFECTS;
|
|
3878
|
+
const storage = globalThis.localStorage;
|
|
3879
|
+
return {
|
|
3880
|
+
load() {
|
|
3881
|
+
if (storage === void 0) return memory;
|
|
3882
|
+
try {
|
|
3883
|
+
const raw = storage.getItem(key);
|
|
3884
|
+
return raw === null ? memory : coerce(JSON.parse(raw));
|
|
3885
|
+
} catch {
|
|
3886
|
+
return memory;
|
|
3887
|
+
}
|
|
3888
|
+
},
|
|
3889
|
+
save(effects) {
|
|
3890
|
+
memory = effects;
|
|
3891
|
+
try {
|
|
3892
|
+
storage?.setItem(key, JSON.stringify(effects));
|
|
3893
|
+
} catch {
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
};
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
// src/core/in-room.ts
|
|
3900
|
+
var MODERATION_ACTIONS = [
|
|
3901
|
+
"mute",
|
|
3902
|
+
"stop_video",
|
|
3903
|
+
"stop_screen",
|
|
3904
|
+
"mute_all",
|
|
3905
|
+
"ask_unmute",
|
|
3906
|
+
"ask_all_unmute",
|
|
3907
|
+
"spotlight",
|
|
3908
|
+
"lower_hand",
|
|
3909
|
+
"lower_all_hands",
|
|
3910
|
+
"make_cohost",
|
|
3911
|
+
"revoke_cohost",
|
|
3912
|
+
"transfer_host",
|
|
3913
|
+
"rename",
|
|
3914
|
+
"remove",
|
|
3915
|
+
"report"
|
|
3916
|
+
];
|
|
3917
|
+
function createInRoomClient(options) {
|
|
3918
|
+
const { api, store } = options;
|
|
3919
|
+
const now = options.now ?? (() => Date.now());
|
|
3920
|
+
function meetingId(operation) {
|
|
3921
|
+
const meeting = store.snapshot().meeting;
|
|
3922
|
+
if (meeting === null) {
|
|
3923
|
+
throw new MeetError(
|
|
3924
|
+
"not-found",
|
|
3925
|
+
`${operation}: this room has no meeting record`,
|
|
3926
|
+
"Moderation and saved chat belong to a meeting. An ad-hoc call has neither."
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3929
|
+
return meeting.id;
|
|
3930
|
+
}
|
|
3931
|
+
function headers() {
|
|
3932
|
+
const token = options.resolveRoomToken();
|
|
3933
|
+
return token === null ? void 0 : { [ROOM_TOKEN_HEADER]: token };
|
|
3934
|
+
}
|
|
3935
|
+
async function refusal2(response, operation, remedy) {
|
|
3936
|
+
let body = null;
|
|
3937
|
+
try {
|
|
3938
|
+
body = await response.json();
|
|
3939
|
+
} catch {
|
|
3940
|
+
body = null;
|
|
3941
|
+
}
|
|
3942
|
+
const said = readServerRefusal(body);
|
|
3943
|
+
return new MeetError(
|
|
3944
|
+
response.status === 403 || response.status === 401 ? "forbidden" : response.status === 404 ? "not-found" : response.status === 409 ? "conflict" : "transport",
|
|
3945
|
+
said?.reason ?? `${operation}: the server answered ${response.status}`,
|
|
3946
|
+
said?.remedy !== void 0 && said.remedy.length > 0 ? said.remedy : remedy
|
|
3947
|
+
);
|
|
3948
|
+
}
|
|
3949
|
+
return {
|
|
3950
|
+
async moderate(request) {
|
|
3951
|
+
const operation = `meet.moderate(${request.action})`;
|
|
3952
|
+
const hdrs = headers();
|
|
3953
|
+
const response = await api.request({
|
|
3954
|
+
path: MEET_ROUTES.moderate,
|
|
3955
|
+
method: "POST",
|
|
3956
|
+
operation,
|
|
3957
|
+
body: {
|
|
3958
|
+
meeting_id: meetingId(operation),
|
|
3959
|
+
action: request.action,
|
|
3960
|
+
...request.identity != null ? { identity: request.identity } : {},
|
|
3961
|
+
...request.name !== void 0 ? { name: request.name } : {},
|
|
3962
|
+
...request.reason !== void 0 ? { reason: request.reason } : {}
|
|
3963
|
+
},
|
|
3964
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
3965
|
+
});
|
|
3966
|
+
if (!response.ok) {
|
|
3967
|
+
throw await refusal2(
|
|
3968
|
+
response,
|
|
3969
|
+
operation,
|
|
3970
|
+
"Nothing was changed. The server decides host powers from the meeting itself."
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
const payload = await response.json();
|
|
3974
|
+
return {
|
|
3975
|
+
action: request.action,
|
|
3976
|
+
affected: Array.isArray(payload["affected"]) ? payload["affected"].filter((value) => typeof value === "string") : [],
|
|
3977
|
+
message: typeof payload["message"] === "string" ? payload["message"] : "Done."
|
|
3978
|
+
};
|
|
3979
|
+
},
|
|
3980
|
+
async sendChat(text3) {
|
|
3981
|
+
const body = text3.trim();
|
|
3982
|
+
if (body.length === 0) return;
|
|
3983
|
+
const operation = "meet.sendChat";
|
|
3984
|
+
const id = meetingId(operation);
|
|
3985
|
+
const snapshot = store.snapshot();
|
|
3986
|
+
const me = snapshot.participants.find((p) => p.identity === snapshot.localIdentity);
|
|
3987
|
+
const localId = `pending-${now()}`;
|
|
3988
|
+
const pending = {
|
|
3989
|
+
id: localId,
|
|
3990
|
+
identity: snapshot.localIdentity ?? "local",
|
|
3991
|
+
displayName: me?.displayName ?? "You",
|
|
3992
|
+
text: body,
|
|
3993
|
+
at: new Date(now()).toISOString(),
|
|
3994
|
+
saved: false
|
|
3995
|
+
};
|
|
3996
|
+
store.addChat(pending);
|
|
3997
|
+
const hdrs = headers();
|
|
3998
|
+
let response;
|
|
3999
|
+
try {
|
|
4000
|
+
response = await api.request({
|
|
4001
|
+
path: MEET_ROUTES.chat,
|
|
4002
|
+
method: "POST",
|
|
4003
|
+
operation,
|
|
4004
|
+
body: { meeting_id: id, body },
|
|
4005
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4006
|
+
});
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
store.settleChat(localId, { failed: "Not sent \u2014 the server could not be reached." });
|
|
4009
|
+
throw error;
|
|
4010
|
+
}
|
|
4011
|
+
if (!response.ok) {
|
|
4012
|
+
const error = await refusal2(response, operation, "Your message was not sent.");
|
|
4013
|
+
store.settleChat(localId, { failed: error.message });
|
|
4014
|
+
throw error;
|
|
4015
|
+
}
|
|
4016
|
+
const saved = chatFromRow(await response.json());
|
|
4017
|
+
if (saved === null) {
|
|
4018
|
+
store.settleChat(localId, { failed: "The server's answer could not be read." });
|
|
4019
|
+
return;
|
|
4020
|
+
}
|
|
4021
|
+
store.settleChat(localId, saved);
|
|
4022
|
+
},
|
|
4023
|
+
async loadChat() {
|
|
4024
|
+
const operation = "meet.loadChat";
|
|
4025
|
+
const id = meetingId(operation);
|
|
4026
|
+
const hdrs = headers();
|
|
4027
|
+
const response = await api.request({
|
|
4028
|
+
path: `${MEET_ROUTES.chat}?meeting_id=${encodeURIComponent(id)}`,
|
|
4029
|
+
method: "GET",
|
|
4030
|
+
operation,
|
|
4031
|
+
...hdrs !== void 0 ? { headers: hdrs } : {}
|
|
4032
|
+
});
|
|
4033
|
+
if (!response.ok) {
|
|
4034
|
+
throw await refusal2(
|
|
4035
|
+
response,
|
|
4036
|
+
operation,
|
|
4037
|
+
"Messages sent before you joined could not be read. New messages still arrive."
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
const payload = await response.json();
|
|
4041
|
+
const rows = Array.isArray(payload.messages) ? payload.messages : [];
|
|
4042
|
+
const messages = rows.map(
|
|
4043
|
+
(row) => typeof row === "object" && row !== null ? chatFromRow(row) : null
|
|
4044
|
+
).filter((message) => message !== null);
|
|
4045
|
+
store.loadChat(messages);
|
|
4046
|
+
}
|
|
4047
|
+
};
|
|
4048
|
+
}
|
|
4049
|
+
|
|
3617
4050
|
// src/core/livekit-driver.ts
|
|
3618
4051
|
function mapQuality(quality) {
|
|
3619
4052
|
switch (quality) {
|
|
@@ -3658,11 +4091,48 @@ function snapshotParticipant(participant, isLocal) {
|
|
|
3658
4091
|
screenSharing: participant.isScreenShareEnabled
|
|
3659
4092
|
};
|
|
3660
4093
|
}
|
|
4094
|
+
var DEFAULT_AUDIO_PROCESSING = {
|
|
4095
|
+
noiseSuppression: true,
|
|
4096
|
+
echoCancellation: true,
|
|
4097
|
+
autoGainControl: true
|
|
4098
|
+
};
|
|
4099
|
+
function videoEffectsSupported() {
|
|
4100
|
+
const scope = globalThis;
|
|
4101
|
+
if (typeof scope.OffscreenCanvas === "undefined" || scope.document === void 0) return false;
|
|
4102
|
+
try {
|
|
4103
|
+
return scope.document.createElement("canvas").getContext("webgl2") !== null;
|
|
4104
|
+
} catch {
|
|
4105
|
+
return false;
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
async function applyVideoEffect(target, effect) {
|
|
4109
|
+
const { Track } = await import("livekit-client");
|
|
4110
|
+
const track = target.localParticipant.getTrackPublication(Track.Source.Camera)?.track;
|
|
4111
|
+
if (track === void 0) return;
|
|
4112
|
+
try {
|
|
4113
|
+
if (effect.kind === "none") {
|
|
4114
|
+
await track.stopProcessor();
|
|
4115
|
+
return;
|
|
4116
|
+
}
|
|
4117
|
+
const processors = await import("@livekit/track-processors");
|
|
4118
|
+
const processor = effect.kind === "blur" ? processors.BackgroundBlur(effect.radius ?? 12) : processors.VirtualBackground(effect.imageUrl);
|
|
4119
|
+
await track.setProcessor(processor);
|
|
4120
|
+
} catch (error) {
|
|
4121
|
+
throw new MeetError(
|
|
4122
|
+
"media-unsupported",
|
|
4123
|
+
"The background effect could not start on this camera",
|
|
4124
|
+
"Background effects need a browser with WebGL2 (current Chrome, Edge, Firefox or Safari). Your video continues without the effect.",
|
|
4125
|
+
error
|
|
4126
|
+
);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
3661
4129
|
var encoder = new TextEncoder();
|
|
3662
4130
|
var decoder = new TextDecoder();
|
|
3663
4131
|
function createLiveKitDriver(handlers, options = {}) {
|
|
3664
4132
|
let room = null;
|
|
3665
4133
|
let projectRoster = null;
|
|
4134
|
+
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
4135
|
+
let videoEffect = { kind: "none" };
|
|
3666
4136
|
async function load() {
|
|
3667
4137
|
if (room !== null) return room;
|
|
3668
4138
|
let module;
|
|
@@ -3691,7 +4161,9 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3691
4161
|
handlers.participants([local, ...remotes]);
|
|
3692
4162
|
};
|
|
3693
4163
|
projectRoster = roster;
|
|
3694
|
-
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.
|
|
4164
|
+
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) => {
|
|
4165
|
+
handlers.roomMetadata?.(metadata ?? "");
|
|
4166
|
+
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
3695
4167
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
3696
4168
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
3697
4169
|
handlers.quality(participant.identity, mapQuality(quality));
|
|
@@ -3768,6 +4240,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3768
4240
|
throw normalizeDeviceError(error, "meet.connect");
|
|
3769
4241
|
}
|
|
3770
4242
|
projectRoster?.();
|
|
4243
|
+
handlers.roomMetadata?.(target.metadata ?? "");
|
|
3771
4244
|
},
|
|
3772
4245
|
async disconnect() {
|
|
3773
4246
|
if (room === null) return;
|
|
@@ -3782,14 +4255,19 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3782
4255
|
},
|
|
3783
4256
|
async setMicrophoneEnabled(enabled) {
|
|
3784
4257
|
try {
|
|
3785
|
-
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4258
|
+
await requireRoom("setMicrophoneEnabled").localParticipant.setMicrophoneEnabled(
|
|
4259
|
+
enabled,
|
|
4260
|
+
audioCapture
|
|
4261
|
+
);
|
|
3786
4262
|
} catch (error) {
|
|
3787
4263
|
throw normalizeDeviceError(error, "meet.setMicrophoneEnabled");
|
|
3788
4264
|
}
|
|
3789
4265
|
},
|
|
3790
4266
|
async setCameraEnabled(enabled) {
|
|
3791
4267
|
try {
|
|
3792
|
-
|
|
4268
|
+
const target = requireRoom("setCameraEnabled");
|
|
4269
|
+
await target.localParticipant.setCameraEnabled(enabled);
|
|
4270
|
+
if (enabled && videoEffect.kind !== "none") await applyVideoEffect(target, videoEffect);
|
|
3793
4271
|
} catch (error) {
|
|
3794
4272
|
throw normalizeDeviceError(error, "meet.setCameraEnabled");
|
|
3795
4273
|
}
|
|
@@ -3801,14 +4279,40 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3801
4279
|
throw normalizeDeviceError(error, "meet.setScreenShareEnabled");
|
|
3802
4280
|
}
|
|
3803
4281
|
},
|
|
3804
|
-
async publishData(topic, payload) {
|
|
4282
|
+
async publishData(topic, payload, to) {
|
|
3805
4283
|
await requireRoom("publishData").localParticipant.publishData(
|
|
3806
4284
|
// `reliable` is not optional for a control plane: a dropped
|
|
3807
4285
|
// "hand lowered" leaves a hand up on every other screen forever.
|
|
3808
4286
|
encoder.encode(payload),
|
|
3809
|
-
{ reliable: true, topic }
|
|
4287
|
+
to === void 0 || to.length === 0 ? { reliable: true, topic } : { reliable: true, topic, destinationIdentities: [...to] }
|
|
3810
4288
|
);
|
|
3811
4289
|
},
|
|
4290
|
+
async setAudioProcessing(processing) {
|
|
4291
|
+
audioCapture = { ...audioCapture, ...processing };
|
|
4292
|
+
const target = room;
|
|
4293
|
+
if (target === null) return;
|
|
4294
|
+
const { Track } = await import("livekit-client");
|
|
4295
|
+
const publication = target.localParticipant.getTrackPublication(Track.Source.Microphone);
|
|
4296
|
+
const track = publication?.track;
|
|
4297
|
+
if (track === void 0 || publication?.isMuted === true) return;
|
|
4298
|
+
try {
|
|
4299
|
+
await track.restartTrack({
|
|
4300
|
+
...audioCapture,
|
|
4301
|
+
...target.getActiveDevice("audioinput") !== void 0 ? { deviceId: target.getActiveDevice("audioinput") } : {}
|
|
4302
|
+
});
|
|
4303
|
+
} catch (error) {
|
|
4304
|
+
throw normalizeDeviceError(error, "meet.setAudioProcessing");
|
|
4305
|
+
}
|
|
4306
|
+
},
|
|
4307
|
+
async setVideoEffect(effect) {
|
|
4308
|
+
videoEffect = effect;
|
|
4309
|
+
const target = room;
|
|
4310
|
+
if (target === null) return;
|
|
4311
|
+
await applyVideoEffect(target, effect);
|
|
4312
|
+
},
|
|
4313
|
+
supportsVideoEffects() {
|
|
4314
|
+
return videoEffectsSupported();
|
|
4315
|
+
},
|
|
3812
4316
|
attach(identity, kind, element) {
|
|
3813
4317
|
const target = requireRoom("attach");
|
|
3814
4318
|
const source = sources?.[kind];
|
|
@@ -3842,6 +4346,52 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
3842
4346
|
}
|
|
3843
4347
|
};
|
|
3844
4348
|
}
|
|
4349
|
+
async function createCameraPreview(deviceId2, effect) {
|
|
4350
|
+
let module;
|
|
4351
|
+
try {
|
|
4352
|
+
module = await import("livekit-client");
|
|
4353
|
+
} catch (error) {
|
|
4354
|
+
throw new MeetError(
|
|
4355
|
+
"misconfigured",
|
|
4356
|
+
"livekit-client could not be loaded for the camera preview",
|
|
4357
|
+
"The bundler excluded `livekit-client`; remove it from any `external` list.",
|
|
4358
|
+
error
|
|
4359
|
+
);
|
|
4360
|
+
}
|
|
4361
|
+
let track;
|
|
4362
|
+
try {
|
|
4363
|
+
track = await module.createLocalVideoTrack(deviceId2 === null ? {} : { deviceId: deviceId2 });
|
|
4364
|
+
} catch (error) {
|
|
4365
|
+
throw normalizeDeviceError(error, "meet.cameraPreview");
|
|
4366
|
+
}
|
|
4367
|
+
const target = track;
|
|
4368
|
+
async function apply(next) {
|
|
4369
|
+
if (next.kind === "none") {
|
|
4370
|
+
await target.stopProcessor();
|
|
4371
|
+
return;
|
|
4372
|
+
}
|
|
4373
|
+
const processors = await import("@livekit/track-processors");
|
|
4374
|
+
await target.setProcessor(
|
|
4375
|
+
next.kind === "blur" ? processors.BackgroundBlur(next.radius ?? 12) : processors.VirtualBackground(next.imageUrl)
|
|
4376
|
+
);
|
|
4377
|
+
}
|
|
4378
|
+
if (effect.kind !== "none" && videoEffectsSupported()) {
|
|
4379
|
+
try {
|
|
4380
|
+
await apply(effect);
|
|
4381
|
+
} catch {
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
return {
|
|
4385
|
+
attach(element) {
|
|
4386
|
+
track.attach(element);
|
|
4387
|
+
},
|
|
4388
|
+
setEffect: apply,
|
|
4389
|
+
stop() {
|
|
4390
|
+
track.stop();
|
|
4391
|
+
track.detach();
|
|
4392
|
+
}
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
3845
4395
|
|
|
3846
4396
|
// src/react/provider.tsx
|
|
3847
4397
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -3951,7 +4501,9 @@ function MeetRuntime(props) {
|
|
|
3951
4501
|
avatarUrl: props.avatarUrl ?? null,
|
|
3952
4502
|
deviceId: deviceId()
|
|
3953
4503
|
};
|
|
3954
|
-
const
|
|
4504
|
+
const effectsStorage = createWebEffectsStorage();
|
|
4505
|
+
const store = createMeetStore({ effects: effectsStorage.load() });
|
|
4506
|
+
const devicesHolder = { current: null };
|
|
3955
4507
|
const api = createMeetApi({
|
|
3956
4508
|
baseUrl,
|
|
3957
4509
|
organizationId: identity.organizationId,
|
|
@@ -3970,7 +4522,9 @@ function MeetRuntime(props) {
|
|
|
3970
4522
|
identity,
|
|
3971
4523
|
resolveMeeting: (slug) => repository.meetingBySlug(slug),
|
|
3972
4524
|
driverFactory: (handlers) => (driverRef.current ?? createLiveKitDriver)(handlers),
|
|
3973
|
-
onDiagnostic: report
|
|
4525
|
+
onDiagnostic: report,
|
|
4526
|
+
effectsStorage,
|
|
4527
|
+
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null
|
|
3974
4528
|
});
|
|
3975
4529
|
const meetings = createMeetingSession({
|
|
3976
4530
|
manager,
|
|
@@ -3985,16 +4539,36 @@ function MeetRuntime(props) {
|
|
|
3985
4539
|
void engine.rejoin();
|
|
3986
4540
|
}
|
|
3987
4541
|
});
|
|
3988
|
-
const
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
4542
|
+
const resolveRoomToken = () => {
|
|
4543
|
+
const roomName = store.snapshot().meeting?.roomName;
|
|
4544
|
+
if (roomName === void 0) return null;
|
|
4545
|
+
const active = engine.token();
|
|
4546
|
+
if (active?.roomName === roomName && active.token.length > 0) return active.token;
|
|
4547
|
+
return tokens.remembered(roomName)?.token ?? null;
|
|
4548
|
+
};
|
|
4549
|
+
const ai = createMeetAi({ api, store, resolveRoomToken });
|
|
4550
|
+
const inRoom = createInRoomClient({ api, store, resolveRoomToken });
|
|
4551
|
+
let chatReadFor = null;
|
|
4552
|
+
store.subscribe(() => {
|
|
4553
|
+
const snapshot = store.snapshot();
|
|
4554
|
+
if (snapshot.phase !== "connected" || snapshot.meeting === null) {
|
|
4555
|
+
if (snapshot.phase === "left" || snapshot.phase === "idle") chatReadFor = null;
|
|
4556
|
+
return;
|
|
3997
4557
|
}
|
|
4558
|
+
if (chatReadFor === snapshot.meeting.id) return;
|
|
4559
|
+
chatReadFor = snapshot.meeting.id;
|
|
4560
|
+
void inRoom.loadChat().catch((error) => {
|
|
4561
|
+
const normalized = error;
|
|
4562
|
+
store.patch({
|
|
4563
|
+
chatLoaded: true,
|
|
4564
|
+
chatHistoryError: normalized.remedy ?? "Messages sent before you joined could not be read. New messages still arrive."
|
|
4565
|
+
});
|
|
4566
|
+
report({
|
|
4567
|
+
level: "warn",
|
|
4568
|
+
message: normalized.message ?? "meet: the saved chat could not be read",
|
|
4569
|
+
...normalized.remedy !== void 0 ? { remedy: normalized.remedy } : {}
|
|
4570
|
+
});
|
|
4571
|
+
});
|
|
3998
4572
|
});
|
|
3999
4573
|
const recording = createRecordingController({
|
|
4000
4574
|
api,
|
|
@@ -4016,6 +4590,7 @@ function MeetRuntime(props) {
|
|
|
4016
4590
|
},
|
|
4017
4591
|
storage: createWebDeviceStorage()
|
|
4018
4592
|
});
|
|
4593
|
+
devicesHolder.current = devices;
|
|
4019
4594
|
const calls = identity.userId === null ? null : createCallCenter({
|
|
4020
4595
|
manager,
|
|
4021
4596
|
repository,
|
|
@@ -4030,6 +4605,7 @@ function MeetRuntime(props) {
|
|
|
4030
4605
|
engine,
|
|
4031
4606
|
calls,
|
|
4032
4607
|
meetings,
|
|
4608
|
+
inRoom,
|
|
4033
4609
|
recording,
|
|
4034
4610
|
devices,
|
|
4035
4611
|
repository,
|
|
@@ -4151,7 +4727,8 @@ function computeStage(args) {
|
|
|
4151
4727
|
const ordered = orderParticipants(args.participants, args.activeSpeakers);
|
|
4152
4728
|
const sharer = ordered.find((participant) => participant.screenSharing) ?? null;
|
|
4153
4729
|
const pinnedParticipant = args.pinned === null ? null : ordered.find((participant) => participant.identity === args.pinned) ?? null;
|
|
4154
|
-
const
|
|
4730
|
+
const spotlit = args.spotlight == null ? null : ordered.find((participant) => participant.identity === args.spotlight) ?? null;
|
|
4731
|
+
const feature = sharer !== null ? sharer : pinnedParticipant !== null && args.layout !== "grid" ? pinnedParticipant : spotlit !== null ? spotlit : args.layout === "grid" ? null : ordered[0] ?? null;
|
|
4155
4732
|
const rest = feature === null ? ordered : ordered.filter((participant) => participant.identity !== feature.identity);
|
|
4156
4733
|
const pageCount = Math.max(1, Math.ceil(rest.length / pageSize));
|
|
4157
4734
|
const page = Math.min(Math.max(0, args.page ?? 0), pageCount - 1);
|
|
@@ -4630,7 +5207,14 @@ function useRoom() {
|
|
|
4630
5207
|
[engine]
|
|
4631
5208
|
),
|
|
4632
5209
|
sendChat: useCallback2(
|
|
4633
|
-
async (text3) =>
|
|
5210
|
+
async (text3) => {
|
|
5211
|
+
if (host === null) throw noRuntime("sendChat");
|
|
5212
|
+
await host.inRoom.sendChat(text3);
|
|
5213
|
+
},
|
|
5214
|
+
[host]
|
|
5215
|
+
),
|
|
5216
|
+
sendPrivateChat: useCallback2(
|
|
5217
|
+
async (to, text3) => requireEngine(engine, "sendPrivateChat").sendPrivateChat(to, text3),
|
|
4634
5218
|
[engine]
|
|
4635
5219
|
),
|
|
4636
5220
|
pin: useCallback2(
|
|
@@ -4651,6 +5235,7 @@ function useStage(pageSize = DEFAULT_PAGE_SIZE) {
|
|
|
4651
5235
|
participants: snapshot?.participants ?? [],
|
|
4652
5236
|
layout: snapshot?.layout ?? "grid",
|
|
4653
5237
|
pinned: snapshot?.pinned ?? null,
|
|
5238
|
+
spotlight: snapshot?.spotlight ?? null,
|
|
4654
5239
|
activeSpeakers: snapshot?.activeSpeakers ?? [],
|
|
4655
5240
|
pageSize,
|
|
4656
5241
|
page
|
|
@@ -4983,33 +5568,160 @@ function useHostControls() {
|
|
|
4983
5568
|
const host = useMeetHost();
|
|
4984
5569
|
const snapshot = useMeetSnapshot();
|
|
4985
5570
|
const role = useRole();
|
|
4986
|
-
const engine = host?.engine ?? null;
|
|
4987
5571
|
const me = snapshot?.participants.find(
|
|
4988
5572
|
(participant) => participant.identity === snapshot.localIdentity
|
|
4989
5573
|
);
|
|
4990
5574
|
const actor = me === void 0 ? void 0 : { ...me, role };
|
|
5575
|
+
const [busy, setBusy] = useState2(false);
|
|
5576
|
+
const inRoom = host?.inRoom ?? null;
|
|
5577
|
+
const store = host?.store ?? null;
|
|
5578
|
+
const notice = snapshot?.actionNotice ?? null;
|
|
5579
|
+
const setNotice = useCallback2(
|
|
5580
|
+
(next) => store?.patch({ actionNotice: next === null ? null : { ...next, at: Date.now() } }),
|
|
5581
|
+
[store]
|
|
5582
|
+
);
|
|
5583
|
+
const run = useCallback2(
|
|
5584
|
+
async (request) => {
|
|
5585
|
+
if (inRoom === null) throw noRuntime(`moderate(${request.action})`);
|
|
5586
|
+
setBusy(true);
|
|
5587
|
+
try {
|
|
5588
|
+
const result = await inRoom.moderate(request);
|
|
5589
|
+
setNotice({ text: result.message, failed: false });
|
|
5590
|
+
return result;
|
|
5591
|
+
} catch (thrown) {
|
|
5592
|
+
const error = thrown;
|
|
5593
|
+
setNotice({
|
|
5594
|
+
text: [error.message, error.remedy].filter(Boolean).join(" "),
|
|
5595
|
+
failed: true
|
|
5596
|
+
});
|
|
5597
|
+
return null;
|
|
5598
|
+
} finally {
|
|
5599
|
+
setBusy(false);
|
|
5600
|
+
}
|
|
5601
|
+
},
|
|
5602
|
+
[inRoom, setNotice]
|
|
5603
|
+
);
|
|
4991
5604
|
return {
|
|
4992
5605
|
isHost: isHostRole(role),
|
|
5606
|
+
isOwner: role === "host",
|
|
5607
|
+
notice,
|
|
5608
|
+
busy,
|
|
5609
|
+
clearNotice: useCallback2(() => setNotice(null), [setNotice]),
|
|
4993
5610
|
canModerate: useCallback2(
|
|
4994
5611
|
(target) => canModerateParticipant(actor, target),
|
|
4995
5612
|
[actor]
|
|
4996
5613
|
),
|
|
5614
|
+
run,
|
|
4997
5615
|
mute: useCallback2(
|
|
4998
|
-
async (target) =>
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
[engine]
|
|
5616
|
+
async (target) => {
|
|
5617
|
+
await run({ action: "mute", identity: target.identity });
|
|
5618
|
+
},
|
|
5619
|
+
[run]
|
|
5003
5620
|
),
|
|
5004
5621
|
remove: useCallback2(
|
|
5005
|
-
async (target) =>
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5622
|
+
async (target) => {
|
|
5623
|
+
await run({ action: "remove", identity: target.identity });
|
|
5624
|
+
},
|
|
5625
|
+
[run]
|
|
5626
|
+
)
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
function useHandQueue() {
|
|
5630
|
+
const snapshot = useMeetSnapshot();
|
|
5631
|
+
return useMemo2(
|
|
5632
|
+
() => (snapshot?.participants ?? []).filter((participant) => participant.handRaised).sort((a, b) => (a.handRaisedAt ?? 0) - (b.handRaisedAt ?? 0)),
|
|
5633
|
+
[snapshot?.participants]
|
|
5634
|
+
);
|
|
5635
|
+
}
|
|
5636
|
+
function useMediaEffects() {
|
|
5637
|
+
const host = useMeetHost();
|
|
5638
|
+
const snapshot = useMeetSnapshot();
|
|
5639
|
+
const engine = host?.engine ?? null;
|
|
5640
|
+
return {
|
|
5641
|
+
effects: snapshot?.effects ?? DEFAULT_EFFECTS,
|
|
5642
|
+
backgroundsSupported: engine?.supportsVideoEffects() ?? false,
|
|
5643
|
+
set: useCallback2(
|
|
5644
|
+
async (next) => requireEngine(engine, "setEffects").setEffects(next),
|
|
5009
5645
|
[engine]
|
|
5010
5646
|
)
|
|
5011
5647
|
};
|
|
5012
5648
|
}
|
|
5649
|
+
function useModerationNotice() {
|
|
5650
|
+
const host = useMeetHost();
|
|
5651
|
+
const snapshot = useMeetSnapshot();
|
|
5652
|
+
return {
|
|
5653
|
+
notice: snapshot?.moderationNotice ?? null,
|
|
5654
|
+
dismiss: useCallback2(() => host?.store.patch({ moderationNotice: null }), [host])
|
|
5655
|
+
};
|
|
5656
|
+
}
|
|
5657
|
+
function useAudioLevel(stream) {
|
|
5658
|
+
const [level, setLevel] = useState2(0);
|
|
5659
|
+
useEffect2(() => {
|
|
5660
|
+
if (stream === null || stream.getAudioTracks().length === 0) {
|
|
5661
|
+
setLevel(0);
|
|
5662
|
+
return void 0;
|
|
5663
|
+
}
|
|
5664
|
+
const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
|
|
5665
|
+
if (Context === void 0) return void 0;
|
|
5666
|
+
const context = new Context();
|
|
5667
|
+
const source = context.createMediaStreamSource(stream);
|
|
5668
|
+
const analyser = context.createAnalyser();
|
|
5669
|
+
analyser.fftSize = 512;
|
|
5670
|
+
source.connect(analyser);
|
|
5671
|
+
const samples = new Uint8Array(analyser.fftSize);
|
|
5672
|
+
let frame = 0;
|
|
5673
|
+
const tick = () => {
|
|
5674
|
+
analyser.getByteTimeDomainData(samples);
|
|
5675
|
+
let peak = 0;
|
|
5676
|
+
for (const sample of samples) peak = Math.max(peak, Math.abs(sample - 128) / 128);
|
|
5677
|
+
setLevel((previous) => Math.abs(previous - peak) > 0.02 ? peak : previous);
|
|
5678
|
+
frame = requestAnimationFrame(tick);
|
|
5679
|
+
};
|
|
5680
|
+
frame = requestAnimationFrame(tick);
|
|
5681
|
+
return () => {
|
|
5682
|
+
cancelAnimationFrame(frame);
|
|
5683
|
+
source.disconnect();
|
|
5684
|
+
void context.close();
|
|
5685
|
+
};
|
|
5686
|
+
}, [stream]);
|
|
5687
|
+
return level;
|
|
5688
|
+
}
|
|
5689
|
+
var MEET_SHORTCUTS = [
|
|
5690
|
+
{ id: "mic", keys: "\u2318/Ctrl + D", label: "Mute or unmute your microphone" },
|
|
5691
|
+
{ id: "camera", keys: "\u2318/Ctrl + E", label: "Turn your camera on or off" },
|
|
5692
|
+
{ id: "hand", keys: "\u2318/Ctrl + Alt + H", label: "Raise or lower your hand" },
|
|
5693
|
+
{ id: "chat", keys: "\u2318/Ctrl + Alt + C", label: "Open or close chat" },
|
|
5694
|
+
{ id: "people", keys: "\u2318/Ctrl + Alt + P", label: "Open or close people" },
|
|
5695
|
+
{ id: "help", keys: "Shift + ?", label: "Show keyboard shortcuts" }
|
|
5696
|
+
];
|
|
5697
|
+
function useMeetShortcuts(handlers) {
|
|
5698
|
+
const ref = useRef2(handlers);
|
|
5699
|
+
ref.current = handlers;
|
|
5700
|
+
useEffect2(() => {
|
|
5701
|
+
const target = globalThis.window;
|
|
5702
|
+
if (target === void 0) return void 0;
|
|
5703
|
+
const onKey = (event) => {
|
|
5704
|
+
const element = event.target;
|
|
5705
|
+
const typing = element !== null && (element.isContentEditable || element.tagName === "INPUT" || element.tagName === "TEXTAREA" || element.tagName === "SELECT");
|
|
5706
|
+
const mod = event.metaKey || event.ctrlKey;
|
|
5707
|
+
const key = event.key.toLowerCase();
|
|
5708
|
+
let id = null;
|
|
5709
|
+
if (mod && !event.altKey && !event.shiftKey && key === "d") id = "mic";
|
|
5710
|
+
else if (mod && !event.altKey && !event.shiftKey && key === "e") id = "camera";
|
|
5711
|
+
else if (mod && event.altKey && (event.code === "KeyH" || key === "h")) id = "hand";
|
|
5712
|
+
else if (mod && event.altKey && (event.code === "KeyC" || key === "c")) id = "chat";
|
|
5713
|
+
else if (mod && event.altKey && (event.code === "KeyP" || key === "p")) id = "people";
|
|
5714
|
+
else if (!mod && !typing && event.key === "?") id = "help";
|
|
5715
|
+
if (id === null) return;
|
|
5716
|
+
const handler = ref.current[id];
|
|
5717
|
+
if (handler === void 0) return;
|
|
5718
|
+
event.preventDefault();
|
|
5719
|
+
handler();
|
|
5720
|
+
};
|
|
5721
|
+
target.addEventListener("keydown", onKey);
|
|
5722
|
+
return () => target.removeEventListener("keydown", onKey);
|
|
5723
|
+
}, []);
|
|
5724
|
+
}
|
|
5013
5725
|
function useMeeting() {
|
|
5014
5726
|
return useMeetSnapshot()?.meeting ?? null;
|
|
5015
5727
|
}
|
|
@@ -5185,7 +5897,7 @@ function useGuestRecordAccess(meeting) {
|
|
|
5185
5897
|
}
|
|
5186
5898
|
|
|
5187
5899
|
// src/react/components.tsx
|
|
5188
|
-
import { useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
5900
|
+
import { useEffect as useEffect3, useLayoutEffect, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
5189
5901
|
import { ErrorBox } from "@ai-matrx/design-system";
|
|
5190
5902
|
import { avatarPaletteIndex, formatDurationMs as formatDurationMs2, getInitials } from "@ai-matrx/kit/format";
|
|
5191
5903
|
|
|
@@ -5356,12 +6068,66 @@ function WifiLowIcon(props) {
|
|
|
5356
6068
|
/* @__PURE__ */ jsx2("path", { d: "M8.5 16.5a5 5 0 0 1 7 0" })
|
|
5357
6069
|
] });
|
|
5358
6070
|
}
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
/* @__PURE__ */
|
|
6071
|
+
function SmileIcon(props) {
|
|
6072
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6073
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
6074
|
+
/* @__PURE__ */ jsx2("path", { d: "M8 14s1.5 2 4 2 4-2 4-2" }),
|
|
6075
|
+
/* @__PURE__ */ jsx2("line", { x1: "9", x2: "9.01", y1: "9", y2: "9" }),
|
|
6076
|
+
/* @__PURE__ */ jsx2("line", { x1: "15", x2: "15.01", y1: "9", y2: "9" })
|
|
6077
|
+
] });
|
|
6078
|
+
}
|
|
6079
|
+
function MoreIcon(props) {
|
|
6080
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6081
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "1" }),
|
|
6082
|
+
/* @__PURE__ */ jsx2("circle", { cx: "19", cy: "12", r: "1" }),
|
|
6083
|
+
/* @__PURE__ */ jsx2("circle", { cx: "5", cy: "12", r: "1" })
|
|
6084
|
+
] });
|
|
6085
|
+
}
|
|
6086
|
+
function SearchIcon(props) {
|
|
6087
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6088
|
+
/* @__PURE__ */ jsx2("circle", { cx: "11", cy: "11", r: "8" }),
|
|
6089
|
+
/* @__PURE__ */ jsx2("path", { d: "m21 21-4.3-4.3" })
|
|
6090
|
+
] });
|
|
6091
|
+
}
|
|
6092
|
+
function KeyboardIcon(props) {
|
|
6093
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6094
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 8h.01M12 12h.01M14 8h.01M16 12h.01M18 8h.01M6 8h.01M7 16h10M8 12h.01" }),
|
|
6095
|
+
/* @__PURE__ */ jsx2("rect", { width: "20", height: "16", x: "2", y: "4", rx: "2" })
|
|
6096
|
+
] });
|
|
6097
|
+
}
|
|
6098
|
+
function SpotlightIcon(props) {
|
|
6099
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("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" }) });
|
|
6100
|
+
}
|
|
6101
|
+
function SignalIcon({ bars, ...props }) {
|
|
6102
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6103
|
+
/* @__PURE__ */ jsx2("path", { d: "M4 20v-2", opacity: bars >= 1 ? 1 : 0.3 }),
|
|
6104
|
+
/* @__PURE__ */ jsx2("path", { d: "M10 20v-6", opacity: bars >= 2 ? 1 : 0.3 }),
|
|
6105
|
+
/* @__PURE__ */ jsx2("path", { d: "M16 20V9", opacity: bars >= 3 ? 1 : 0.3 }),
|
|
6106
|
+
/* @__PURE__ */ jsx2("path", { d: "M22 20V4", opacity: bars >= 3 ? 1 : 0.3 })
|
|
6107
|
+
] });
|
|
6108
|
+
}
|
|
6109
|
+
function SpeakerIcon(props) {
|
|
6110
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6111
|
+
/* @__PURE__ */ jsx2("path", { d: "M11 5 6 9H2v6h4l5 4V5Z" }),
|
|
6112
|
+
/* @__PURE__ */ jsx2("path", { d: "M15.54 8.46a5 5 0 0 1 0 7.07" }),
|
|
6113
|
+
/* @__PURE__ */ jsx2("path", { d: "M19.07 4.93a10 10 0 0 1 0 14.14" })
|
|
6114
|
+
] });
|
|
6115
|
+
}
|
|
6116
|
+
function FlagIcon(props) {
|
|
6117
|
+
return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
|
|
6118
|
+
/* @__PURE__ */ jsx2("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" }),
|
|
6119
|
+
/* @__PURE__ */ jsx2("line", { x1: "4", x2: "4", y1: "22", y2: "15" })
|
|
6120
|
+
] });
|
|
6121
|
+
}
|
|
6122
|
+
function PencilIcon(props) {
|
|
6123
|
+
return /* @__PURE__ */ jsx2(Icon, { ...props, children: /* @__PURE__ */ jsx2("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }) });
|
|
6124
|
+
}
|
|
6125
|
+
|
|
6126
|
+
// src/react/components.tsx
|
|
6127
|
+
import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
6128
|
+
function InertNotice({ what }) {
|
|
6129
|
+
return /* @__PURE__ */ jsxs2("div", { className: "mx-meet__empty", role: "status", children: [
|
|
6130
|
+
/* @__PURE__ */ jsxs2("p", { className: "mx-meet__empty-title", children: [
|
|
5365
6131
|
what,
|
|
5366
6132
|
" is not ready yet"
|
|
5367
6133
|
] }),
|
|
@@ -5456,12 +6222,17 @@ function ParticipantTile({
|
|
|
5456
6222
|
featured = false
|
|
5457
6223
|
}) {
|
|
5458
6224
|
const host = useMeetHost();
|
|
5459
|
-
const
|
|
6225
|
+
const snapshot = useMeetSnapshot();
|
|
6226
|
+
const queue = useHandQueue();
|
|
6227
|
+
const [menu, setMenu] = useState3(false);
|
|
5460
6228
|
const videoRef = useTrack(
|
|
5461
6229
|
participant.identity,
|
|
5462
6230
|
participant.screenSharing ? "screen" : "camera"
|
|
5463
6231
|
);
|
|
5464
6232
|
const showVideo = participant.cameraEnabled || participant.screenSharing;
|
|
6233
|
+
const pinnedHere = snapshot?.pinned === participant.identity;
|
|
6234
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
6235
|
+
const handPosition = participant.handRaised ? queue.findIndex((entry) => entry.identity === participant.identity) + 1 : 0;
|
|
5465
6236
|
return /* @__PURE__ */ jsxs2(
|
|
5466
6237
|
"div",
|
|
5467
6238
|
{
|
|
@@ -5482,44 +6253,59 @@ function ParticipantTile({
|
|
|
5482
6253
|
muted: participant.isLocal
|
|
5483
6254
|
}
|
|
5484
6255
|
) : /* @__PURE__ */ jsx3("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ jsx3(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: featured ? 96 : 56 }) }),
|
|
6256
|
+
participant.handRaised ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__tile-hand", title: `Hand raised \xB7 ${ordinal(handPosition)} in line`, children: [
|
|
6257
|
+
/* @__PURE__ */ jsx3(HandIcon, { width: 14, height: 14 }),
|
|
6258
|
+
handPosition > 0 ? handPosition : null
|
|
6259
|
+
] }) : null,
|
|
5485
6260
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__tile-bar", children: [
|
|
5486
6261
|
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__tile-name", children: [
|
|
6262
|
+
spotlit ? /* @__PURE__ */ jsx3("span", { title: "Spotlighted for everyone", className: "mx-meet__tile-flag", children: /* @__PURE__ */ jsx3(SpotlightIcon, { width: 12, height: 12 }) }) : null,
|
|
5487
6263
|
participant.displayName,
|
|
5488
6264
|
participant.isLocal ? " (you)" : "",
|
|
5489
6265
|
participant.isAgent ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
5490
6266
|
/* @__PURE__ */ jsx3(RoleBadge, { role: participant.role })
|
|
5491
6267
|
] }),
|
|
5492
6268
|
!participant.micEnabled ? /* @__PURE__ */ jsx3(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
5493
|
-
|
|
5494
|
-
/* @__PURE__ */ jsx3(ConnectionDot, { quality: participant.quality })
|
|
6269
|
+
/* @__PURE__ */ jsx3(QualityBars, { quality: participant.quality })
|
|
5495
6270
|
] }),
|
|
5496
|
-
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__tile-actions", children: [
|
|
6271
|
+
/* @__PURE__ */ jsxs2("div", { className: menu ? "mx-meet__tile-actions mx-meet__tile-actions--open" : "mx-meet__tile-actions", children: [
|
|
5497
6272
|
/* @__PURE__ */ jsx3(
|
|
5498
6273
|
ControlButton,
|
|
5499
6274
|
{
|
|
5500
|
-
label:
|
|
5501
|
-
|
|
6275
|
+
label: pinnedHere ? "Unpin" : `Pin ${participant.displayName} for me`,
|
|
6276
|
+
active: pinnedHere,
|
|
6277
|
+
onClick: () => host?.engine.pin(pinnedHere ? null : participant.identity),
|
|
5502
6278
|
children: /* @__PURE__ */ jsx3(PinIcon, { width: 16, height: 16 })
|
|
5503
6279
|
}
|
|
5504
6280
|
),
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
) : null
|
|
6281
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__anchor", children: [
|
|
6282
|
+
/* @__PURE__ */ jsx3(
|
|
6283
|
+
ControlButton,
|
|
6284
|
+
{
|
|
6285
|
+
label: `More for ${participant.displayName}`,
|
|
6286
|
+
active: menu,
|
|
6287
|
+
onClick: () => setMenu(!menu),
|
|
6288
|
+
children: /* @__PURE__ */ jsx3(MoreIcon, { width: 16, height: 16 })
|
|
6289
|
+
}
|
|
6290
|
+
),
|
|
6291
|
+
menu ? /* @__PURE__ */ jsx3(PersonMenu, { participant, onClose: () => setMenu(false), placement: "down" }) : null
|
|
6292
|
+
] })
|
|
5518
6293
|
] })
|
|
5519
6294
|
]
|
|
5520
6295
|
}
|
|
5521
6296
|
);
|
|
5522
6297
|
}
|
|
6298
|
+
function ordinal(position) {
|
|
6299
|
+
if (position <= 0) return "";
|
|
6300
|
+
const suffix = position % 100 >= 11 && position % 100 <= 13 ? "th" : { 1: "st", 2: "nd", 3: "rd" }[position % 10] ?? "th";
|
|
6301
|
+
return `${position}${suffix}`;
|
|
6302
|
+
}
|
|
6303
|
+
function QualityBars({ quality }) {
|
|
6304
|
+
if (quality === "unknown") return null;
|
|
6305
|
+
const bars = quality === "excellent" ? 3 : quality === "good" ? 2 : quality === "poor" ? 1 : 0;
|
|
6306
|
+
const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
|
|
6307
|
+
return /* @__PURE__ */ jsx3("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ jsx3(SignalIcon, { bars, width: 14, height: 14 }) });
|
|
6308
|
+
}
|
|
5523
6309
|
function Stage({ pageSize }) {
|
|
5524
6310
|
const stage = useStage(pageSize);
|
|
5525
6311
|
const columns = gridColumns(stage.tiles.length);
|
|
@@ -5548,22 +6334,52 @@ function Stage({ pageSize }) {
|
|
|
5548
6334
|
] }) : null
|
|
5549
6335
|
] });
|
|
5550
6336
|
}
|
|
6337
|
+
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
5551
6338
|
function ControlBar({ onLeave }) {
|
|
5552
6339
|
const room = useRoom();
|
|
5553
6340
|
const recording = useRecording();
|
|
5554
6341
|
const [panel, setPanel] = useState3("none");
|
|
6342
|
+
const [chatTo, setChatTo] = useState3(null);
|
|
6343
|
+
const [popover, setPopover] = useState3("none");
|
|
6344
|
+
const [shortcuts, setShortcuts] = useState3(false);
|
|
5555
6345
|
const snapshot = useMeetSnapshot();
|
|
5556
6346
|
const lobby = useLobby();
|
|
6347
|
+
const controls = useHostControls();
|
|
6348
|
+
const queue = useHandQueue();
|
|
5557
6349
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
6350
|
+
const unread = useUnreadChat(panel === "chat");
|
|
6351
|
+
const toggle = (next) => setPanel(panel === next ? "none" : next);
|
|
6352
|
+
useMeetShortcuts({
|
|
6353
|
+
mic: () => void room.toggleMicrophone(),
|
|
6354
|
+
camera: () => void room.toggleCamera(),
|
|
6355
|
+
hand: () => void room.toggleHand(),
|
|
6356
|
+
chat: () => toggle("chat"),
|
|
6357
|
+
people: () => toggle("people"),
|
|
6358
|
+
help: () => setShortcuts(true)
|
|
6359
|
+
});
|
|
6360
|
+
const hasSpotlight = snapshot?.spotlight !== null && snapshot?.spotlight !== void 0;
|
|
5558
6361
|
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
5559
|
-
|
|
5560
|
-
|
|
6362
|
+
/* @__PURE__ */ jsx3(MeetingOverlays, {}),
|
|
6363
|
+
shortcuts ? /* @__PURE__ */ jsx3(ShortcutsSheet, { onClose: () => setShortcuts(false) }) : null,
|
|
6364
|
+
panel === "chat" ? /* @__PURE__ */ jsx3(ChatPanel, { onClose: () => setPanel("none"), to: chatTo, onTo: setChatTo }) : null,
|
|
6365
|
+
panel === "people" ? /* @__PURE__ */ jsx3(
|
|
6366
|
+
PeoplePanel,
|
|
6367
|
+
{
|
|
6368
|
+
onClose: () => setPanel("none"),
|
|
6369
|
+
onMessage: (identity) => {
|
|
6370
|
+
setChatTo(identity);
|
|
6371
|
+
setPanel("chat");
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
) : null,
|
|
5561
6375
|
panel === "ai" ? /* @__PURE__ */ jsx3(AiPanel, { onClose: () => setPanel("none") }) : null,
|
|
6376
|
+
panel === "settings" ? /* @__PURE__ */ jsx3(SettingsPanel, { onClose: () => setPanel("none") }) : null,
|
|
6377
|
+
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ jsx3(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
5562
6378
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__controls", children: [
|
|
5563
6379
|
/* @__PURE__ */ jsx3(
|
|
5564
6380
|
ControlButton,
|
|
5565
6381
|
{
|
|
5566
|
-
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute" : "Unmute",
|
|
6382
|
+
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
|
|
5567
6383
|
active: room.local?.micEnabled === true,
|
|
5568
6384
|
onClick: () => void room.toggleMicrophone(),
|
|
5569
6385
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ jsx3(MicIcon, {}) : /* @__PURE__ */ jsx3(MicOffIcon, {})
|
|
@@ -5572,7 +6388,7 @@ function ControlBar({ onLeave }) {
|
|
|
5572
6388
|
/* @__PURE__ */ jsx3(
|
|
5573
6389
|
ControlButton,
|
|
5574
6390
|
{
|
|
5575
|
-
label: room.local?.cameraEnabled === true ? "Stop video" : "Start video",
|
|
6391
|
+
label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
|
|
5576
6392
|
active: room.local?.cameraEnabled === true,
|
|
5577
6393
|
onClick: () => void room.toggleCamera(),
|
|
5578
6394
|
children: room.local?.cameraEnabled === true ? /* @__PURE__ */ jsx3(CameraIcon, {}) : /* @__PURE__ */ jsx3(CameraOffIcon, {})
|
|
@@ -5587,31 +6403,152 @@ function ControlBar({ onLeave }) {
|
|
|
5587
6403
|
children: /* @__PURE__ */ jsx3(ScreenShareIcon, {})
|
|
5588
6404
|
}
|
|
5589
6405
|
),
|
|
6406
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__anchor", children: [
|
|
6407
|
+
/* @__PURE__ */ jsx3(
|
|
6408
|
+
ControlButton,
|
|
6409
|
+
{
|
|
6410
|
+
label: "Send a reaction",
|
|
6411
|
+
active: popover === "reactions",
|
|
6412
|
+
onClick: () => setPopover(popover === "reactions" ? "none" : "reactions"),
|
|
6413
|
+
children: /* @__PURE__ */ jsx3(SmileIcon, {})
|
|
6414
|
+
}
|
|
6415
|
+
),
|
|
6416
|
+
popover === "reactions" ? /* @__PURE__ */ jsx3(Popover, { label: "Reactions", onClose: () => setPopover("none"), children: /* @__PURE__ */ jsx3("div", { className: "mx-meet__emoji-bar", children: MEET_REACTIONS.map((emoji) => /* @__PURE__ */ jsx3(
|
|
6417
|
+
"button",
|
|
6418
|
+
{
|
|
6419
|
+
type: "button",
|
|
6420
|
+
className: "mx-meet__emoji",
|
|
6421
|
+
"aria-label": `React ${emoji}`,
|
|
6422
|
+
onClick: () => void room.react(emoji),
|
|
6423
|
+
children: emoji
|
|
6424
|
+
},
|
|
6425
|
+
emoji
|
|
6426
|
+
)) }) }) : null
|
|
6427
|
+
] }),
|
|
5590
6428
|
/* @__PURE__ */ jsx3(
|
|
5591
6429
|
ControlButton,
|
|
5592
6430
|
{
|
|
5593
|
-
label: room.local?.handRaised === true ? "Lower hand" : "Raise hand",
|
|
6431
|
+
label: room.local?.handRaised === true ? "Lower hand (\u2318\u2325H)" : "Raise hand (\u2318\u2325H)",
|
|
5594
6432
|
active: room.local?.handRaised === true,
|
|
5595
6433
|
onClick: () => void room.toggleHand(),
|
|
5596
6434
|
children: /* @__PURE__ */ jsx3(HandIcon, {})
|
|
5597
6435
|
}
|
|
5598
6436
|
),
|
|
5599
|
-
/* @__PURE__ */
|
|
6437
|
+
/* @__PURE__ */ jsxs2(ControlButton, { label: "Chat (\u2318\u2325C)", active: panel === "chat", onClick: () => toggle("chat"), children: [
|
|
6438
|
+
/* @__PURE__ */ jsx3(ChatIcon, {}),
|
|
6439
|
+
unread > 0 ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6440
|
+
] }),
|
|
5600
6441
|
/* @__PURE__ */ jsxs2(
|
|
5601
6442
|
ControlButton,
|
|
5602
6443
|
{
|
|
5603
6444
|
label: lobby.waiting.length > 0 ? `People \u2014 ${lobby.waiting.length} waiting` : `People \u2014 ${(snapshot?.participants ?? []).length} in the meeting`,
|
|
5604
6445
|
active: panel === "people",
|
|
5605
|
-
onClick: () =>
|
|
6446
|
+
onClick: () => toggle("people"),
|
|
5606
6447
|
children: [
|
|
5607
6448
|
/* @__PURE__ */ jsx3(PeopleIcon, {}),
|
|
5608
|
-
lobby.waiting.length > 0 ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
6449
|
+
lobby.waiting.length > 0 || controls.isHost && queue.length > 0 ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__dot", "aria-hidden": "true" }) : null
|
|
5609
6450
|
]
|
|
5610
6451
|
}
|
|
5611
6452
|
),
|
|
5612
|
-
/* @__PURE__ */ jsx3(AiControl, { active: panel === "ai", onToggle: () =>
|
|
6453
|
+
/* @__PURE__ */ jsx3(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
|
|
5613
6454
|
recording.canStart ? /* @__PURE__ */ jsx3(ControlButton, { label: "Start recording", onClick: () => void recording.start(), children: /* @__PURE__ */ jsx3(RecordIcon, {}) }) : null,
|
|
5614
6455
|
recording.canStop ? /* @__PURE__ */ jsx3(ControlButton, { label: "Stop recording", active: true, onClick: () => void recording.stop(), children: /* @__PURE__ */ jsx3(RecordIcon, {}) }) : null,
|
|
6456
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__anchor", children: [
|
|
6457
|
+
/* @__PURE__ */ jsx3(
|
|
6458
|
+
ControlButton,
|
|
6459
|
+
{
|
|
6460
|
+
label: "More options",
|
|
6461
|
+
active: popover === "more" || panel === "settings",
|
|
6462
|
+
onClick: () => setPopover(popover === "more" ? "none" : "more"),
|
|
6463
|
+
children: /* @__PURE__ */ jsx3(MoreIcon, {})
|
|
6464
|
+
}
|
|
6465
|
+
),
|
|
6466
|
+
popover === "more" ? /* @__PURE__ */ jsx3(Popover, { label: "More options", onClose: () => setPopover("none"), children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
6467
|
+
/* @__PURE__ */ jsxs2(
|
|
6468
|
+
MenuItem,
|
|
6469
|
+
{
|
|
6470
|
+
onSelect: () => {
|
|
6471
|
+
setPopover("none");
|
|
6472
|
+
setPanel("settings");
|
|
6473
|
+
},
|
|
6474
|
+
children: [
|
|
6475
|
+
/* @__PURE__ */ jsx3(SettingsIcon, { width: 16, height: 16 }),
|
|
6476
|
+
" Audio, video and effects"
|
|
6477
|
+
]
|
|
6478
|
+
}
|
|
6479
|
+
),
|
|
6480
|
+
/* @__PURE__ */ jsxs2(
|
|
6481
|
+
MenuItem,
|
|
6482
|
+
{
|
|
6483
|
+
onSelect: () => {
|
|
6484
|
+
setPopover("none");
|
|
6485
|
+
setShortcuts(true);
|
|
6486
|
+
},
|
|
6487
|
+
children: [
|
|
6488
|
+
/* @__PURE__ */ jsx3(KeyboardIcon, { width: 16, height: 16 }),
|
|
6489
|
+
" Keyboard shortcuts"
|
|
6490
|
+
]
|
|
6491
|
+
}
|
|
6492
|
+
),
|
|
6493
|
+
controls.isHost ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
6494
|
+
/* @__PURE__ */ jsx3("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
6495
|
+
/* @__PURE__ */ jsxs2(
|
|
6496
|
+
MenuItem,
|
|
6497
|
+
{
|
|
6498
|
+
onSelect: () => {
|
|
6499
|
+
setPopover("none");
|
|
6500
|
+
void controls.run({ action: "mute_all" });
|
|
6501
|
+
},
|
|
6502
|
+
children: [
|
|
6503
|
+
/* @__PURE__ */ jsx3(MicOffIcon, { width: 16, height: 16 }),
|
|
6504
|
+
" Mute everyone"
|
|
6505
|
+
]
|
|
6506
|
+
}
|
|
6507
|
+
),
|
|
6508
|
+
/* @__PURE__ */ jsxs2(
|
|
6509
|
+
MenuItem,
|
|
6510
|
+
{
|
|
6511
|
+
onSelect: () => {
|
|
6512
|
+
setPopover("none");
|
|
6513
|
+
void controls.run({ action: "ask_all_unmute" });
|
|
6514
|
+
},
|
|
6515
|
+
children: [
|
|
6516
|
+
/* @__PURE__ */ jsx3(MicIcon, { width: 16, height: 16 }),
|
|
6517
|
+
" Ask everyone to unmute"
|
|
6518
|
+
]
|
|
6519
|
+
}
|
|
6520
|
+
),
|
|
6521
|
+
queue.length > 0 ? /* @__PURE__ */ jsxs2(
|
|
6522
|
+
MenuItem,
|
|
6523
|
+
{
|
|
6524
|
+
onSelect: () => {
|
|
6525
|
+
setPopover("none");
|
|
6526
|
+
void controls.run({ action: "lower_all_hands" });
|
|
6527
|
+
},
|
|
6528
|
+
children: [
|
|
6529
|
+
/* @__PURE__ */ jsx3(HandIcon, { width: 16, height: 16 }),
|
|
6530
|
+
" Lower all hands (",
|
|
6531
|
+
queue.length,
|
|
6532
|
+
")"
|
|
6533
|
+
]
|
|
6534
|
+
}
|
|
6535
|
+
) : null,
|
|
6536
|
+
hasSpotlight ? /* @__PURE__ */ jsxs2(
|
|
6537
|
+
MenuItem,
|
|
6538
|
+
{
|
|
6539
|
+
onSelect: () => {
|
|
6540
|
+
setPopover("none");
|
|
6541
|
+
void controls.run({ action: "spotlight", identity: null });
|
|
6542
|
+
},
|
|
6543
|
+
children: [
|
|
6544
|
+
/* @__PURE__ */ jsx3(SpotlightIcon, { width: 16, height: 16 }),
|
|
6545
|
+
" Remove spotlight"
|
|
6546
|
+
]
|
|
6547
|
+
}
|
|
6548
|
+
) : null
|
|
6549
|
+
] }) : null
|
|
6550
|
+
] }) }) : null
|
|
6551
|
+
] }),
|
|
5615
6552
|
/* @__PURE__ */ jsx3(
|
|
5616
6553
|
ControlButton,
|
|
5617
6554
|
{
|
|
@@ -5627,6 +6564,150 @@ function ControlBar({ onLeave }) {
|
|
|
5627
6564
|
] })
|
|
5628
6565
|
] });
|
|
5629
6566
|
}
|
|
6567
|
+
function useUnreadChat(open) {
|
|
6568
|
+
const snapshot = useMeetSnapshot();
|
|
6569
|
+
const chat = snapshot?.chat ?? [];
|
|
6570
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6571
|
+
const [seen, setSeen] = useState3(0);
|
|
6572
|
+
useEffect3(() => {
|
|
6573
|
+
if (open) setSeen(chat.length);
|
|
6574
|
+
}, [open, chat.length]);
|
|
6575
|
+
const loaded = snapshot?.chatLoaded === true;
|
|
6576
|
+
const [baseline, setBaseline] = useState3(null);
|
|
6577
|
+
useEffect3(() => {
|
|
6578
|
+
if (loaded && baseline === null) setBaseline(chat.length);
|
|
6579
|
+
}, [loaded, baseline, chat.length]);
|
|
6580
|
+
if (open) return 0;
|
|
6581
|
+
const from = Math.max(seen, baseline ?? chat.length);
|
|
6582
|
+
return chat.slice(from).filter((message) => message.identity !== local).length;
|
|
6583
|
+
}
|
|
6584
|
+
function Popover({
|
|
6585
|
+
label,
|
|
6586
|
+
onClose,
|
|
6587
|
+
children,
|
|
6588
|
+
placement = "up"
|
|
6589
|
+
}) {
|
|
6590
|
+
const ref = useRef3(null);
|
|
6591
|
+
const [style, setStyle] = useState3({ visibility: "hidden" });
|
|
6592
|
+
useLayoutEffect(() => {
|
|
6593
|
+
const element = ref.current;
|
|
6594
|
+
const anchor = element?.parentElement ?? null;
|
|
6595
|
+
if (element === null || anchor === null) return;
|
|
6596
|
+
const place = () => {
|
|
6597
|
+
const rect = anchor.getBoundingClientRect();
|
|
6598
|
+
const width = element.offsetWidth;
|
|
6599
|
+
const height = element.offsetHeight;
|
|
6600
|
+
const gutter = 8;
|
|
6601
|
+
const left = Math.min(
|
|
6602
|
+
Math.max(gutter, rect.left + rect.width / 2 - width / 2),
|
|
6603
|
+
window.innerWidth - width - gutter
|
|
6604
|
+
);
|
|
6605
|
+
const fitsBelow = rect.bottom + gutter + height <= window.innerHeight;
|
|
6606
|
+
const fitsAbove = rect.top - gutter - height >= 0;
|
|
6607
|
+
const down = placement === "down" ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;
|
|
6608
|
+
setStyle(
|
|
6609
|
+
down ? { position: "fixed", left, top: rect.bottom + gutter } : { position: "fixed", left, bottom: window.innerHeight - rect.top + gutter }
|
|
6610
|
+
);
|
|
6611
|
+
};
|
|
6612
|
+
place();
|
|
6613
|
+
window.addEventListener("resize", place);
|
|
6614
|
+
return () => window.removeEventListener("resize", place);
|
|
6615
|
+
}, [placement]);
|
|
6616
|
+
useEffect3(() => {
|
|
6617
|
+
const onKey = (event) => {
|
|
6618
|
+
if (event.key === "Escape") onClose();
|
|
6619
|
+
};
|
|
6620
|
+
const onPointer = (event) => {
|
|
6621
|
+
const anchor = ref.current?.parentElement ?? null;
|
|
6622
|
+
if (anchor !== null && !anchor.contains(event.target)) onClose();
|
|
6623
|
+
};
|
|
6624
|
+
window.addEventListener("keydown", onKey);
|
|
6625
|
+
window.addEventListener("pointerdown", onPointer);
|
|
6626
|
+
return () => {
|
|
6627
|
+
window.removeEventListener("keydown", onKey);
|
|
6628
|
+
window.removeEventListener("pointerdown", onPointer);
|
|
6629
|
+
};
|
|
6630
|
+
}, [onClose]);
|
|
6631
|
+
return /* @__PURE__ */ jsx3("div", { ref, className: "mx-meet__popover", style, role: "dialog", "aria-label": label, children });
|
|
6632
|
+
}
|
|
6633
|
+
function MenuItem({
|
|
6634
|
+
onSelect,
|
|
6635
|
+
danger = false,
|
|
6636
|
+
children
|
|
6637
|
+
}) {
|
|
6638
|
+
return /* @__PURE__ */ jsx3(
|
|
6639
|
+
"button",
|
|
6640
|
+
{
|
|
6641
|
+
type: "button",
|
|
6642
|
+
role: "menuitem",
|
|
6643
|
+
className: danger ? "mx-meet__menu-item mx-meet__menu-item--danger" : "mx-meet__menu-item",
|
|
6644
|
+
onClick: onSelect,
|
|
6645
|
+
children
|
|
6646
|
+
}
|
|
6647
|
+
);
|
|
6648
|
+
}
|
|
6649
|
+
function Toast({ text: text3, failed, onDone }) {
|
|
6650
|
+
useEffect3(() => {
|
|
6651
|
+
const timer = setTimeout(onDone, failed ? 7e3 : 4e3);
|
|
6652
|
+
return () => clearTimeout(timer);
|
|
6653
|
+
}, [text3, failed, onDone]);
|
|
6654
|
+
return /* @__PURE__ */ jsxs2("div", { className: failed ? "mx-meet__toast mx-meet__toast--failed" : "mx-meet__toast", role: "status", children: [
|
|
6655
|
+
/* @__PURE__ */ jsx3("span", { children: text3 }),
|
|
6656
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
|
|
6657
|
+
] });
|
|
6658
|
+
}
|
|
6659
|
+
function MeetingOverlays() {
|
|
6660
|
+
const reactions = useReactions();
|
|
6661
|
+
const { notice, dismiss } = useModerationNotice();
|
|
6662
|
+
const room = useRoom();
|
|
6663
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
6664
|
+
/* @__PURE__ */ jsx3(ReactionOverlay, { reactions }),
|
|
6665
|
+
notice !== null && notice.kind !== "removed" ? /* @__PURE__ */ jsxs2("div", { className: "mx-meet__notice-bar", role: "alert", children: [
|
|
6666
|
+
/* @__PURE__ */ jsx3("span", { children: notice.text }),
|
|
6667
|
+
notice.kind === "ask-unmute" && room.local?.micEnabled !== true ? /* @__PURE__ */ jsx3(
|
|
6668
|
+
"button",
|
|
6669
|
+
{
|
|
6670
|
+
type: "button",
|
|
6671
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
6672
|
+
onClick: () => {
|
|
6673
|
+
dismiss();
|
|
6674
|
+
void room.toggleMicrophone();
|
|
6675
|
+
},
|
|
6676
|
+
children: "Unmute"
|
|
6677
|
+
}
|
|
6678
|
+
) : null,
|
|
6679
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismiss, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 14, height: 14 }) })
|
|
6680
|
+
] }) : null
|
|
6681
|
+
] });
|
|
6682
|
+
}
|
|
6683
|
+
function ShortcutsSheet({ onClose }) {
|
|
6684
|
+
useEffect3(() => {
|
|
6685
|
+
const onKey = (event) => {
|
|
6686
|
+
if (event.key === "Escape") onClose();
|
|
6687
|
+
};
|
|
6688
|
+
window.addEventListener("keydown", onKey);
|
|
6689
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
6690
|
+
}, [onClose]);
|
|
6691
|
+
return /* @__PURE__ */ jsx3("div", { className: "mx-meet__sheet-backdrop", onClick: onClose, children: /* @__PURE__ */ jsxs2(
|
|
6692
|
+
"div",
|
|
6693
|
+
{
|
|
6694
|
+
className: "mx-meet__sheet",
|
|
6695
|
+
role: "dialog",
|
|
6696
|
+
"aria-label": "Keyboard shortcuts",
|
|
6697
|
+
onClick: (event) => event.stopPropagation(),
|
|
6698
|
+
children: [
|
|
6699
|
+
/* @__PURE__ */ jsxs2("header", { className: "mx-meet__panel-head", children: [
|
|
6700
|
+
/* @__PURE__ */ jsx3("h2", { className: "mx-meet__panel-title", children: "Keyboard shortcuts" }),
|
|
6701
|
+
/* @__PURE__ */ jsx3(ControlButton, { label: "Close", onClick: onClose, children: /* @__PURE__ */ jsx3(CloseIcon, { width: 16, height: 16 }) })
|
|
6702
|
+
] }),
|
|
6703
|
+
/* @__PURE__ */ jsx3("dl", { className: "mx-meet__shortcuts", children: MEET_SHORTCUTS.map((shortcut) => /* @__PURE__ */ jsxs2("div", { className: "mx-meet__shortcut", children: [
|
|
6704
|
+
/* @__PURE__ */ jsx3("dt", { children: shortcut.label }),
|
|
6705
|
+
/* @__PURE__ */ jsx3("dd", { children: /* @__PURE__ */ jsx3("kbd", { className: "mx-meet__kbd", children: shortcut.keys }) })
|
|
6706
|
+
] }, shortcut.id)) })
|
|
6707
|
+
]
|
|
6708
|
+
}
|
|
6709
|
+
) });
|
|
6710
|
+
}
|
|
5630
6711
|
function AiControl({ active, onToggle }) {
|
|
5631
6712
|
const snapshot = useMeetSnapshot();
|
|
5632
6713
|
const meeting = snapshot?.meeting ?? null;
|
|
@@ -5646,117 +6727,641 @@ function PanelShell({
|
|
|
5646
6727
|
/* @__PURE__ */ jsx3("div", { className: "mx-meet__panel-body", children })
|
|
5647
6728
|
] });
|
|
5648
6729
|
}
|
|
5649
|
-
function ChatPanel({
|
|
6730
|
+
function ChatPanel({
|
|
6731
|
+
onClose,
|
|
6732
|
+
to = null,
|
|
6733
|
+
onTo
|
|
6734
|
+
}) {
|
|
5650
6735
|
const snapshot = useMeetSnapshot();
|
|
5651
6736
|
const room = useRoom();
|
|
5652
6737
|
const [draft, setDraft] = useState3("");
|
|
6738
|
+
const [recipient, setRecipient] = useState3(to);
|
|
6739
|
+
const listRef = useRef3(null);
|
|
5653
6740
|
const messages = snapshot?.chat ?? [];
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
6741
|
+
const local = snapshot?.localIdentity ?? null;
|
|
6742
|
+
const people = (snapshot?.participants ?? []).filter(
|
|
6743
|
+
(participant) => !participant.isLocal && !participant.isAgent
|
|
6744
|
+
);
|
|
6745
|
+
const recipientName = recipient === null ? null : people.find((p) => p.identity === recipient)?.displayName ?? null;
|
|
6746
|
+
useEffect3(() => setRecipient(to), [to]);
|
|
6747
|
+
useEffect3(() => {
|
|
6748
|
+
const list = listRef.current;
|
|
6749
|
+
if (list !== null) list.scrollTop = list.scrollHeight;
|
|
6750
|
+
}, [messages.length]);
|
|
6751
|
+
const choose = (next) => {
|
|
6752
|
+
setRecipient(next);
|
|
6753
|
+
onTo?.(next);
|
|
6754
|
+
};
|
|
6755
|
+
return /* @__PURE__ */ jsxs2(PanelShell, { title: "Chat", onClose, children: [
|
|
6756
|
+
/* @__PURE__ */ jsxs2("ul", { className: "mx-meet__chat", ref: listRef, children: [
|
|
6757
|
+
snapshot?.chatHistoryError != null ? /* @__PURE__ */ jsx3("li", { className: "mx-meet__panel-note", children: snapshot.chatHistoryError }) : null,
|
|
6758
|
+
snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ jsx3("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 && snapshot?.chatHistoryError == null ? /* @__PURE__ */ jsx3("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,
|
|
6759
|
+
messages.map((message) => /* @__PURE__ */ jsx3(ChatLine, { message, local, people }, message.id))
|
|
6760
|
+
] }),
|
|
5660
6761
|
/* @__PURE__ */ jsxs2(
|
|
5661
6762
|
"form",
|
|
5662
6763
|
{
|
|
5663
|
-
className: "mx-meet__composer",
|
|
6764
|
+
className: "mx-meet__composer mx-meet__composer--chat",
|
|
5664
6765
|
onSubmit: (event) => {
|
|
5665
6766
|
event.preventDefault();
|
|
5666
6767
|
const text3 = draft;
|
|
6768
|
+
if (text3.trim().length === 0) return;
|
|
5667
6769
|
setDraft("");
|
|
5668
|
-
void room.sendChat(text3);
|
|
6770
|
+
if (recipient === null) void room.sendChat(text3).catch(() => void 0);
|
|
6771
|
+
else void room.sendPrivateChat(recipient, text3);
|
|
5669
6772
|
},
|
|
5670
6773
|
children: [
|
|
5671
|
-
/* @__PURE__ */
|
|
5672
|
-
"
|
|
6774
|
+
people.length > 0 ? /* @__PURE__ */ jsxs2(
|
|
6775
|
+
"select",
|
|
5673
6776
|
{
|
|
5674
|
-
className: "mx-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
6777
|
+
className: "mx-meet__select mx-meet__select--compact",
|
|
6778
|
+
"aria-label": "Send to",
|
|
6779
|
+
value: recipient ?? "",
|
|
6780
|
+
onChange: (event) => choose(event.target.value === "" ? null : event.target.value),
|
|
6781
|
+
children: [
|
|
6782
|
+
/* @__PURE__ */ jsx3("option", { value: "", children: "Everyone" }),
|
|
6783
|
+
people.map((participant) => /* @__PURE__ */ jsxs2("option", { value: participant.identity, children: [
|
|
6784
|
+
participant.displayName,
|
|
6785
|
+
" (private)"
|
|
6786
|
+
] }, participant.identity))
|
|
6787
|
+
]
|
|
5679
6788
|
}
|
|
5680
|
-
),
|
|
5681
|
-
/* @__PURE__ */
|
|
6789
|
+
) : null,
|
|
6790
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__composer-row", children: [
|
|
6791
|
+
/* @__PURE__ */ jsx3(
|
|
6792
|
+
"input",
|
|
6793
|
+
{
|
|
6794
|
+
className: "mx-meet__input",
|
|
6795
|
+
value: draft,
|
|
6796
|
+
onChange: (event) => setDraft(event.target.value),
|
|
6797
|
+
placeholder: recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
6798
|
+
"aria-label": recipientName === null ? "Message everyone" : `Message ${recipientName} privately`,
|
|
6799
|
+
maxLength: 4e3
|
|
6800
|
+
}
|
|
6801
|
+
),
|
|
6802
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: draft.trim().length === 0, children: "Send" })
|
|
6803
|
+
] }),
|
|
6804
|
+
recipientName !== null ? /* @__PURE__ */ jsxs2("p", { className: "mx-meet__panel-note", children: [
|
|
6805
|
+
"Only ",
|
|
6806
|
+
recipientName,
|
|
6807
|
+
" sees this. Private messages are not saved."
|
|
6808
|
+
] }) : null
|
|
5682
6809
|
]
|
|
5683
6810
|
}
|
|
5684
6811
|
)
|
|
5685
6812
|
] });
|
|
5686
6813
|
}
|
|
5687
|
-
function
|
|
6814
|
+
function ChatLine({
|
|
6815
|
+
message,
|
|
6816
|
+
local,
|
|
6817
|
+
people
|
|
6818
|
+
}) {
|
|
6819
|
+
const mine = message.identity === local;
|
|
6820
|
+
const privateTo = message.to ?? null;
|
|
6821
|
+
const counterpart = privateTo === null ? null : mine ? people.find((p) => p.identity === privateTo)?.displayName ?? "them" : "you";
|
|
6822
|
+
return /* @__PURE__ */ jsxs2(
|
|
6823
|
+
"li",
|
|
6824
|
+
{
|
|
6825
|
+
className: [
|
|
6826
|
+
"mx-meet__chat-line",
|
|
6827
|
+
privateTo !== null ? "mx-meet__chat-line--private" : "",
|
|
6828
|
+
message.failed !== void 0 ? "mx-meet__chat-line--failed" : ""
|
|
6829
|
+
].filter(Boolean).join(" "),
|
|
6830
|
+
children: [
|
|
6831
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__chat-meta", children: [
|
|
6832
|
+
/* @__PURE__ */ jsx3("strong", { children: mine ? "You" : message.displayName }),
|
|
6833
|
+
counterpart !== null ? /* @__PURE__ */ jsxs2("span", { className: "mx-meet__chat-private", children: [
|
|
6834
|
+
"to ",
|
|
6835
|
+
counterpart,
|
|
6836
|
+
" \xB7 private"
|
|
6837
|
+
] }) : null,
|
|
6838
|
+
/* @__PURE__ */ jsx3("time", { dateTime: message.at, children: clockOf(message.at) })
|
|
6839
|
+
] }),
|
|
6840
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet__chat-text", children: message.text }),
|
|
6841
|
+
message.failed !== void 0 ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__chat-failed", children: message.failed }) : privateTo === null && message.saved === false ? /* @__PURE__ */ jsx3("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
|
|
6842
|
+
]
|
|
6843
|
+
}
|
|
6844
|
+
);
|
|
6845
|
+
}
|
|
6846
|
+
function PeoplePanel({
|
|
6847
|
+
onClose,
|
|
6848
|
+
onMessage
|
|
6849
|
+
}) {
|
|
5688
6850
|
const snapshot = useMeetSnapshot();
|
|
5689
6851
|
const controls = useHostControls();
|
|
6852
|
+
const queue = useHandQueue();
|
|
6853
|
+
const [query, setQuery] = useState3("");
|
|
6854
|
+
const [menuFor, setMenuFor] = useState3(null);
|
|
5690
6855
|
const myRole = useRole();
|
|
5691
6856
|
const local = snapshot?.localIdentity ?? null;
|
|
5692
|
-
|
|
6857
|
+
const everyone = snapshot?.participants ?? [];
|
|
6858
|
+
const needle = query.trim().toLowerCase();
|
|
6859
|
+
const shown = needle.length === 0 ? everyone : everyone.filter((participant) => participant.displayName.toLowerCase().includes(needle));
|
|
6860
|
+
return /* @__PURE__ */ jsxs2(PanelShell, { title: `People (${everyone.length})`, onClose, children: [
|
|
5693
6861
|
/* @__PURE__ */ jsx3(LobbyPanel, {}),
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
6862
|
+
queue.length > 0 ? /* @__PURE__ */ jsxs2("section", { className: "mx-meet__section", "aria-label": "Raised hands", children: [
|
|
6863
|
+
/* @__PURE__ */ jsxs2("header", { className: "mx-meet__section-head", children: [
|
|
6864
|
+
/* @__PURE__ */ jsxs2("h3", { className: "mx-meet__section-title", children: [
|
|
6865
|
+
"Raised hands \xB7 ",
|
|
6866
|
+
queue.length
|
|
6867
|
+
] }),
|
|
6868
|
+
controls.isHost ? /* @__PURE__ */ jsx3(
|
|
6869
|
+
"button",
|
|
5701
6870
|
{
|
|
5702
|
-
|
|
6871
|
+
type: "button",
|
|
6872
|
+
className: "mx-meet__link",
|
|
6873
|
+
onClick: () => void controls.run({ action: "lower_all_hands" }),
|
|
6874
|
+
children: "Lower all"
|
|
5703
6875
|
}
|
|
5704
|
-
)
|
|
5705
|
-
isNoteTaker(participant) ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null
|
|
6876
|
+
) : null
|
|
5706
6877
|
] }),
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
6878
|
+
/* @__PURE__ */ jsx3("ol", { className: "mx-meet__queue", children: queue.map((participant) => /* @__PURE__ */ jsxs2("li", { className: "mx-meet__person", children: [
|
|
6879
|
+
/* @__PURE__ */ jsx3(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 28 }),
|
|
6880
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__person-name", children: [
|
|
6881
|
+
participant.displayName,
|
|
6882
|
+
participant.identity === local ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__you", children: " (you)" }) : null
|
|
6883
|
+
] }),
|
|
6884
|
+
controls.isHost || participant.identity === local ? /* @__PURE__ */ jsx3(
|
|
6885
|
+
"button",
|
|
6886
|
+
{
|
|
6887
|
+
type: "button",
|
|
6888
|
+
className: "mx-meet__link",
|
|
6889
|
+
onClick: () => void controls.run({ action: "lower_hand", identity: participant.identity }),
|
|
6890
|
+
children: "Lower"
|
|
6891
|
+
}
|
|
6892
|
+
) : null
|
|
6893
|
+
] }, participant.identity)) })
|
|
6894
|
+
] }) : null,
|
|
6895
|
+
controls.isHost && everyone.length > 1 ? /* @__PURE__ */ jsxs2("div", { className: "mx-meet__bulk", children: [
|
|
6896
|
+
/* @__PURE__ */ jsxs2("button", { type: "button", className: "mx-meet__chip", onClick: () => void controls.run({ action: "mute_all" }), children: [
|
|
6897
|
+
/* @__PURE__ */ jsx3(MicOffIcon, { width: 14, height: 14 }),
|
|
6898
|
+
" Mute all"
|
|
6899
|
+
] }),
|
|
6900
|
+
/* @__PURE__ */ jsxs2(
|
|
6901
|
+
"button",
|
|
6902
|
+
{
|
|
6903
|
+
type: "button",
|
|
6904
|
+
className: "mx-meet__chip",
|
|
6905
|
+
onClick: () => void controls.run({ action: "ask_all_unmute" }),
|
|
6906
|
+
children: [
|
|
6907
|
+
/* @__PURE__ */ jsx3(MicIcon, { width: 14, height: 14 }),
|
|
6908
|
+
" Ask all to unmute"
|
|
6909
|
+
]
|
|
6910
|
+
}
|
|
6911
|
+
)
|
|
6912
|
+
] }) : null,
|
|
6913
|
+
everyone.length > 4 ? /* @__PURE__ */ jsxs2("label", { className: "mx-meet__search", children: [
|
|
6914
|
+
/* @__PURE__ */ jsx3(SearchIcon, { width: 16, height: 16 }),
|
|
6915
|
+
/* @__PURE__ */ jsx3(
|
|
6916
|
+
"input",
|
|
6917
|
+
{
|
|
6918
|
+
className: "mx-meet__input mx-meet__input--bare",
|
|
6919
|
+
value: query,
|
|
6920
|
+
onChange: (event) => setQuery(event.target.value),
|
|
6921
|
+
placeholder: "Search people",
|
|
6922
|
+
"aria-label": "Search people"
|
|
6923
|
+
}
|
|
6924
|
+
)
|
|
6925
|
+
] }) : null,
|
|
6926
|
+
controls.notice !== null ? /* @__PURE__ */ jsx3("p", { className: controls.notice.failed ? "mx-meet__error" : "mx-meet__notice", role: "status", children: controls.notice.text }) : null,
|
|
6927
|
+
/* @__PURE__ */ jsxs2("ul", { className: "mx-meet__people", children: [
|
|
6928
|
+
shown.map((participant) => /* @__PURE__ */ jsxs2("li", { className: "mx-meet__person", children: [
|
|
6929
|
+
/* @__PURE__ */ jsx3(Avatar, { name: participant.displayName, url: participant.avatarUrl, size: 32 }),
|
|
6930
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__person-name", children: [
|
|
6931
|
+
participant.displayName,
|
|
6932
|
+
participant.identity === local ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__you", children: " (you)" }) : null,
|
|
6933
|
+
/* @__PURE__ */ jsx3(RoleBadge, { role: participant.identity === local ? myRole : participant.role }),
|
|
6934
|
+
isNoteTaker(participant) ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Meeting assistant" }) : null,
|
|
6935
|
+
snapshot?.spotlight === participant.identity ? /* @__PURE__ */ jsx3("span", { className: "mx-meet__badge", children: "Spotlight" }) : null
|
|
6936
|
+
] }),
|
|
6937
|
+
participant.handRaised ? /* @__PURE__ */ jsx3(HandIcon, { width: 14, height: 14 }) : null,
|
|
6938
|
+
!participant.micEnabled && !isNoteTaker(participant) ? /* @__PURE__ */ jsx3(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
6939
|
+
/* @__PURE__ */ jsx3(QualityBars, { quality: participant.quality }),
|
|
6940
|
+
/* @__PURE__ */ jsxs2("span", { className: "mx-meet__anchor", children: [
|
|
6941
|
+
/* @__PURE__ */ jsx3(
|
|
6942
|
+
ControlButton,
|
|
6943
|
+
{
|
|
6944
|
+
label: `More for ${participant.displayName}`,
|
|
6945
|
+
active: menuFor === participant.identity,
|
|
6946
|
+
onClick: () => setMenuFor(menuFor === participant.identity ? null : participant.identity),
|
|
6947
|
+
children: /* @__PURE__ */ jsx3(MoreIcon, { width: 16, height: 16 })
|
|
6948
|
+
}
|
|
6949
|
+
),
|
|
6950
|
+
menuFor === participant.identity ? /* @__PURE__ */ jsx3(
|
|
6951
|
+
PersonMenu,
|
|
6952
|
+
{
|
|
6953
|
+
participant,
|
|
6954
|
+
placement: "down",
|
|
6955
|
+
onClose: () => setMenuFor(null),
|
|
6956
|
+
...onMessage !== void 0 ? { onMessage } : {}
|
|
6957
|
+
}
|
|
6958
|
+
) : null
|
|
6959
|
+
] })
|
|
6960
|
+
] }, participant.identity)),
|
|
6961
|
+
shown.length === 0 ? /* @__PURE__ */ jsxs2("li", { className: "mx-meet__panel-note", children: [
|
|
6962
|
+
"Nobody matches \u201C",
|
|
6963
|
+
query,
|
|
6964
|
+
"\u201D."
|
|
6965
|
+
] }) : null
|
|
6966
|
+
] }),
|
|
6967
|
+
snapshot?.meeting !== null && snapshot?.meeting !== void 0 ? /* @__PURE__ */ jsx3(GuestRecordAccessControl, { meeting: snapshot.meeting }) : null
|
|
5711
6968
|
] });
|
|
5712
6969
|
}
|
|
5713
|
-
function
|
|
6970
|
+
function PersonMenu({
|
|
5714
6971
|
participant,
|
|
5715
|
-
|
|
6972
|
+
onClose,
|
|
6973
|
+
onMessage,
|
|
6974
|
+
placement = "down"
|
|
5716
6975
|
}) {
|
|
5717
|
-
const
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
6976
|
+
const host = useMeetHost();
|
|
6977
|
+
const snapshot = useMeetSnapshot();
|
|
6978
|
+
const controls = useHostControls();
|
|
6979
|
+
const [mode, setMode] = useState3("menu");
|
|
6980
|
+
const [text3, setText] = useState3("");
|
|
6981
|
+
const self = participant.identity === snapshot?.localIdentity;
|
|
6982
|
+
const moderate = controls.canModerate(participant);
|
|
6983
|
+
const pinned = snapshot?.pinned === participant.identity;
|
|
6984
|
+
const spotlit = snapshot?.spotlight === participant.identity;
|
|
6985
|
+
const hasAccount = participant.userId !== null;
|
|
6986
|
+
const act = (request) => {
|
|
6987
|
+
void controls.run(request).then((result) => {
|
|
6988
|
+
if (result !== null) onClose();
|
|
6989
|
+
});
|
|
6990
|
+
};
|
|
6991
|
+
if (mode === "rename" || mode === "report") {
|
|
6992
|
+
const renaming = mode === "rename";
|
|
6993
|
+
return /* @__PURE__ */ jsx3(Popover, { label: renaming ? "Rename" : "Report", onClose, placement, children: /* @__PURE__ */ jsxs2(
|
|
6994
|
+
"form",
|
|
6995
|
+
{
|
|
6996
|
+
className: "mx-meet__menu-form",
|
|
6997
|
+
onSubmit: (event) => {
|
|
6998
|
+
event.preventDefault();
|
|
6999
|
+
if (text3.trim().length === 0) return;
|
|
7000
|
+
act(
|
|
7001
|
+
renaming ? { action: "rename", identity: participant.identity, name: text3.trim() } : { action: "report", identity: participant.identity, reason: text3.trim() }
|
|
7002
|
+
);
|
|
7003
|
+
},
|
|
7004
|
+
children: [
|
|
7005
|
+
/* @__PURE__ */ jsxs2("label", { className: "mx-meet__field", children: [
|
|
7006
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet__field-label", children: renaming ? "Name shown in this meeting" : `What did ${participant.displayName} do?` }),
|
|
7007
|
+
renaming ? /* @__PURE__ */ jsx3(
|
|
7008
|
+
"input",
|
|
7009
|
+
{
|
|
7010
|
+
className: "mx-meet__input",
|
|
7011
|
+
autoFocus: true,
|
|
7012
|
+
maxLength: 80,
|
|
7013
|
+
value: text3,
|
|
7014
|
+
onChange: (event) => setText(event.target.value)
|
|
7015
|
+
}
|
|
7016
|
+
) : /* @__PURE__ */ jsx3(
|
|
7017
|
+
"textarea",
|
|
7018
|
+
{
|
|
7019
|
+
className: "mx-meet__input",
|
|
7020
|
+
autoFocus: true,
|
|
7021
|
+
rows: 3,
|
|
7022
|
+
maxLength: 1e3,
|
|
7023
|
+
value: text3,
|
|
7024
|
+
onChange: (event) => setText(event.target.value)
|
|
7025
|
+
}
|
|
7026
|
+
)
|
|
7027
|
+
] }),
|
|
7028
|
+
controls.notice?.failed === true ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__error", children: controls.notice.text }) : null,
|
|
7029
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7030
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Back" }),
|
|
7031
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", className: "mx-meet__send", disabled: text3.trim().length === 0 || controls.busy, children: renaming ? "Rename" : "Report" })
|
|
7032
|
+
] })
|
|
7033
|
+
]
|
|
7034
|
+
}
|
|
7035
|
+
) });
|
|
7036
|
+
}
|
|
7037
|
+
if (mode === "remove" || mode === "transfer") {
|
|
7038
|
+
const removing = mode === "remove";
|
|
7039
|
+
return /* @__PURE__ */ jsx3(Popover, { label: removing ? "Remove" : "Make host", onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-form", children: [
|
|
7040
|
+
/* @__PURE__ */ jsx3("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.` }),
|
|
7041
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu-actions", children: [
|
|
7042
|
+
/* @__PURE__ */ jsx3("button", { type: "button", className: "mx-meet__link", onClick: () => setMode("menu"), children: "Cancel" }),
|
|
7043
|
+
/* @__PURE__ */ jsx3(
|
|
7044
|
+
"button",
|
|
7045
|
+
{
|
|
7046
|
+
type: "button",
|
|
7047
|
+
className: removing ? "mx-meet__send mx-meet__send--danger" : "mx-meet__send",
|
|
7048
|
+
disabled: controls.busy,
|
|
7049
|
+
onClick: () => act({ action: removing ? "remove" : "transfer_host", identity: participant.identity }),
|
|
7050
|
+
children: removing ? "Remove" : "Make host"
|
|
7051
|
+
}
|
|
7052
|
+
)
|
|
7053
|
+
] })
|
|
7054
|
+
] }) });
|
|
7055
|
+
}
|
|
7056
|
+
return /* @__PURE__ */ jsx3(Popover, { label: `Options for ${participant.displayName}`, onClose, placement, children: /* @__PURE__ */ jsxs2("div", { className: "mx-meet__menu", role: "menu", children: [
|
|
7057
|
+
/* @__PURE__ */ jsxs2(
|
|
7058
|
+
MenuItem,
|
|
7059
|
+
{
|
|
7060
|
+
onSelect: () => {
|
|
7061
|
+
host?.engine.pin(pinned ? null : participant.identity);
|
|
7062
|
+
onClose();
|
|
7063
|
+
},
|
|
7064
|
+
children: [
|
|
7065
|
+
/* @__PURE__ */ jsx3(PinIcon, { width: 16, height: 16 }),
|
|
7066
|
+
" ",
|
|
7067
|
+
pinned ? "Unpin" : "Pin for me"
|
|
7068
|
+
]
|
|
7069
|
+
}
|
|
7070
|
+
),
|
|
7071
|
+
!self && !participant.isAgent && onMessage !== void 0 ? /* @__PURE__ */ jsxs2(
|
|
7072
|
+
MenuItem,
|
|
7073
|
+
{
|
|
7074
|
+
onSelect: () => {
|
|
7075
|
+
onMessage(participant.identity);
|
|
7076
|
+
onClose();
|
|
7077
|
+
},
|
|
7078
|
+
children: [
|
|
7079
|
+
/* @__PURE__ */ jsx3(ChatIcon, { width: 16, height: 16 }),
|
|
7080
|
+
" Message privately"
|
|
7081
|
+
]
|
|
7082
|
+
}
|
|
7083
|
+
) : null,
|
|
7084
|
+
controls.isHost ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "spotlight", identity: spotlit ? null : participant.identity }), children: [
|
|
7085
|
+
/* @__PURE__ */ jsx3(SpotlightIcon, { width: 16, height: 16 }),
|
|
7086
|
+
" ",
|
|
7087
|
+
spotlit ? "Remove spotlight" : "Spotlight for everyone"
|
|
7088
|
+
] }) : null,
|
|
7089
|
+
moderate ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
7090
|
+
/* @__PURE__ */ jsx3("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
7091
|
+
participant.micEnabled ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "mute", identity: participant.identity }), children: [
|
|
7092
|
+
/* @__PURE__ */ jsx3(MicOffIcon, { width: 16, height: 16 }),
|
|
7093
|
+
" Mute"
|
|
7094
|
+
] }) : !participant.isAgent ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "ask_unmute", identity: participant.identity }), children: [
|
|
7095
|
+
/* @__PURE__ */ jsx3(MicIcon, { width: 16, height: 16 }),
|
|
7096
|
+
" Ask to unmute"
|
|
7097
|
+
] }) : null,
|
|
7098
|
+
participant.cameraEnabled ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "stop_video", identity: participant.identity }), children: [
|
|
7099
|
+
/* @__PURE__ */ jsx3(CameraOffIcon, { width: 16, height: 16 }),
|
|
7100
|
+
" Stop video"
|
|
7101
|
+
] }) : null,
|
|
7102
|
+
participant.screenSharing ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "stop_screen", identity: participant.identity }), children: [
|
|
7103
|
+
/* @__PURE__ */ jsx3(ScreenShareIcon, { width: 16, height: 16 }),
|
|
7104
|
+
" Stop screen share"
|
|
7105
|
+
] }) : null,
|
|
7106
|
+
participant.handRaised ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "lower_hand", identity: participant.identity }), children: [
|
|
7107
|
+
/* @__PURE__ */ jsx3(HandIcon, { width: 16, height: 16 }),
|
|
7108
|
+
" Lower hand"
|
|
7109
|
+
] }) : null,
|
|
7110
|
+
hasAccount && !participant.isAgent && participant.role !== "cohost" ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "make_cohost", identity: participant.identity }), children: [
|
|
7111
|
+
/* @__PURE__ */ jsx3(PeopleIcon, { width: 16, height: 16 }),
|
|
7112
|
+
" Make co-host"
|
|
7113
|
+
] }) : null,
|
|
7114
|
+
!participant.isAgent ? /* @__PURE__ */ jsxs2(
|
|
7115
|
+
MenuItem,
|
|
5727
7116
|
{
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
setConfirming(false);
|
|
5732
|
-
void controls.remove(participant);
|
|
7117
|
+
onSelect: () => {
|
|
7118
|
+
setText(participant.displayName);
|
|
7119
|
+
setMode("rename");
|
|
5733
7120
|
},
|
|
5734
|
-
children:
|
|
7121
|
+
children: [
|
|
7122
|
+
/* @__PURE__ */ jsx3(PencilIcon, { width: 16, height: 16 }),
|
|
7123
|
+
" Rename"
|
|
7124
|
+
]
|
|
5735
7125
|
}
|
|
5736
|
-
)
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
7126
|
+
) : null
|
|
7127
|
+
] }) : null,
|
|
7128
|
+
controls.isOwner && participant.role === "cohost" ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => act({ action: "revoke_cohost", identity: participant.identity }), children: [
|
|
7129
|
+
/* @__PURE__ */ jsx3(PeopleIcon, { width: 16, height: 16 }),
|
|
7130
|
+
" Remove co-host"
|
|
7131
|
+
] }) : null,
|
|
7132
|
+
controls.isOwner && !self && hasAccount && !participant.isAgent ? /* @__PURE__ */ jsxs2(MenuItem, { onSelect: () => setMode("transfer"), children: [
|
|
7133
|
+
/* @__PURE__ */ jsx3(SpotlightIcon, { width: 16, height: 16 }),
|
|
7134
|
+
" Make host"
|
|
7135
|
+
] }) : null,
|
|
7136
|
+
self ? /* @__PURE__ */ jsxs2(
|
|
7137
|
+
MenuItem,
|
|
5743
7138
|
{
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
7139
|
+
onSelect: () => {
|
|
7140
|
+
setText(participant.displayName);
|
|
7141
|
+
setMode("rename");
|
|
7142
|
+
},
|
|
7143
|
+
children: [
|
|
7144
|
+
/* @__PURE__ */ jsx3(PencilIcon, { width: 16, height: 16 }),
|
|
7145
|
+
" Rename yourself"
|
|
7146
|
+
]
|
|
5747
7147
|
}
|
|
5748
7148
|
) : null,
|
|
7149
|
+
!self && !participant.isAgent ? /* @__PURE__ */ jsxs2(
|
|
7150
|
+
MenuItem,
|
|
7151
|
+
{
|
|
7152
|
+
onSelect: () => {
|
|
7153
|
+
setText("");
|
|
7154
|
+
setMode("report");
|
|
7155
|
+
},
|
|
7156
|
+
children: [
|
|
7157
|
+
/* @__PURE__ */ jsx3(FlagIcon, { width: 16, height: 16 }),
|
|
7158
|
+
" Report"
|
|
7159
|
+
]
|
|
7160
|
+
}
|
|
7161
|
+
) : null,
|
|
7162
|
+
moderate && !participant.isAgent ? /* @__PURE__ */ jsxs2(MenuItem, { danger: true, onSelect: () => setMode("remove"), children: [
|
|
7163
|
+
/* @__PURE__ */ jsx3(CloseIcon, { width: 16, height: 16 }),
|
|
7164
|
+
" Remove from meeting"
|
|
7165
|
+
] }) : null
|
|
7166
|
+
] }) });
|
|
7167
|
+
}
|
|
7168
|
+
function SettingsPanel({ onClose }) {
|
|
7169
|
+
const host = useRequiredMeetHost();
|
|
7170
|
+
return /* @__PURE__ */ jsxs2(PanelShell, { title: "Audio, video and effects", onClose, children: [
|
|
5749
7171
|
/* @__PURE__ */ jsx3(
|
|
5750
|
-
|
|
7172
|
+
DeviceSettings,
|
|
5751
7173
|
{
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
7174
|
+
onPick: (kind, deviceId2) => {
|
|
7175
|
+
host.devices.select(kind, deviceId2);
|
|
7176
|
+
void host.engine.switchDevice(kind, deviceId2);
|
|
7177
|
+
}
|
|
5756
7178
|
}
|
|
5757
|
-
)
|
|
7179
|
+
),
|
|
7180
|
+
/* @__PURE__ */ jsx3(EffectsSettings, {})
|
|
7181
|
+
] });
|
|
7182
|
+
}
|
|
7183
|
+
function DeviceSettings({
|
|
7184
|
+
onPick
|
|
7185
|
+
}) {
|
|
7186
|
+
const host = useRequiredMeetHost();
|
|
7187
|
+
const devices = useDeviceSnapshot();
|
|
7188
|
+
const microphone = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7189
|
+
const speaker = devices?.selected.audiooutput ?? host.devices.resolve("audiooutput");
|
|
7190
|
+
const stream = useMicrophoneStream(microphone);
|
|
7191
|
+
const level = useAudioLevel(stream);
|
|
7192
|
+
const [testing, setTesting] = useState3(false);
|
|
7193
|
+
useEffect3(() => {
|
|
7194
|
+
void host.devices.refresh();
|
|
7195
|
+
}, [host]);
|
|
7196
|
+
return /* @__PURE__ */ jsxs2("section", { className: "mx-meet__section", "aria-label": "Devices", children: [
|
|
7197
|
+
/* @__PURE__ */ jsx3(DevicePicker, { kind: "audioinput", label: "Microphone", onPick }),
|
|
7198
|
+
/* @__PURE__ */ jsx3("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__ */ jsx3("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }),
|
|
7199
|
+
/* @__PURE__ */ jsx3(DevicePicker, { kind: "audiooutput", label: "Speaker", onPick }),
|
|
7200
|
+
/* @__PURE__ */ jsxs2(
|
|
7201
|
+
"button",
|
|
7202
|
+
{
|
|
7203
|
+
type: "button",
|
|
7204
|
+
className: "mx-meet__chip",
|
|
7205
|
+
disabled: testing,
|
|
7206
|
+
onClick: () => {
|
|
7207
|
+
setTesting(true);
|
|
7208
|
+
void playTestSound(speaker).finally(() => setTesting(false));
|
|
7209
|
+
},
|
|
7210
|
+
children: [
|
|
7211
|
+
/* @__PURE__ */ jsx3(SpeakerIcon, { width: 14, height: 14 }),
|
|
7212
|
+
" ",
|
|
7213
|
+
testing ? "Playing\u2026" : "Test speaker"
|
|
7214
|
+
]
|
|
7215
|
+
}
|
|
7216
|
+
),
|
|
7217
|
+
/* @__PURE__ */ jsx3(DevicePicker, { kind: "videoinput", label: "Camera", onPick })
|
|
5758
7218
|
] });
|
|
5759
7219
|
}
|
|
7220
|
+
function EffectsSettings({ preview }) {
|
|
7221
|
+
const effects = useMediaEffects();
|
|
7222
|
+
const [error, setError] = useState3(null);
|
|
7223
|
+
const fileRef = useRef3(null);
|
|
7224
|
+
const supported = effects.backgroundsSupported || preview != null || videoEffectsSupported();
|
|
7225
|
+
const apply = (next) => {
|
|
7226
|
+
setError(null);
|
|
7227
|
+
void effects.set(next);
|
|
7228
|
+
if (preview != null) {
|
|
7229
|
+
const merged = { ...effects.effects, ...next };
|
|
7230
|
+
void preview.setEffect(toVideoEffect(merged)).catch(
|
|
7231
|
+
() => setError("This browser could not run the background effect.")
|
|
7232
|
+
);
|
|
7233
|
+
}
|
|
7234
|
+
};
|
|
7235
|
+
return /* @__PURE__ */ jsxs2("section", { className: "mx-meet__section", "aria-label": "Effects", children: [
|
|
7236
|
+
/* @__PURE__ */ jsxs2("label", { className: "mx-meet__toggle", children: [
|
|
7237
|
+
/* @__PURE__ */ jsx3(
|
|
7238
|
+
"input",
|
|
7239
|
+
{
|
|
7240
|
+
type: "checkbox",
|
|
7241
|
+
checked: effects.effects.noiseSuppression,
|
|
7242
|
+
onChange: (event) => apply({ noiseSuppression: event.target.checked })
|
|
7243
|
+
}
|
|
7244
|
+
),
|
|
7245
|
+
"Noise suppression",
|
|
7246
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet__hint", children: "Filters keyboards, fans and background chatter." })
|
|
7247
|
+
] }),
|
|
7248
|
+
supported ? /* @__PURE__ */ jsxs2("div", { className: "mx-meet__field", children: [
|
|
7249
|
+
/* @__PURE__ */ jsx3("span", { className: "mx-meet__field-label", children: "Background" }),
|
|
7250
|
+
/* @__PURE__ */ jsx3("div", { className: "mx-meet__pills", role: "radiogroup", "aria-label": "Background", children: ["none", "blur", "image"].map((kind) => /* @__PURE__ */ jsx3(
|
|
7251
|
+
"button",
|
|
7252
|
+
{
|
|
7253
|
+
type: "button",
|
|
7254
|
+
role: "radio",
|
|
7255
|
+
"aria-checked": effects.effects.background === kind,
|
|
7256
|
+
className: effects.effects.background === kind ? "mx-meet__pill mx-meet__pill--on" : "mx-meet__pill",
|
|
7257
|
+
onClick: () => {
|
|
7258
|
+
if (kind === "image") {
|
|
7259
|
+
fileRef.current?.click();
|
|
7260
|
+
return;
|
|
7261
|
+
}
|
|
7262
|
+
apply({ background: kind });
|
|
7263
|
+
},
|
|
7264
|
+
children: kind === "none" ? "None" : kind === "blur" ? "Blur" : "Picture\u2026"
|
|
7265
|
+
},
|
|
7266
|
+
kind
|
|
7267
|
+
)) }),
|
|
7268
|
+
/* @__PURE__ */ jsx3(
|
|
7269
|
+
"input",
|
|
7270
|
+
{
|
|
7271
|
+
ref: fileRef,
|
|
7272
|
+
type: "file",
|
|
7273
|
+
accept: "image/*",
|
|
7274
|
+
hidden: true,
|
|
7275
|
+
onChange: (event) => {
|
|
7276
|
+
const file = event.target.files?.[0];
|
|
7277
|
+
event.target.value = "";
|
|
7278
|
+
if (file === void 0) return;
|
|
7279
|
+
void shrinkImage(file).then((url) => apply({ background: "image", backgroundImage: url })).catch(() => setError("That picture could not be read. Try a JPEG or PNG."));
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
),
|
|
7283
|
+
error !== null ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__error", children: error }) : null
|
|
7284
|
+
] }) : null
|
|
7285
|
+
] });
|
|
7286
|
+
}
|
|
7287
|
+
function toVideoEffect(effects) {
|
|
7288
|
+
if (effects.background === "blur") return { kind: "blur" };
|
|
7289
|
+
if (effects.background === "image" && effects.backgroundImage !== null) {
|
|
7290
|
+
return { kind: "image", imageUrl: effects.backgroundImage };
|
|
7291
|
+
}
|
|
7292
|
+
return { kind: "none" };
|
|
7293
|
+
}
|
|
7294
|
+
async function shrinkImage(file) {
|
|
7295
|
+
const url = URL.createObjectURL(file);
|
|
7296
|
+
try {
|
|
7297
|
+
const image = new Image();
|
|
7298
|
+
image.src = url;
|
|
7299
|
+
await image.decode();
|
|
7300
|
+
const scale = Math.min(1, 1280 / image.naturalWidth);
|
|
7301
|
+
const canvas = document.createElement("canvas");
|
|
7302
|
+
canvas.width = Math.round(image.naturalWidth * scale);
|
|
7303
|
+
canvas.height = Math.round(image.naturalHeight * scale);
|
|
7304
|
+
const context = canvas.getContext("2d");
|
|
7305
|
+
if (context === null) throw new Error("no 2d context");
|
|
7306
|
+
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
|
7307
|
+
return canvas.toDataURL("image/jpeg", 0.85);
|
|
7308
|
+
} finally {
|
|
7309
|
+
URL.revokeObjectURL(url);
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
async function playTestSound(sinkId) {
|
|
7313
|
+
const Context = globalThis.AudioContext;
|
|
7314
|
+
if (Context === void 0) return;
|
|
7315
|
+
const context = new Context();
|
|
7316
|
+
const destination = context.createMediaStreamDestination();
|
|
7317
|
+
const gain = context.createGain();
|
|
7318
|
+
gain.gain.value = 0.2;
|
|
7319
|
+
gain.connect(destination);
|
|
7320
|
+
for (const [frequency, start] of [
|
|
7321
|
+
[660, 0],
|
|
7322
|
+
[880, 0.25]
|
|
7323
|
+
]) {
|
|
7324
|
+
const oscillator = context.createOscillator();
|
|
7325
|
+
oscillator.frequency.value = frequency;
|
|
7326
|
+
oscillator.connect(gain);
|
|
7327
|
+
oscillator.start(context.currentTime + start);
|
|
7328
|
+
oscillator.stop(context.currentTime + start + 0.22);
|
|
7329
|
+
}
|
|
7330
|
+
const audio = new Audio();
|
|
7331
|
+
audio.srcObject = destination.stream;
|
|
7332
|
+
const sinkable = audio;
|
|
7333
|
+
if (sinkId !== null && sinkId !== "default" && sinkable.setSinkId !== void 0) {
|
|
7334
|
+
await sinkable.setSinkId(sinkId).catch(() => void 0);
|
|
7335
|
+
}
|
|
7336
|
+
await audio.play().catch(() => void 0);
|
|
7337
|
+
await new Promise((resolve) => setTimeout(resolve, 700));
|
|
7338
|
+
audio.pause();
|
|
7339
|
+
await context.close();
|
|
7340
|
+
}
|
|
7341
|
+
function useMicrophoneStream(deviceId2) {
|
|
7342
|
+
const [stream, setStream] = useState3(null);
|
|
7343
|
+
useEffect3(() => {
|
|
7344
|
+
if (deviceId2 === "off") return void 0;
|
|
7345
|
+
let live = true;
|
|
7346
|
+
let opened = null;
|
|
7347
|
+
const media = globalThis.navigator?.mediaDevices;
|
|
7348
|
+
if (media === void 0) return void 0;
|
|
7349
|
+
void media.getUserMedia({ audio: deviceId2 === null ? true : { deviceId: { exact: deviceId2 } } }).then((next) => {
|
|
7350
|
+
if (!live) {
|
|
7351
|
+
next.getTracks().forEach((track) => track.stop());
|
|
7352
|
+
return;
|
|
7353
|
+
}
|
|
7354
|
+
opened = next;
|
|
7355
|
+
setStream(next);
|
|
7356
|
+
}).catch(() => setStream(null));
|
|
7357
|
+
return () => {
|
|
7358
|
+
live = false;
|
|
7359
|
+
opened?.getTracks().forEach((track) => track.stop());
|
|
7360
|
+
setStream(null);
|
|
7361
|
+
};
|
|
7362
|
+
}, [deviceId2]);
|
|
7363
|
+
return stream;
|
|
7364
|
+
}
|
|
5760
7365
|
function LobbyPanel() {
|
|
5761
7366
|
const lobby = useLobby();
|
|
5762
7367
|
if (!lobby.canAdmit || lobby.waiting.length === 0) return null;
|
|
@@ -5982,16 +7587,40 @@ function PreJoinBody({
|
|
|
5982
7587
|
const room = useRoom();
|
|
5983
7588
|
const devices = useDeviceSnapshot();
|
|
5984
7589
|
const recording = useRecording();
|
|
7590
|
+
const effects = useMediaEffects();
|
|
7591
|
+
const { notice } = useModerationNotice();
|
|
5985
7592
|
const [mode, setMode] = useState3("video");
|
|
5986
7593
|
const [microphone, setMicrophone] = useState3(true);
|
|
5987
7594
|
const [joining, setJoining] = useState3(false);
|
|
7595
|
+
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
7596
|
+
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
7597
|
+
const preview = useCameraPreview(mode === "video" ? camera : void 0, effects.effects);
|
|
7598
|
+
const micStream = useMicrophoneStream(microphone ? mic : "off");
|
|
7599
|
+
const level = useAudioLevel(micStream);
|
|
7600
|
+
const [speakerTesting, setSpeakerTesting] = useState3(false);
|
|
5988
7601
|
useEffect3(() => {
|
|
5989
7602
|
void host.devices.refresh();
|
|
5990
7603
|
}, [host]);
|
|
5991
7604
|
return /* @__PURE__ */ jsxs2("div", { className: "mx-meet__prejoin", children: [
|
|
5992
|
-
/* @__PURE__ */
|
|
7605
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__preview", children: [
|
|
7606
|
+
mode === "video" && preview.preview !== null ? /* @__PURE__ */ jsx3(
|
|
7607
|
+
"video",
|
|
7608
|
+
{
|
|
7609
|
+
className: "mx-meet__video mx-meet__video--mirror",
|
|
7610
|
+
autoPlay: true,
|
|
7611
|
+
playsInline: true,
|
|
7612
|
+
muted: true,
|
|
7613
|
+
ref: (element) => {
|
|
7614
|
+
if (element !== null) preview.preview?.attach(element);
|
|
7615
|
+
}
|
|
7616
|
+
}
|
|
7617
|
+
) : /* @__PURE__ */ jsx3("div", { className: "mx-meet__tile-placeholder", children: /* @__PURE__ */ jsx3(Avatar, { name: host.identity.displayName, url: host.identity.avatarUrl, size: 96 }) }),
|
|
7618
|
+
mode === "video" && preview.error !== null ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__preview-note", children: preview.error }) : null,
|
|
7619
|
+
microphone && micStream !== null ? /* @__PURE__ */ jsx3("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__ */ jsx3("span", { style: { width: `${Math.min(100, Math.round(level * 160))}%` } }) }) : null
|
|
7620
|
+
] }),
|
|
5993
7621
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__prejoin-panel", children: [
|
|
5994
7622
|
/* @__PURE__ */ jsx3("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
7623
|
+
notice?.kind === "removed" ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
5995
7624
|
devices?.labelsHidden === true ? /* @__PURE__ */ jsxs2("p", { className: "mx-meet__panel-note", children: [
|
|
5996
7625
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
5997
7626
|
/* @__PURE__ */ jsx3(
|
|
@@ -6008,7 +7637,30 @@ function PreJoinBody({
|
|
|
6008
7637
|
] }) : null,
|
|
6009
7638
|
devices?.fallbackNotice != null ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__warning", children: devices.fallbackNotice }) : null,
|
|
6010
7639
|
/* @__PURE__ */ jsx3(DevicePicker, { kind: "audioinput", label: "Microphone" }),
|
|
7640
|
+
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__field-row", children: [
|
|
7641
|
+
/* @__PURE__ */ jsx3(DevicePicker, { kind: "audiooutput", label: "Speaker" }),
|
|
7642
|
+
/* @__PURE__ */ jsxs2(
|
|
7643
|
+
"button",
|
|
7644
|
+
{
|
|
7645
|
+
type: "button",
|
|
7646
|
+
className: "mx-meet__chip",
|
|
7647
|
+
disabled: speakerTesting,
|
|
7648
|
+
onClick: () => {
|
|
7649
|
+
setSpeakerTesting(true);
|
|
7650
|
+
void playTestSound(devices?.selected.audiooutput ?? host.devices.resolve("audiooutput")).finally(
|
|
7651
|
+
() => setSpeakerTesting(false)
|
|
7652
|
+
);
|
|
7653
|
+
},
|
|
7654
|
+
children: [
|
|
7655
|
+
/* @__PURE__ */ jsx3(SpeakerIcon, { width: 14, height: 14 }),
|
|
7656
|
+
" ",
|
|
7657
|
+
speakerTesting ? "Playing\u2026" : "Test"
|
|
7658
|
+
]
|
|
7659
|
+
}
|
|
7660
|
+
)
|
|
7661
|
+
] }),
|
|
6011
7662
|
/* @__PURE__ */ jsx3(DevicePicker, { kind: "videoinput", label: "Camera" }),
|
|
7663
|
+
/* @__PURE__ */ jsx3(EffectsSettings, { preview: preview.preview }),
|
|
6012
7664
|
recording.consentNotice !== null ? /* @__PURE__ */ jsx3("p", { className: "mx-meet__consent", children: recording.consentNotice }) : null,
|
|
6013
7665
|
/* @__PURE__ */ jsxs2("div", { className: "mx-meet__prejoin-actions", children: [
|
|
6014
7666
|
/* @__PURE__ */ jsxs2("label", { className: "mx-meet__toggle", children: [
|
|
@@ -6040,6 +7692,7 @@ function PreJoinBody({
|
|
|
6040
7692
|
className: "mx-meet__primary",
|
|
6041
7693
|
onClick: () => {
|
|
6042
7694
|
setJoining(true);
|
|
7695
|
+
preview.release();
|
|
6043
7696
|
void room.join({
|
|
6044
7697
|
roomName,
|
|
6045
7698
|
mode,
|
|
@@ -6059,9 +7712,51 @@ function PreJoinBody({
|
|
|
6059
7712
|
] })
|
|
6060
7713
|
] });
|
|
6061
7714
|
}
|
|
7715
|
+
function useCameraPreview(deviceId2, effects) {
|
|
7716
|
+
const [preview, setPreview] = useState3(null);
|
|
7717
|
+
const [error, setError] = useState3(null);
|
|
7718
|
+
const current = useRef3(null);
|
|
7719
|
+
const initialEffect = useRef3(toVideoEffect(effects));
|
|
7720
|
+
initialEffect.current = toVideoEffect(effects);
|
|
7721
|
+
useEffect3(() => {
|
|
7722
|
+
if (deviceId2 === void 0) return void 0;
|
|
7723
|
+
let live = true;
|
|
7724
|
+
setError(null);
|
|
7725
|
+
void createCameraPreview(deviceId2, initialEffect.current).then((created) => {
|
|
7726
|
+
if (!live) {
|
|
7727
|
+
created.stop();
|
|
7728
|
+
return;
|
|
7729
|
+
}
|
|
7730
|
+
current.current = created;
|
|
7731
|
+
setPreview(created);
|
|
7732
|
+
}).catch((thrown) => {
|
|
7733
|
+
if (!live) return;
|
|
7734
|
+
const failure = thrown;
|
|
7735
|
+
const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
|
|
7736
|
+
const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
|
|
7737
|
+
setError([sentence, failure.remedy].filter(Boolean).join(". ") || "The camera did not start.");
|
|
7738
|
+
});
|
|
7739
|
+
return () => {
|
|
7740
|
+
live = false;
|
|
7741
|
+
current.current?.stop();
|
|
7742
|
+
current.current = null;
|
|
7743
|
+
setPreview(null);
|
|
7744
|
+
};
|
|
7745
|
+
}, [deviceId2]);
|
|
7746
|
+
return {
|
|
7747
|
+
preview,
|
|
7748
|
+
error,
|
|
7749
|
+
release: () => {
|
|
7750
|
+
current.current?.stop();
|
|
7751
|
+
current.current = null;
|
|
7752
|
+
setPreview(null);
|
|
7753
|
+
}
|
|
7754
|
+
};
|
|
7755
|
+
}
|
|
6062
7756
|
function DevicePicker({
|
|
6063
7757
|
kind,
|
|
6064
|
-
label
|
|
7758
|
+
label,
|
|
7759
|
+
onPick
|
|
6065
7760
|
}) {
|
|
6066
7761
|
const host = useRequiredMeetHost();
|
|
6067
7762
|
const devices = useDeviceSnapshot();
|
|
@@ -6074,7 +7769,10 @@ function DevicePicker({
|
|
|
6074
7769
|
{
|
|
6075
7770
|
className: "mx-meet__select",
|
|
6076
7771
|
value: devices?.selected[kind] ?? host.devices.resolve(kind) ?? "",
|
|
6077
|
-
onChange: (event) =>
|
|
7772
|
+
onChange: (event) => {
|
|
7773
|
+
if (onPick !== void 0) onPick(kind, event.target.value);
|
|
7774
|
+
else host.devices.select(kind, event.target.value);
|
|
7775
|
+
},
|
|
6078
7776
|
children: list.map((device) => /* @__PURE__ */ jsx3("option", { value: device.deviceId, children: device.label.length > 0 ? device.label : `${label} (name hidden)` }, device.deviceId))
|
|
6079
7777
|
}
|
|
6080
7778
|
)
|
|
@@ -6982,19 +8680,23 @@ export {
|
|
|
6982
8680
|
ControlBar,
|
|
6983
8681
|
ControlButton,
|
|
6984
8682
|
DATA_TOPICS,
|
|
8683
|
+
DEFAULT_EFFECTS,
|
|
6985
8684
|
DEFAULT_PAGE_SIZE,
|
|
6986
8685
|
END_MEETING_NOT_SERVED,
|
|
6987
8686
|
FILE_SESSION_PATH,
|
|
8687
|
+
FlagIcon,
|
|
6988
8688
|
GUEST_RECORD_ACCESS_CHOICES,
|
|
6989
8689
|
GUEST_RECORD_ACCESS_LEVELS,
|
|
6990
8690
|
GUEST_RECORD_ACCESS_METADATA_KEY,
|
|
6991
8691
|
GUEST_RECORD_NOT_SHARED,
|
|
6992
8692
|
GuestRecordAccessControl,
|
|
6993
8693
|
HandIcon,
|
|
8694
|
+
HostMenu,
|
|
6994
8695
|
IDLE_MEDIA,
|
|
6995
8696
|
IDLE_TRACK,
|
|
6996
8697
|
IncomingCallCard,
|
|
6997
8698
|
IncomingCallHost,
|
|
8699
|
+
KeyboardIcon,
|
|
6998
8700
|
LISTEN_ONLY_NOTICE,
|
|
6999
8701
|
LayoutIcon,
|
|
7000
8702
|
LobbyPanel,
|
|
@@ -7004,15 +8706,20 @@ export {
|
|
|
7004
8706
|
MEET_ANSWER_CONTEXT_EVENT,
|
|
7005
8707
|
MEET_ANSWER_EVENTS,
|
|
7006
8708
|
MEET_EVENTS,
|
|
8709
|
+
MEET_REACTIONS,
|
|
7007
8710
|
MEET_ROUTES,
|
|
7008
8711
|
MEET_SCHEMA,
|
|
8712
|
+
MEET_SHORTCUTS,
|
|
8713
|
+
MODERATION_ACTIONS,
|
|
7009
8714
|
MeetError,
|
|
7010
8715
|
MeetProvider,
|
|
7011
8716
|
MeetingList,
|
|
8717
|
+
MeetingOverlays,
|
|
7012
8718
|
MeetingRecordView,
|
|
7013
8719
|
MeetingRoom,
|
|
7014
8720
|
MicIcon,
|
|
7015
8721
|
MicOffIcon,
|
|
8722
|
+
MoreIcon,
|
|
7016
8723
|
NOTE_TAKER_IDENTITY_PREFIX,
|
|
7017
8724
|
NOTE_TAKER_METADATA_KEY,
|
|
7018
8725
|
NO_GRANTS,
|
|
@@ -7020,12 +8727,15 @@ export {
|
|
|
7020
8727
|
PARTICIPANT_CAN_RECORD_ATTRIBUTE,
|
|
7021
8728
|
PARTICIPANT_ROLE_ATTRIBUTE,
|
|
7022
8729
|
ParticipantTile,
|
|
8730
|
+
PencilIcon,
|
|
7023
8731
|
PeopleIcon,
|
|
7024
8732
|
PeoplePanel,
|
|
8733
|
+
PersonMenu,
|
|
7025
8734
|
PhoneIcon,
|
|
7026
8735
|
PhoneOffIcon,
|
|
7027
8736
|
PinIcon,
|
|
7028
8737
|
PreJoin,
|
|
8738
|
+
QualityBars,
|
|
7029
8739
|
REACTION_TTL_MS,
|
|
7030
8740
|
REFUSED_ABSENCE,
|
|
7031
8741
|
ROLE_RANK,
|
|
@@ -7039,8 +8749,15 @@ export {
|
|
|
7039
8749
|
RecordingPlayer,
|
|
7040
8750
|
ScheduleMeeting,
|
|
7041
8751
|
ScreenShareIcon,
|
|
8752
|
+
SearchIcon,
|
|
7042
8753
|
SettingsIcon,
|
|
8754
|
+
SettingsPanel,
|
|
8755
|
+
ShortcutsSheet,
|
|
8756
|
+
SignalIcon,
|
|
8757
|
+
SmileIcon,
|
|
7043
8758
|
SparklesIcon,
|
|
8759
|
+
SpeakerIcon,
|
|
8760
|
+
SpotlightIcon,
|
|
7044
8761
|
Stage,
|
|
7045
8762
|
TABLES,
|
|
7046
8763
|
TRANSCRIPTION_ATTRIBUTES,
|
|
@@ -7068,6 +8785,7 @@ export {
|
|
|
7068
8785
|
createCallCenter,
|
|
7069
8786
|
createCallInviteHandler,
|
|
7070
8787
|
createDeviceManager,
|
|
8788
|
+
createInRoomClient,
|
|
7071
8789
|
createLiveKitDriver,
|
|
7072
8790
|
createMeetAi,
|
|
7073
8791
|
createMeetApi,
|
|
@@ -7080,6 +8798,7 @@ export {
|
|
|
7080
8798
|
createRoomEngine,
|
|
7081
8799
|
createTokenClient,
|
|
7082
8800
|
createWebDeviceStorage,
|
|
8801
|
+
createWebEffectsStorage,
|
|
7083
8802
|
createWebRoomTokenStorage,
|
|
7084
8803
|
displayNameFor,
|
|
7085
8804
|
fileDownloadPath,
|
|
@@ -7143,6 +8862,7 @@ export {
|
|
|
7143
8862
|
settingsPatch,
|
|
7144
8863
|
snapshotParticipant,
|
|
7145
8864
|
unavailableFrom,
|
|
8865
|
+
useAudioLevel,
|
|
7146
8866
|
useCalls,
|
|
7147
8867
|
useCanModerate,
|
|
7148
8868
|
useCanRecord,
|
|
@@ -7151,19 +8871,23 @@ export {
|
|
|
7151
8871
|
useElapsed,
|
|
7152
8872
|
useEndMeeting,
|
|
7153
8873
|
useGuestRecordAccess,
|
|
8874
|
+
useHandQueue,
|
|
7154
8875
|
useHostControls,
|
|
7155
8876
|
useIsHost,
|
|
7156
8877
|
useListenOnlyNotice,
|
|
7157
8878
|
useLobby,
|
|
7158
8879
|
useLocalMedia,
|
|
7159
8880
|
useLocalUserId,
|
|
8881
|
+
useMediaEffects,
|
|
7160
8882
|
useMeetAi,
|
|
7161
8883
|
useMeetHost,
|
|
8884
|
+
useMeetShortcuts,
|
|
7162
8885
|
useMeetSnapshot,
|
|
7163
8886
|
useMeeting,
|
|
7164
8887
|
useMeetingId,
|
|
7165
8888
|
useMeetingRecord,
|
|
7166
8889
|
useMeetings,
|
|
8890
|
+
useModerationNotice,
|
|
7167
8891
|
useReactions,
|
|
7168
8892
|
useRecording,
|
|
7169
8893
|
useRequiredMeetHost,
|