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