@ai-matrx/meet 0.11.42 → 0.11.44
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 +12 -0
- package/dist/{collab-Bf5evcLO.d.cts → collab-BNJNgXFB.d.cts} +10 -1
- package/dist/{collab-Bf5evcLO.d.ts → collab-BNJNgXFB.d.ts} +10 -1
- package/dist/{components-Frpvls4N.d.ts → components-BDtGzHf4.d.ts} +4 -2
- package/dist/{components-roCEgyFH.d.cts → components-BxK993iW.d.cts} +4 -2
- package/dist/core.cjs +328 -25
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +152 -7
- package/dist/core.d.ts +152 -7
- package/dist/core.js +328 -25
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +328 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +328 -25
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +839 -77
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +22 -5
- package/dist/react.d.ts +22 -5
- package/dist/react.js +1225 -463
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +581 -60
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +6 -3
- package/dist/skins/meet.d.ts +6 -3
- package/dist/skins/meet.js +908 -387
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -4
package/dist/react.cjs
CHANGED
|
@@ -122,6 +122,7 @@ __export(react_exports, {
|
|
|
122
122
|
autoAssign: () => autoAssign,
|
|
123
123
|
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
124
124
|
availableActivities: () => availableActivities,
|
|
125
|
+
breakoutClosingMs: () => breakoutClosingMs,
|
|
125
126
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
126
127
|
callInviteAction: () => callInviteAction,
|
|
127
128
|
callInviteText: () => callInviteText,
|
|
@@ -157,6 +158,7 @@ __export(react_exports, {
|
|
|
157
158
|
createReconnectPolicy: () => createReconnectPolicy,
|
|
158
159
|
createRecordingController: () => createRecordingController,
|
|
159
160
|
createRoomEngine: () => createRoomEngine,
|
|
161
|
+
createSpeakingWhileMutedDetector: () => createSpeakingWhileMutedDetector,
|
|
160
162
|
createTokenClient: () => createTokenClient,
|
|
161
163
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
162
164
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
@@ -165,6 +167,7 @@ __export(react_exports, {
|
|
|
165
167
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
166
168
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
167
169
|
detectBrowser: () => detectBrowser,
|
|
170
|
+
detectCapabilities: () => detectCapabilities,
|
|
168
171
|
detectOs: () => detectOs,
|
|
169
172
|
deviceChipLabel: () => deviceChipLabel,
|
|
170
173
|
deviceFixSteps: () => deviceFixSteps,
|
|
@@ -189,6 +192,7 @@ __export(react_exports, {
|
|
|
189
192
|
isLinkFollower: () => isLinkFollower,
|
|
190
193
|
isListenOnly: () => isListenOnly,
|
|
191
194
|
isLivePhase: () => isLivePhase,
|
|
195
|
+
isMobileUserAgent: () => isMobileUserAgent,
|
|
192
196
|
isNoteTaker: () => isNoteTaker,
|
|
193
197
|
isObserverRole: () => isObserverRole,
|
|
194
198
|
isRealName: () => isRealName,
|
|
@@ -281,6 +285,7 @@ __export(react_exports, {
|
|
|
281
285
|
transcriptDocument: () => transcriptDocument,
|
|
282
286
|
unavailableFrom: () => unavailableFrom,
|
|
283
287
|
uniqueAttendees: () => uniqueAttendees,
|
|
288
|
+
unsupportedWords: () => unsupportedWords,
|
|
284
289
|
untranslatedLines: () => untranslatedLines,
|
|
285
290
|
useActivityAttention: () => useActivityAttention,
|
|
286
291
|
useAudioLevel: () => useAudioLevel,
|
|
@@ -289,6 +294,7 @@ __export(react_exports, {
|
|
|
289
294
|
useCanModerate: () => useCanModerate,
|
|
290
295
|
useCanRecord: () => useCanRecord,
|
|
291
296
|
useCaptionTranslation: () => useCaptionTranslation,
|
|
297
|
+
useCaptions: () => useCaptions,
|
|
292
298
|
useCollab: () => useCollab,
|
|
293
299
|
useCollabPanelRequest: () => useCollabPanelRequest,
|
|
294
300
|
useConsentNotice: () => useConsentNotice,
|
|
@@ -1107,6 +1113,8 @@ var MEET_ROUTES = {
|
|
|
1107
1113
|
noteTakerStop: "/api/v1/meet/agent/stop",
|
|
1108
1114
|
/** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
|
|
1109
1115
|
intelligenceAsk: "/api/v1/meet/intelligence/ask",
|
|
1116
|
+
/** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
|
|
1117
|
+
noteTakerCaptions: "/api/v1/meet/agent/captions",
|
|
1110
1118
|
/** A host ends the meeting for everyone. Served by MRI-A6. */
|
|
1111
1119
|
meetingEnd: "/api/v1/meet/end",
|
|
1112
1120
|
/**
|
|
@@ -1253,7 +1261,8 @@ function parseRoomToken(body, roomName) {
|
|
|
1253
1261
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
1254
1262
|
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1255
1263
|
connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
|
|
1256
|
-
leavePolicy: leavePolicyFrom(payload["leave_policy"])
|
|
1264
|
+
leavePolicy: leavePolicyFrom(payload["leave_policy"]),
|
|
1265
|
+
canReclaim: payload["can_reclaim"] === true
|
|
1257
1266
|
};
|
|
1258
1267
|
}
|
|
1259
1268
|
function createTokenClient(options) {
|
|
@@ -1715,6 +1724,42 @@ function createMeetAi(options) {
|
|
|
1715
1724
|
},
|
|
1716
1725
|
"meet.ai.startNoteTaker"
|
|
1717
1726
|
),
|
|
1727
|
+
requestCaptions: async ({ meetingId }) => {
|
|
1728
|
+
const roomToken = options.resolveRoomToken?.() ?? null;
|
|
1729
|
+
const operation = "meet.ai.requestCaptions";
|
|
1730
|
+
try {
|
|
1731
|
+
const response = await send({
|
|
1732
|
+
path: MEET_ROUTES.noteTakerCaptions,
|
|
1733
|
+
body: { meeting_id: meetingId },
|
|
1734
|
+
operation,
|
|
1735
|
+
requireAuth: roomToken === null,
|
|
1736
|
+
...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {}
|
|
1737
|
+
});
|
|
1738
|
+
if (!response.ok) {
|
|
1739
|
+
options.store.patch({ captionsRefusal: await refusalFor(response, operation) });
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
let payload = null;
|
|
1743
|
+
try {
|
|
1744
|
+
payload = await response.json();
|
|
1745
|
+
} catch {
|
|
1746
|
+
payload = null;
|
|
1747
|
+
}
|
|
1748
|
+
const row = typeof payload === "object" && payload !== null ? payload : {};
|
|
1749
|
+
const detail = typeof row["detail"] === "string" ? row["detail"] : "";
|
|
1750
|
+
options.store.patch({
|
|
1751
|
+
captionsRefusal: row["dispatched"] === true ? null : { status: 200, reason: detail, remedy: "" }
|
|
1752
|
+
});
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
options.store.patch({
|
|
1755
|
+
captionsRefusal: {
|
|
1756
|
+
status: 0,
|
|
1757
|
+
reason: error.message,
|
|
1758
|
+
remedy: error.remedy ?? ""
|
|
1759
|
+
}
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1718
1763
|
stopNoteTaker: ({ meetingId }) => dispatch(
|
|
1719
1764
|
MEET_ROUTES.noteTakerStop,
|
|
1720
1765
|
{ meeting_id: meetingId },
|
|
@@ -2701,12 +2746,63 @@ function recordingLabel(state) {
|
|
|
2701
2746
|
}
|
|
2702
2747
|
}
|
|
2703
2748
|
|
|
2749
|
+
// src/core/live-policy.ts
|
|
2750
|
+
var DEFAULT_LIVE_POLICY = {
|
|
2751
|
+
chatMode: "everyone",
|
|
2752
|
+
avLock: "off",
|
|
2753
|
+
timeLimitMinutes: 0,
|
|
2754
|
+
timeLimitWarningMinutes: 5,
|
|
2755
|
+
recordingConsent: "notice"
|
|
2756
|
+
};
|
|
2757
|
+
function oneOf(value, allowed, fallback) {
|
|
2758
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
2759
|
+
}
|
|
2760
|
+
function whole(value, fallback) {
|
|
2761
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
2762
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
2763
|
+
}
|
|
2764
|
+
function livePolicyFrom(raw) {
|
|
2765
|
+
const o = typeof raw === "object" && raw !== null ? raw : {};
|
|
2766
|
+
return {
|
|
2767
|
+
chatMode: oneOf(o["chat_mode"], ["everyone", "hosts_only", "off"], DEFAULT_LIVE_POLICY.chatMode),
|
|
2768
|
+
avLock: oneOf(o["attendee_av_lock"], ["off", "mic", "camera", "both"], DEFAULT_LIVE_POLICY.avLock),
|
|
2769
|
+
timeLimitMinutes: whole(o["time_limit_minutes"], 0),
|
|
2770
|
+
timeLimitWarningMinutes: whole(o["time_limit_warning_minutes"], DEFAULT_LIVE_POLICY.timeLimitWarningMinutes),
|
|
2771
|
+
recordingConsent: oneOf(
|
|
2772
|
+
o["recording_consent"],
|
|
2773
|
+
["notice", "ok_or_leave", "view_only_until_agree"],
|
|
2774
|
+
DEFAULT_LIVE_POLICY.recordingConsent
|
|
2775
|
+
)
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
function samePolicy(a, b) {
|
|
2779
|
+
return a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent;
|
|
2780
|
+
}
|
|
2781
|
+
var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
2782
|
+
function isLockedFor(policy, role, kind) {
|
|
2783
|
+
if (MODERATORS.has(role)) return false;
|
|
2784
|
+
return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
|
|
2785
|
+
}
|
|
2786
|
+
function consentPending(policy, role, recordingOn, given) {
|
|
2787
|
+
return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
|
|
2788
|
+
}
|
|
2789
|
+
function canChat(policy, role) {
|
|
2790
|
+
if (policy.chatMode === "everyone") return true;
|
|
2791
|
+
return policy.chatMode === "hosts_only" && MODERATORS.has(role);
|
|
2792
|
+
}
|
|
2793
|
+
function minutesLeft(policy, startedAtMs, nowMs) {
|
|
2794
|
+
if (policy.timeLimitMinutes <= 0 || startedAtMs === null) return null;
|
|
2795
|
+
return Math.ceil((startedAtMs + policy.timeLimitMinutes * 6e4 - nowMs) / 6e4);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2704
2798
|
// src/core/attendance.ts
|
|
2799
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
2705
2800
|
var IDLE_MEDIA_REGION = {
|
|
2706
2801
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
2707
2802
|
audioOutput: "ok",
|
|
2708
2803
|
notice: null,
|
|
2709
|
-
offer: null
|
|
2804
|
+
offer: null,
|
|
2805
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
2710
2806
|
};
|
|
2711
2807
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
2712
2808
|
var IDLE_MEDIA = {
|
|
@@ -3827,6 +3923,8 @@ function planFromRow(row) {
|
|
|
3827
3923
|
minutes: num2(row["minutes"]) ?? 0,
|
|
3828
3924
|
openedAt: str(row["opened_at"]),
|
|
3829
3925
|
endsAt: str(row["ends_at"]),
|
|
3926
|
+
closingAt: str(row["closing_at"]),
|
|
3927
|
+
moveMode: row["move_mode"] === "auto" ? "auto" : "invite",
|
|
3830
3928
|
closedAt: str(row["closed_at"]),
|
|
3831
3929
|
broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
|
|
3832
3930
|
};
|
|
@@ -3978,9 +4076,17 @@ function autoAssign(identities, rooms, exclude = []) {
|
|
|
3978
4076
|
return out;
|
|
3979
4077
|
}
|
|
3980
4078
|
function breakoutRemainingMs(plan, now) {
|
|
3981
|
-
if (plan === null || plan.state !== "open"
|
|
3982
|
-
const
|
|
3983
|
-
|
|
4079
|
+
if (plan === null || plan.state !== "open") return null;
|
|
4080
|
+
const ends = [plan.endsAt, plan.closingAt].map((stamp) => stamp === null ? Number.NaN : Date.parse(stamp)).filter((time) => !Number.isNaN(time));
|
|
4081
|
+
if (ends.length === 0) return null;
|
|
4082
|
+
return Math.max(0, Math.min(...ends) - now);
|
|
4083
|
+
}
|
|
4084
|
+
var BREAKOUT_COUNTDOWN_MS = 3e4;
|
|
4085
|
+
function breakoutClosingMs(plan, now) {
|
|
4086
|
+
if (plan === null || plan.state !== "open") return null;
|
|
4087
|
+
const remaining = breakoutRemainingMs(plan, now);
|
|
4088
|
+
if (remaining === null) return null;
|
|
4089
|
+
return plan.closingAt !== null || remaining <= BREAKOUT_COUNTDOWN_MS ? remaining : null;
|
|
3984
4090
|
}
|
|
3985
4091
|
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
3986
4092
|
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
@@ -4157,6 +4263,11 @@ var EMPTY = {
|
|
|
4157
4263
|
presenter: null,
|
|
4158
4264
|
waitingCount: 0,
|
|
4159
4265
|
leavePolicy: DEFAULT_LEAVE_POLICY,
|
|
4266
|
+
livePolicy: DEFAULT_LIVE_POLICY,
|
|
4267
|
+
canReclaim: false,
|
|
4268
|
+
recordingConsentGiven: false,
|
|
4269
|
+
sessionStartedAt: null,
|
|
4270
|
+
mutedByHost: false,
|
|
4160
4271
|
moderationNotice: null,
|
|
4161
4272
|
actionNotice: null,
|
|
4162
4273
|
privateChatRequest: null,
|
|
@@ -4166,6 +4277,8 @@ var EMPTY = {
|
|
|
4166
4277
|
names: {},
|
|
4167
4278
|
wrapUp: null,
|
|
4168
4279
|
noteTakerRefusal: null,
|
|
4280
|
+
captionsOn: false,
|
|
4281
|
+
captionsRefusal: null,
|
|
4169
4282
|
media: IDLE_MEDIA,
|
|
4170
4283
|
mediaRegion: IDLE_MEDIA_REGION,
|
|
4171
4284
|
failure: null,
|
|
@@ -4370,7 +4483,12 @@ function createMeetStore(initial = {}) {
|
|
|
4370
4483
|
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
4371
4484
|
return;
|
|
4372
4485
|
}
|
|
4373
|
-
commit({
|
|
4486
|
+
commit({
|
|
4487
|
+
...state,
|
|
4488
|
+
media: { ...state.media, [kind]: next },
|
|
4489
|
+
// The person turned their own microphone on again: the host's mute is over.
|
|
4490
|
+
...kind === "microphone" && next.enabled && state.mutedByHost ? { mutedByHost: false } : {}
|
|
4491
|
+
});
|
|
4374
4492
|
},
|
|
4375
4493
|
reset() {
|
|
4376
4494
|
const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
|
|
@@ -4432,6 +4550,7 @@ function applyDoorRow(store, attendee) {
|
|
|
4432
4550
|
function decidedState(state) {
|
|
4433
4551
|
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4434
4552
|
}
|
|
4553
|
+
var LIVE_POLICY_POLL_MS = 3e3;
|
|
4435
4554
|
function createMeetingSession(options) {
|
|
4436
4555
|
const { manager, repository, store, identity } = options;
|
|
4437
4556
|
let channel = null;
|
|
@@ -4573,6 +4692,46 @@ function createMeetingSession(options) {
|
|
|
4573
4692
|
stopWaiting();
|
|
4574
4693
|
}
|
|
4575
4694
|
}
|
|
4695
|
+
let policyTimer = null;
|
|
4696
|
+
let policyReading = false;
|
|
4697
|
+
async function readLivePolicy() {
|
|
4698
|
+
const meeting = store.snapshot().meeting;
|
|
4699
|
+
const pass = options.resolvePass?.() ?? null;
|
|
4700
|
+
if (meeting === null || pass === null || policyReading) return;
|
|
4701
|
+
policyReading = true;
|
|
4702
|
+
try {
|
|
4703
|
+
const response = await options.api.request({
|
|
4704
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
4705
|
+
method: "GET",
|
|
4706
|
+
requireAuth: false,
|
|
4707
|
+
operation: "meet.state(policy)",
|
|
4708
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
4709
|
+
});
|
|
4710
|
+
if (!response.ok) return;
|
|
4711
|
+
const read = await response.json();
|
|
4712
|
+
const next = livePolicyFrom(read.live_policy);
|
|
4713
|
+
const started = read.session_started_at ? Date.parse(read.session_started_at) : NaN;
|
|
4714
|
+
const snapshot = store.snapshot();
|
|
4715
|
+
const startedAt = Number.isFinite(started) ? started : snapshot.sessionStartedAt;
|
|
4716
|
+
if (!samePolicy(snapshot.livePolicy, next) || startedAt !== snapshot.sessionStartedAt) {
|
|
4717
|
+
store.patch({ livePolicy: next, sessionStartedAt: startedAt });
|
|
4718
|
+
}
|
|
4719
|
+
} catch {
|
|
4720
|
+
} finally {
|
|
4721
|
+
policyReading = false;
|
|
4722
|
+
}
|
|
4723
|
+
}
|
|
4724
|
+
function syncLivePolicyTimer() {
|
|
4725
|
+
const inCall = store.snapshot().phase === "in_call";
|
|
4726
|
+
if (inCall && policyTimer === null) {
|
|
4727
|
+
void readLivePolicy();
|
|
4728
|
+
policyTimer = setInterval(() => void readLivePolicy(), LIVE_POLICY_POLL_MS);
|
|
4729
|
+
} else if (!inCall && policyTimer !== null) {
|
|
4730
|
+
clearInterval(policyTimer);
|
|
4731
|
+
policyTimer = null;
|
|
4732
|
+
}
|
|
4733
|
+
}
|
|
4734
|
+
const unsubscribePolicy = store.subscribe(syncLivePolicyTimer);
|
|
4576
4735
|
async function readState2() {
|
|
4577
4736
|
if (reading !== null) return reading;
|
|
4578
4737
|
const meeting = store.snapshot().meeting;
|
|
@@ -4959,6 +5118,9 @@ function createMeetingSession(options) {
|
|
|
4959
5118
|
},
|
|
4960
5119
|
dispose() {
|
|
4961
5120
|
stopWaiting();
|
|
5121
|
+
unsubscribePolicy();
|
|
5122
|
+
if (policyTimer !== null) clearInterval(policyTimer);
|
|
5123
|
+
policyTimer = null;
|
|
4962
5124
|
channel?.close();
|
|
4963
5125
|
channel = null;
|
|
4964
5126
|
meetingChannel?.close();
|
|
@@ -4995,6 +5157,47 @@ function createMeetSessionIdentity(options) {
|
|
|
4995
5157
|
});
|
|
4996
5158
|
}
|
|
4997
5159
|
|
|
5160
|
+
// src/core/capabilities.ts
|
|
5161
|
+
var IN_APP = [
|
|
5162
|
+
[/Instagram/i, "Instagram"],
|
|
5163
|
+
[/FBAN|FBAV|FB_IAB/i, "Facebook"],
|
|
5164
|
+
[/LinkedInApp/i, "LinkedIn"],
|
|
5165
|
+
[/MicroMessenger/i, "WeChat"],
|
|
5166
|
+
[/Snapchat/i, "Snapchat"],
|
|
5167
|
+
[/\bLine\//i, "LINE"],
|
|
5168
|
+
[/Twitter|TwitterAndroid/i, "X"],
|
|
5169
|
+
[/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
|
|
5170
|
+
];
|
|
5171
|
+
function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
|
|
5172
|
+
if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
|
|
5173
|
+
return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
|
|
5174
|
+
}
|
|
5175
|
+
function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
|
|
5176
|
+
if (scope === null || scope.navigator === void 0) {
|
|
5177
|
+
return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
|
|
5178
|
+
}
|
|
5179
|
+
const ua = scope.navigator.userAgent ?? "";
|
|
5180
|
+
const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
|
|
5181
|
+
const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
|
|
5182
|
+
const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
|
|
5183
|
+
return {
|
|
5184
|
+
canJoin: webrtc,
|
|
5185
|
+
reason: webrtc ? null : "no_webrtc",
|
|
5186
|
+
inAppBrowser,
|
|
5187
|
+
mobile,
|
|
5188
|
+
canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
function unsupportedWords(capabilities) {
|
|
5192
|
+
if (capabilities.inAppBrowser !== null) {
|
|
5193
|
+
return {
|
|
5194
|
+
title: `${capabilities.inAppBrowser}'s browser can't join calls`,
|
|
5195
|
+
body: "Open this link in Chrome, Safari, Edge or Firefox"
|
|
5196
|
+
};
|
|
5197
|
+
}
|
|
5198
|
+
return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
|
|
5199
|
+
}
|
|
5200
|
+
|
|
4998
5201
|
// src/core/media-supervisor.ts
|
|
4999
5202
|
function classifyDeviceFailure(error, permission) {
|
|
5000
5203
|
if (permission === "denied") return "denied";
|
|
@@ -5055,7 +5258,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
5055
5258
|
case "safari":
|
|
5056
5259
|
return where.os === "ios" ? [`Settings \u203A Apps \u203A Safari \u203A ${device}`, "Choose Allow", "Return here and press Try again"] : ["Safari \u203A Settings for This Website", `Set ${device} to Allow`, "Press Try again"];
|
|
5057
5260
|
default:
|
|
5058
|
-
return ["Click the icon
|
|
5261
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
5059
5262
|
}
|
|
5060
5263
|
case "blocked_os": {
|
|
5061
5264
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -5153,7 +5356,7 @@ function createMediaSupervisor(options) {
|
|
|
5153
5356
|
function setRegion(next) {
|
|
5154
5357
|
const current = region();
|
|
5155
5358
|
const merged = { ...current, ...next };
|
|
5156
|
-
if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
|
|
5359
|
+
if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.preview === current.preview && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
|
|
5157
5360
|
return;
|
|
5158
5361
|
}
|
|
5159
5362
|
store.patch({ mediaRegion: merged });
|
|
@@ -5249,7 +5452,7 @@ function createMediaSupervisor(options) {
|
|
|
5249
5452
|
if (permission !== "unknown") setPermission(kind, permission);
|
|
5250
5453
|
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
5251
5454
|
}
|
|
5252
|
-
|
|
5455
|
+
const api = {
|
|
5253
5456
|
start() {
|
|
5254
5457
|
if (started || platform === null) return;
|
|
5255
5458
|
started = true;
|
|
@@ -5271,6 +5474,16 @@ function createMediaSupervisor(options) {
|
|
|
5271
5474
|
started = false;
|
|
5272
5475
|
},
|
|
5273
5476
|
unavailable,
|
|
5477
|
+
async preview(kind, outcome) {
|
|
5478
|
+
api.start();
|
|
5479
|
+
let next;
|
|
5480
|
+
if (typeof outcome === "string") next = { state: outcome, unavailable: null };
|
|
5481
|
+
else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
|
|
5482
|
+
const current = region().preview;
|
|
5483
|
+
const was = current[kind];
|
|
5484
|
+
if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
|
|
5485
|
+
setRegion({ preview: { ...region().preview, [kind]: next } });
|
|
5486
|
+
},
|
|
5274
5487
|
onAudioPlayback(canPlay) {
|
|
5275
5488
|
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
5276
5489
|
},
|
|
@@ -5329,6 +5542,7 @@ function createMediaSupervisor(options) {
|
|
|
5329
5542
|
setRegion({ notice: null });
|
|
5330
5543
|
}
|
|
5331
5544
|
};
|
|
5545
|
+
return api;
|
|
5332
5546
|
}
|
|
5333
5547
|
|
|
5334
5548
|
// src/core/room-events.ts
|
|
@@ -5548,10 +5762,11 @@ function moderationText(kind, payload) {
|
|
|
5548
5762
|
switch (kind) {
|
|
5549
5763
|
case "muted": {
|
|
5550
5764
|
const source = text2(payload["source"]) ?? "microphone";
|
|
5551
|
-
|
|
5765
|
+
const who = payload["by_role"] === "host" || payload["by_role"] === "cohost" ? "the host" : by;
|
|
5766
|
+
return source === "microphone" ? `You've been muted by ${who}.` : source === "camera" ? `Your camera was turned off by ${who}.` : `Your screen share was stopped by ${who}.`;
|
|
5552
5767
|
}
|
|
5553
5768
|
case "ask-unmute":
|
|
5554
|
-
return `${by}
|
|
5769
|
+
return `${by === "The host" ? "The host" : by} asked you to unmute.`;
|
|
5555
5770
|
case "removed":
|
|
5556
5771
|
return `${by} removed you from this meeting.`;
|
|
5557
5772
|
default:
|
|
@@ -5577,7 +5792,7 @@ function applyModeration(store, payload, now) {
|
|
|
5577
5792
|
grants: { ...snapshot.grants, role },
|
|
5578
5793
|
moderationNotice: {
|
|
5579
5794
|
kind: "role",
|
|
5580
|
-
text: role === "host" ? "You are now the host." : role === "cohost" ? "You
|
|
5795
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You're now a co-host." : role === "presenter" ? "You're now a presenter." : snapshot.grants.role === "presenter" ? "You're now a participant: you can no longer present." : "You're now a participant: you are no longer a co-host.",
|
|
5581
5796
|
at: now()
|
|
5582
5797
|
}
|
|
5583
5798
|
});
|
|
@@ -5589,7 +5804,10 @@ function applyModeration(store, payload, now) {
|
|
|
5589
5804
|
case "removed": {
|
|
5590
5805
|
const sentence = moderationText(kind, payload);
|
|
5591
5806
|
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
5592
|
-
store.patch({
|
|
5807
|
+
store.patch({
|
|
5808
|
+
moderationNotice: { kind, text: sentence, at: now() },
|
|
5809
|
+
...kind === "muted" && (text2(payload["source"]) ?? "microphone") === "microphone" ? { mutedByHost: true } : {}
|
|
5810
|
+
});
|
|
5593
5811
|
return HANDLED;
|
|
5594
5812
|
}
|
|
5595
5813
|
default:
|
|
@@ -5628,7 +5846,7 @@ function exactNoteTakerParticipant(meeting, participants) {
|
|
|
5628
5846
|
}
|
|
5629
5847
|
function noteTakerStatus(args) {
|
|
5630
5848
|
const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
|
|
5631
|
-
const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
|
|
5849
|
+
const raw = args.meeting?.metadata?.[NOTE_TAKER_METADATA_KEY];
|
|
5632
5850
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
5633
5851
|
const recorded = readState(row["state"]);
|
|
5634
5852
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
@@ -5836,7 +6054,8 @@ function createRoomEngine(options) {
|
|
|
5836
6054
|
message: normalized.message,
|
|
5837
6055
|
remedy: normalized.remedy,
|
|
5838
6056
|
reason: normalized.reason,
|
|
5839
|
-
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
6057
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
|
|
6058
|
+
stage: "join"
|
|
5840
6059
|
}
|
|
5841
6060
|
});
|
|
5842
6061
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
@@ -6105,6 +6324,7 @@ function createRoomEngine(options) {
|
|
|
6105
6324
|
lastServerUrl = token.serverUrl;
|
|
6106
6325
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6107
6326
|
if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
|
|
6327
|
+
store.patch({ canReclaim: token.canReclaim === true });
|
|
6108
6328
|
store.patch({
|
|
6109
6329
|
localIdentity: token.identity,
|
|
6110
6330
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -6226,6 +6446,10 @@ function createRoomEngine(options) {
|
|
|
6226
6446
|
}
|
|
6227
6447
|
departures.clear();
|
|
6228
6448
|
liveRoster = list;
|
|
6449
|
+
const localNow = list.find((p) => p.isLocal);
|
|
6450
|
+
if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
|
|
6451
|
+
store.patch({ share: NO_SHARE });
|
|
6452
|
+
}
|
|
6229
6453
|
const merged = mergeGhosts(list, ghosts, at);
|
|
6230
6454
|
ghosts = merged.kept;
|
|
6231
6455
|
const people = /* @__PURE__ */ new Map();
|
|
@@ -6445,7 +6669,10 @@ function createRoomEngine(options) {
|
|
|
6445
6669
|
try {
|
|
6446
6670
|
await driver.setScreenShareEnabled(enabled);
|
|
6447
6671
|
ambiguousShareDenials = 0;
|
|
6448
|
-
|
|
6672
|
+
const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
|
|
6673
|
+
store.patch({
|
|
6674
|
+
share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
|
|
6675
|
+
});
|
|
6449
6676
|
} catch (error) {
|
|
6450
6677
|
const kind = classifyScreenShareError(error);
|
|
6451
6678
|
if (kind === "ambiguous") {
|
|
@@ -6512,13 +6739,14 @@ function createRoomEngine(options) {
|
|
|
6512
6739
|
return driver?.supportsVideoEffects?.() ?? false;
|
|
6513
6740
|
},
|
|
6514
6741
|
supportsScreenShare() {
|
|
6515
|
-
return driver?.supportsScreenShare?.() ??
|
|
6742
|
+
return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
|
|
6516
6743
|
},
|
|
6517
6744
|
async switchDevice(kind, deviceId2) {
|
|
6518
6745
|
if (driver === null) return;
|
|
6519
6746
|
await driver.switchDevice(kind, deviceId2);
|
|
6520
6747
|
},
|
|
6521
6748
|
enableAudio: () => media.enableAudio(),
|
|
6749
|
+
previewDevice: (kind, outcome) => media.preview(kind, outcome),
|
|
6522
6750
|
acceptDeviceOffer: () => media.acceptOffer(),
|
|
6523
6751
|
dismissDeviceOffer: () => media.dismissOffer(),
|
|
6524
6752
|
dismissDeviceNotice: () => media.dismissNotice(),
|
|
@@ -6604,8 +6832,12 @@ var MODERATION_ACTIONS = [
|
|
|
6604
6832
|
"lower_hand",
|
|
6605
6833
|
"lower_all_hands",
|
|
6606
6834
|
"make_cohost",
|
|
6835
|
+
"make_presenter",
|
|
6836
|
+
"revoke_presenter",
|
|
6837
|
+
"set_av_lock",
|
|
6607
6838
|
"revoke_cohost",
|
|
6608
6839
|
"transfer_host",
|
|
6840
|
+
"reclaim_host",
|
|
6609
6841
|
"rename",
|
|
6610
6842
|
"remove",
|
|
6611
6843
|
"report"
|
|
@@ -6986,7 +7218,15 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
6986
7218
|
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
6987
7219
|
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
6988
7220
|
roster();
|
|
6989
|
-
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished,
|
|
7221
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
|
|
7222
|
+
if (publication.source === Track.Source.ScreenShare) {
|
|
7223
|
+
const ended = () => {
|
|
7224
|
+
void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
|
|
7225
|
+
};
|
|
7226
|
+
publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
|
|
7227
|
+
}
|
|
7228
|
+
roster();
|
|
7229
|
+
}).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
6990
7230
|
handlers.roomMetadata?.(metadata ?? "");
|
|
6991
7231
|
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
6992
7232
|
handlers.audioPlayback?.(canPlay);
|
|
@@ -7127,7 +7367,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
7127
7367
|
},
|
|
7128
7368
|
async setScreenShareEnabled(enabled) {
|
|
7129
7369
|
try {
|
|
7130
|
-
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
7370
|
+
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
7371
|
+
enabled,
|
|
7372
|
+
enabled ? { audio: true, systemAudio: "include" } : void 0
|
|
7373
|
+
);
|
|
7131
7374
|
} catch (error) {
|
|
7132
7375
|
throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
|
|
7133
7376
|
}
|
|
@@ -7166,8 +7409,13 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
7166
7409
|
supportsVideoEffects() {
|
|
7167
7410
|
return videoEffectsSupported();
|
|
7168
7411
|
},
|
|
7412
|
+
screenShareHasAudio() {
|
|
7413
|
+
const target = room;
|
|
7414
|
+
if (target === null) return false;
|
|
7415
|
+
return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
|
|
7416
|
+
},
|
|
7169
7417
|
supportsScreenShare() {
|
|
7170
|
-
return
|
|
7418
|
+
return detectCapabilities().canShareScreen;
|
|
7171
7419
|
},
|
|
7172
7420
|
attach(identity, kind, element) {
|
|
7173
7421
|
const target = requireRoom("attach");
|
|
@@ -7643,14 +7891,31 @@ var import_react3 = require("react");
|
|
|
7643
7891
|
|
|
7644
7892
|
// src/core/observation.ts
|
|
7645
7893
|
function observedRole(role, standing) {
|
|
7646
|
-
if (role === "host" || role === "cohost") return role;
|
|
7894
|
+
if (role === "host" || role === "cohost" || role === "presenter") return role;
|
|
7647
7895
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
7648
7896
|
}
|
|
7649
|
-
function observedDevice(track, permission = "unknown") {
|
|
7897
|
+
function observedDevice(track, permission = "unknown", held = null) {
|
|
7898
|
+
if (held === "locked") return "locked";
|
|
7650
7899
|
if (track.enabled) return "on";
|
|
7900
|
+
if (held === "muted_by_host") return "muted_by_host";
|
|
7651
7901
|
const reason = deviceUnavailableReason(track, permission);
|
|
7652
7902
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
7653
7903
|
}
|
|
7904
|
+
function observedPreview(preview, permission = "unknown") {
|
|
7905
|
+
if (permission === "denied") return "unavailable:denied";
|
|
7906
|
+
switch (preview.state) {
|
|
7907
|
+
case "on":
|
|
7908
|
+
return "on";
|
|
7909
|
+
case "off":
|
|
7910
|
+
return "muted_by_me";
|
|
7911
|
+
case "prompting":
|
|
7912
|
+
return "prompting";
|
|
7913
|
+
case "unavailable":
|
|
7914
|
+
return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
|
|
7915
|
+
default:
|
|
7916
|
+
return null;
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7654
7919
|
function endedRecord(snapshot, given) {
|
|
7655
7920
|
const record = snapshot?.meeting ?? given;
|
|
7656
7921
|
return record !== null && record.endedAt !== null;
|
|
@@ -7674,20 +7939,34 @@ function observe(input) {
|
|
|
7674
7939
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
7675
7940
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
7676
7941
|
const deviceInPlay = phase === "joining" || inCall;
|
|
7942
|
+
const region = snapshot?.mediaRegion;
|
|
7943
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
7677
7944
|
return {
|
|
7678
7945
|
phase,
|
|
7679
7946
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
7680
7947
|
role,
|
|
7681
|
-
|
|
7948
|
+
// `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
|
|
7949
|
+
// person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
|
|
7950
|
+
connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
|
|
7682
7951
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
7683
7952
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
7684
7953
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
7685
7954
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
7686
7955
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
7687
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
7688
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
7956
|
+
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : atPrejoin ? observedPreview(region.preview.camera, region.permission.camera) : null,
|
|
7957
|
+
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : atPrejoin ? observedPreview(region.preview.microphone, region.permission.microphone) : null
|
|
7689
7958
|
};
|
|
7690
7959
|
}
|
|
7960
|
+
function lockedFor(snapshot, kind, inCall) {
|
|
7961
|
+
if (!inCall) return null;
|
|
7962
|
+
const role = effectiveRole(snapshot);
|
|
7963
|
+
if (isLockedFor(snapshot.livePolicy, role, kind)) return "locked";
|
|
7964
|
+
if (snapshot.livePolicy.recordingConsent === "view_only_until_agree" && consentPending(snapshot.livePolicy, role, snapshot.recording === "recording", snapshot.recordingConsentGiven)) {
|
|
7965
|
+
return "locked";
|
|
7966
|
+
}
|
|
7967
|
+
if (kind === "microphone" && snapshot.mutedByHost) return "muted_by_host";
|
|
7968
|
+
return null;
|
|
7969
|
+
}
|
|
7691
7970
|
function staticObservation(phase, reason = null) {
|
|
7692
7971
|
return {
|
|
7693
7972
|
phase,
|
|
@@ -7775,7 +8054,13 @@ function MeetRoot({
|
|
|
7775
8054
|
...options
|
|
7776
8055
|
}) {
|
|
7777
8056
|
const snapshot = useMeetSnapshot();
|
|
7778
|
-
const
|
|
8057
|
+
const probed = useCapabilities();
|
|
8058
|
+
const base = useCoreObservation(options);
|
|
8059
|
+
const unsupported = probed !== null && !probed.canJoin;
|
|
8060
|
+
const observation = (0, import_react3.useMemo)(
|
|
8061
|
+
() => unsupported ? { ...staticObservation("unsupported", probed?.reason ?? null) } : base,
|
|
8062
|
+
[unsupported, probed, base]
|
|
8063
|
+
);
|
|
7779
8064
|
const attrs = (0, import_react3.useMemo)(() => rootAttributes(observation), [observation]);
|
|
7780
8065
|
const latest = (0, import_react3.useRef)({ observation, storePhase: snapshot?.phase ?? null });
|
|
7781
8066
|
latest.current = { observation, storePhase: snapshot?.phase ?? null };
|
|
@@ -7793,7 +8078,34 @@ function MeetRoot({
|
|
|
7793
8078
|
if (target.__matrxMeet === diagnostics) delete target.__matrxMeet;
|
|
7794
8079
|
};
|
|
7795
8080
|
}, []);
|
|
7796
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...attrs, style: { display: "contents" }, children });
|
|
8081
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { ...attrs, style: { display: "contents" }, children: unsupported && probed !== null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(UnsupportedBrowser, { capabilities: probed }) : children });
|
|
8082
|
+
}
|
|
8083
|
+
function useCapabilities() {
|
|
8084
|
+
const [found, setFound] = (0, import_react3.useState)(null);
|
|
8085
|
+
(0, import_react3.useEffect)(() => {
|
|
8086
|
+
setFound(detectCapabilities());
|
|
8087
|
+
}, []);
|
|
8088
|
+
return found;
|
|
8089
|
+
}
|
|
8090
|
+
function UnsupportedBrowser({ capabilities }) {
|
|
8091
|
+
const words = unsupportedWords(capabilities);
|
|
8092
|
+
const [copied, setCopied] = (0, import_react3.useState)(false);
|
|
8093
|
+
const href = typeof window === "undefined" ? "" : window.location.href;
|
|
8094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mx-meet", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mx-meet__waiting", "data-meet-exit": "unsupported", children: [
|
|
8095
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: "mx-meet__prejoin-title", children: words.title }),
|
|
8096
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mx-meet__hint", role: "status", "data-meet-notice": "browser-unsupported", children: words.body }),
|
|
8097
|
+
href.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
8098
|
+
"button",
|
|
8099
|
+
{
|
|
8100
|
+
type: "button",
|
|
8101
|
+
className: "mx-meet__primary",
|
|
8102
|
+
onClick: () => {
|
|
8103
|
+
void navigator.clipboard?.writeText(href).then(() => setCopied(true), () => void 0);
|
|
8104
|
+
},
|
|
8105
|
+
children: copied ? "Link copied" : "Copy link"
|
|
8106
|
+
}
|
|
8107
|
+
) : null
|
|
8108
|
+
] }) });
|
|
7797
8109
|
}
|
|
7798
8110
|
|
|
7799
8111
|
// src/react/hooks.ts
|
|
@@ -8493,7 +8805,8 @@ function useRoom() {
|
|
|
8493
8805
|
failure: snapshot?.failure ?? null,
|
|
8494
8806
|
secondDevice: snapshot?.secondDevice ?? null,
|
|
8495
8807
|
local,
|
|
8496
|
-
|
|
8808
|
+
// An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
|
|
8809
|
+
canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null),
|
|
8497
8810
|
join: (0, import_react4.useCallback)(
|
|
8498
8811
|
async (request) => requireEngine(engine, "join").join(request),
|
|
8499
8812
|
[engine]
|
|
@@ -8877,6 +9190,45 @@ function useMeetAi() {
|
|
|
8877
9190
|
}, [ai, meeting, dispatchNoteTaker])
|
|
8878
9191
|
};
|
|
8879
9192
|
}
|
|
9193
|
+
function useCaptions() {
|
|
9194
|
+
const host = useMeetHost();
|
|
9195
|
+
const snapshot = useMeetSnapshot();
|
|
9196
|
+
const ai = useMeetAi();
|
|
9197
|
+
const on = snapshot?.captionsOn === true;
|
|
9198
|
+
const refusal2 = snapshot?.captionsRefusal ?? null;
|
|
9199
|
+
const taker = ai.noteTaker;
|
|
9200
|
+
const meeting = snapshot?.meeting ?? null;
|
|
9201
|
+
const request = (0, import_react4.useCallback)(() => {
|
|
9202
|
+
if (host === null || meeting === null) return;
|
|
9203
|
+
void host.ai.requestCaptions({ meetingId: meeting.id });
|
|
9204
|
+
}, [host, meeting]);
|
|
9205
|
+
const toggle = (0, import_react4.useCallback)(() => {
|
|
9206
|
+
if (host === null) return;
|
|
9207
|
+
const next = !on;
|
|
9208
|
+
host.store.patch({ captionsOn: next, captionsRefusal: null });
|
|
9209
|
+
if (next && taker.state !== "joining" && taker.state !== "transcribing") request();
|
|
9210
|
+
}, [host, on, taker.state, request]);
|
|
9211
|
+
let state = "off";
|
|
9212
|
+
let reason = null;
|
|
9213
|
+
let remedy = null;
|
|
9214
|
+
if (on) {
|
|
9215
|
+
if (taker.state === "transcribing" && taker.present) {
|
|
9216
|
+
state = "live";
|
|
9217
|
+
} else if (taker.state === "joining") {
|
|
9218
|
+
state = "starting";
|
|
9219
|
+
} else if (refusal2 !== null && refusal2.reason.length > 0) {
|
|
9220
|
+
state = "unavailable";
|
|
9221
|
+
reason = refusal2.reason;
|
|
9222
|
+
remedy = refusal2.remedy.length > 0 ? refusal2.remedy : null;
|
|
9223
|
+
} else if (taker.state === "ended" || taker.state === "handoff_pending") {
|
|
9224
|
+
state = "unavailable";
|
|
9225
|
+
reason = noteTakerLabel(taker);
|
|
9226
|
+
} else {
|
|
9227
|
+
state = "starting";
|
|
9228
|
+
}
|
|
9229
|
+
}
|
|
9230
|
+
return { on, state, reason, remedy, toggle };
|
|
9231
|
+
}
|
|
8880
9232
|
function useEndMeeting() {
|
|
8881
9233
|
const host = useMeetHost();
|
|
8882
9234
|
const snapshot = useMeetSnapshot();
|
|
@@ -8914,7 +9266,7 @@ function useConsentNotice() {
|
|
|
8914
9266
|
const snapshot = useMeetSnapshot();
|
|
8915
9267
|
const agent = noteTakerParticipant(snapshot?.participants ?? []);
|
|
8916
9268
|
if (agent === null) return null;
|
|
8917
|
-
return
|
|
9269
|
+
return `AI notes are on. ${agent.displayName} is in this meeting, transcribing what is said and writing notes everyone here can read.`;
|
|
8918
9270
|
}
|
|
8919
9271
|
function useMediaSupervisor() {
|
|
8920
9272
|
const host = useMeetHost();
|
|
@@ -8933,6 +9285,12 @@ function useMediaSupervisor() {
|
|
|
8933
9285
|
retry: (0, import_react4.useCallback)(
|
|
8934
9286
|
async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
|
|
8935
9287
|
[engine]
|
|
9288
|
+
),
|
|
9289
|
+
reportPreview: (0, import_react4.useCallback)(
|
|
9290
|
+
(kind, outcome) => {
|
|
9291
|
+
void engine?.previewDevice(kind, outcome);
|
|
9292
|
+
},
|
|
9293
|
+
[engine]
|
|
8936
9294
|
)
|
|
8937
9295
|
};
|
|
8938
9296
|
}
|
|
@@ -9378,6 +9736,38 @@ function useGuestRecordAccess(meeting) {
|
|
|
9378
9736
|
)
|
|
9379
9737
|
};
|
|
9380
9738
|
}
|
|
9739
|
+
function useLivePolicy() {
|
|
9740
|
+
const snapshot = useMeetSnapshot();
|
|
9741
|
+
const role = useRole();
|
|
9742
|
+
const [now, setNow] = (0, import_react4.useState)(() => Date.now());
|
|
9743
|
+
const limited = (snapshot?.livePolicy.timeLimitMinutes ?? 0) > 0;
|
|
9744
|
+
(0, import_react4.useEffect)(() => {
|
|
9745
|
+
if (!limited) return void 0;
|
|
9746
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
9747
|
+
return () => clearInterval(timer);
|
|
9748
|
+
}, [limited]);
|
|
9749
|
+
const policy = snapshot?.livePolicy ?? DEFAULT_LIVE_POLICY;
|
|
9750
|
+
const left = minutesLeft(policy, snapshot?.sessionStartedAt ?? null, now);
|
|
9751
|
+
const host = useMeetHost();
|
|
9752
|
+
const pending = consentPending(policy, role, snapshot?.recording === "recording", snapshot?.recordingConsentGiven === true);
|
|
9753
|
+
const viewOnly = pending && policy.recordingConsent === "view_only_until_agree";
|
|
9754
|
+
const idle = snapshot !== null && snapshot.recording !== "recording" && snapshot.recording !== "starting" && snapshot.recording !== "stopping";
|
|
9755
|
+
const given = snapshot?.recordingConsentGiven === true;
|
|
9756
|
+
const store = host?.store ?? null;
|
|
9757
|
+
(0, import_react4.useEffect)(() => {
|
|
9758
|
+
if (idle && given) store?.patch({ recordingConsentGiven: false });
|
|
9759
|
+
}, [idle, given, store]);
|
|
9760
|
+
return {
|
|
9761
|
+
canChat: canChat(policy, role),
|
|
9762
|
+
chatMode: policy.chatMode,
|
|
9763
|
+
micLocked: isLockedFor(policy, role, "microphone") || viewOnly,
|
|
9764
|
+
cameraLocked: isLockedFor(policy, role, "camera") || viewOnly,
|
|
9765
|
+
consent: pending ? policy.recordingConsent === "ok_or_leave" ? "ok_or_leave" : "view_only_until_agree" : null,
|
|
9766
|
+
agree: () => store?.patch({ recordingConsentGiven: true }),
|
|
9767
|
+
minutesLeft: left,
|
|
9768
|
+
timeWarning: left !== null && left <= policy.timeLimitWarningMinutes
|
|
9769
|
+
};
|
|
9770
|
+
}
|
|
9381
9771
|
|
|
9382
9772
|
// src/react/skin-root.tsx
|
|
9383
9773
|
var import_react12 = require("react");
|
|
@@ -9996,6 +10386,39 @@ function useCollabPanelRequest(onRequest) {
|
|
|
9996
10386
|
// src/core/reactions.ts
|
|
9997
10387
|
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
9998
10388
|
|
|
10389
|
+
// src/core/speaking-detector.ts
|
|
10390
|
+
function createSpeakingWhileMutedDetector(options = {}) {
|
|
10391
|
+
const threshold = options.threshold ?? 0.06;
|
|
10392
|
+
const holdMs = options.holdMs ?? 600;
|
|
10393
|
+
const gapMs = options.gapMs ?? 400;
|
|
10394
|
+
const cooldownMs = options.cooldownMs ?? 2e4;
|
|
10395
|
+
let speechStart = null;
|
|
10396
|
+
let lastLoud = null;
|
|
10397
|
+
let firedAt = null;
|
|
10398
|
+
return {
|
|
10399
|
+
feed(level, at) {
|
|
10400
|
+
if (level >= threshold) {
|
|
10401
|
+
if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
|
|
10402
|
+
lastLoud = at;
|
|
10403
|
+
} else if (lastLoud !== null && at - lastLoud > gapMs) {
|
|
10404
|
+
speechStart = null;
|
|
10405
|
+
lastLoud = null;
|
|
10406
|
+
}
|
|
10407
|
+
if (speechStart === null || at - speechStart < holdMs) return false;
|
|
10408
|
+
if (firedAt !== null && at - firedAt < cooldownMs) return false;
|
|
10409
|
+
firedAt = at;
|
|
10410
|
+
speechStart = null;
|
|
10411
|
+
lastLoud = null;
|
|
10412
|
+
return true;
|
|
10413
|
+
},
|
|
10414
|
+
reset() {
|
|
10415
|
+
speechStart = null;
|
|
10416
|
+
lastLoud = null;
|
|
10417
|
+
firedAt = null;
|
|
10418
|
+
}
|
|
10419
|
+
};
|
|
10420
|
+
}
|
|
10421
|
+
|
|
9999
10422
|
// src/react/facade.ts
|
|
10000
10423
|
var NO_ACTIONS = {
|
|
10001
10424
|
join: async () => void 0,
|
|
@@ -10152,6 +10575,18 @@ function SparklesIcon(props) {
|
|
|
10152
10575
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M19 15l.9 2.1 2.1.9-2.1.9-.9 2.1-.9-2.1-2.1-.9 2.1-.9L19 15Z" })
|
|
10153
10576
|
] });
|
|
10154
10577
|
}
|
|
10578
|
+
function CaptionsIcon(props) {
|
|
10579
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
10580
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
|
|
10581
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M10 10.5a2 2 0 1 0 0 3M16 10.5a2 2 0 1 0 0 3" })
|
|
10582
|
+
] });
|
|
10583
|
+
}
|
|
10584
|
+
function NotesIcon(props) {
|
|
10585
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
10586
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 20l1-4L16.5 4.5a2.1 2.1 0 0 1 3 3L8 19l-4 1Z" }),
|
|
10587
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 7l3 3" })
|
|
10588
|
+
] });
|
|
10589
|
+
}
|
|
10155
10590
|
function LockIcon(props) {
|
|
10156
10591
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
10157
10592
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
|
|
@@ -10287,6 +10722,9 @@ function UndoIcon(props) {
|
|
|
10287
10722
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" })
|
|
10288
10723
|
] });
|
|
10289
10724
|
}
|
|
10725
|
+
function ChevronUpIcon(props) {
|
|
10726
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 15 6-6 6 6" }) });
|
|
10727
|
+
}
|
|
10290
10728
|
|
|
10291
10729
|
// src/skins/meet/collab.tsx
|
|
10292
10730
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
@@ -11058,7 +11496,7 @@ function BreakoutRun({ plan }) {
|
|
|
11058
11496
|
type: "button",
|
|
11059
11497
|
className: "mx-meet__primary mx-meet__primary--small",
|
|
11060
11498
|
onClick: () => void act({ action: "breakout_close" }).then(show),
|
|
11061
|
-
children: "
|
|
11499
|
+
children: "Close rooms"
|
|
11062
11500
|
}
|
|
11063
11501
|
) : null
|
|
11064
11502
|
] });
|
|
@@ -11076,9 +11514,14 @@ function BreakoutBanner() {
|
|
|
11076
11514
|
const { collab, act, moveTo, localIdentity, breakoutRoomId } = useCollab();
|
|
11077
11515
|
const plan = collab?.breakout ?? null;
|
|
11078
11516
|
const open = plan?.state === "open" ? plan : null;
|
|
11079
|
-
const now = useNow(open?.endsAt != null);
|
|
11517
|
+
const now = useNow(open?.endsAt != null || open?.closingAt != null);
|
|
11080
11518
|
const remaining = breakoutRemainingMs(open, now);
|
|
11519
|
+
const closing = breakoutClosingMs(open, now);
|
|
11081
11520
|
const assigned = assignedRoom(open, localIdentity);
|
|
11521
|
+
const [later, setLater] = (0, import_react9.useState)(null);
|
|
11522
|
+
const [autoSince, setAutoSince] = (0, import_react9.useState)(null);
|
|
11523
|
+
const AUTO_MOVE_MS = 1e4;
|
|
11524
|
+
const autoLeft = open?.moveMode === "auto" && assigned !== null && autoSince?.id === open.id && later !== open.id ? Math.max(0, AUTO_MOVE_MS - (now - autoSince.at)) : null;
|
|
11082
11525
|
const [dismissedBroadcast, setDismissedBroadcast] = (0, import_react9.useState)(null);
|
|
11083
11526
|
const [failure, setFailure] = (0, import_react9.useState)(null);
|
|
11084
11527
|
const expireAsked = (0, import_react9.useRef)(null);
|
|
@@ -11091,6 +11534,19 @@ function BreakoutBanner() {
|
|
|
11091
11534
|
if ((here ?? null) === request.roomId) return;
|
|
11092
11535
|
void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));
|
|
11093
11536
|
}, [moveAt, host, moveTo]);
|
|
11537
|
+
const joinAssigned = async () => {
|
|
11538
|
+
if (assigned === null) return;
|
|
11539
|
+
const said = await act({ action: "breakout_join", roomId: assigned.id });
|
|
11540
|
+
const result = said.ok ? await moveTo(assigned.id) : said;
|
|
11541
|
+
setFailure(result.ok ? null : result.message);
|
|
11542
|
+
};
|
|
11543
|
+
(0, import_react9.useEffect)(() => {
|
|
11544
|
+
if (open === null || open.moveMode !== "auto" || assigned === null || breakoutRoomId !== null) return;
|
|
11545
|
+
if (autoSince?.id !== open.id) setAutoSince({ id: open.id, at: Date.now() });
|
|
11546
|
+
}, [open, assigned, breakoutRoomId, autoSince]);
|
|
11547
|
+
(0, import_react9.useEffect)(() => {
|
|
11548
|
+
if (autoLeft === 0) void joinAssigned();
|
|
11549
|
+
}, [autoLeft]);
|
|
11094
11550
|
(0, import_react9.useEffect)(() => {
|
|
11095
11551
|
if (open === null || remaining === null || remaining > 0) return;
|
|
11096
11552
|
const asked = expireAsked.current;
|
|
@@ -11104,27 +11560,25 @@ function BreakoutBanner() {
|
|
|
11104
11560
|
if (open === null && failure === null) return null;
|
|
11105
11561
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__breakout-banner", role: "status", children: [
|
|
11106
11562
|
failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__collab-error", children: failure }) : null,
|
|
11563
|
+
closing !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { role: "status", "data-meet-notice": "breakout-closing", children: [
|
|
11564
|
+
"Rooms closing in ",
|
|
11565
|
+
Math.ceil(closing / 1e3),
|
|
11566
|
+
room !== null ? " \u2014 you return to the main meeting" : ""
|
|
11567
|
+
] }) : null,
|
|
11107
11568
|
room !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
11108
11569
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: room.name }),
|
|
11109
|
-
remaining !== null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
11570
|
+
remaining !== null && closing === null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
11110
11571
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void moveTo(null), children: "Return to main room" })
|
|
11572
|
+
] }) : assigned !== null && later !== open?.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
11573
|
+
autoLeft !== null ? `You're being moved to ${assigned.name} in ${Math.ceil(autoLeft / 1e3)} s` : `You're invited to ${assigned.name}`,
|
|
11574
|
+
remaining !== null && closing === null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
11575
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void joinAssigned(), children: "Join room" }),
|
|
11576
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => setLater(open?.id ?? null), children: "Later" })
|
|
11111
11577
|
] }) : assigned !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
"button",
|
|
11117
|
-
{
|
|
11118
|
-
type: "button",
|
|
11119
|
-
className: "mx-meet__primary mx-meet__primary--small",
|
|
11120
|
-
onClick: () => void act({ action: "breakout_join", roomId: assigned.id }).then(async (said) => {
|
|
11121
|
-
const result = said.ok ? await moveTo(assigned.id) : said;
|
|
11122
|
-
setFailure(result.ok ? null : result.message);
|
|
11123
|
-
}),
|
|
11124
|
-
children: "Join"
|
|
11125
|
-
}
|
|
11126
|
-
)
|
|
11127
|
-
] }) : open?.allowChoose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Breakout rooms are open \u2014 pick one under Activities." }) : null,
|
|
11578
|
+
assigned.name,
|
|
11579
|
+
" is open",
|
|
11580
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void joinAssigned(), children: "Join room" })
|
|
11581
|
+
] }) : open?.allowChoose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Rooms are open \u2014 pick one under Activities." }) : null,
|
|
11128
11582
|
showBroadcast ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__breakout-broadcast", children: [
|
|
11129
11583
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("strong", { children: [
|
|
11130
11584
|
broadcast.by,
|
|
@@ -11172,6 +11626,33 @@ function CaptionLanguageSettings() {
|
|
|
11172
11626
|
collab?.captionTranslationError != null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.captionTranslationError }) : null
|
|
11173
11627
|
] });
|
|
11174
11628
|
}
|
|
11629
|
+
function CaptionsControl() {
|
|
11630
|
+
const captions = useCaptions();
|
|
11631
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
11632
|
+
ControlButton,
|
|
11633
|
+
{
|
|
11634
|
+
label: captions.on ? "Turn off captions" : "Turn on captions",
|
|
11635
|
+
active: captions.on,
|
|
11636
|
+
pressed: captions.on,
|
|
11637
|
+
onClick: captions.toggle,
|
|
11638
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CaptionsIcon, {})
|
|
11639
|
+
}
|
|
11640
|
+
);
|
|
11641
|
+
}
|
|
11642
|
+
function TakeNotesControl() {
|
|
11643
|
+
const ai = useMeetAi();
|
|
11644
|
+
if (!ai.canControlNoteTaker) return null;
|
|
11645
|
+
const running = ai.noteTaker.state === "joining" || ai.noteTaker.state === "transcribing";
|
|
11646
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
11647
|
+
ControlButton,
|
|
11648
|
+
{
|
|
11649
|
+
label: running ? "Stop taking notes" : "Take notes for me",
|
|
11650
|
+
active: running,
|
|
11651
|
+
onClick: () => void (running ? ai.stopNoteTaker() : ai.startNoteTaker()),
|
|
11652
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NotesIcon, {})
|
|
11653
|
+
}
|
|
11654
|
+
);
|
|
11655
|
+
}
|
|
11175
11656
|
|
|
11176
11657
|
// src/skins/meet/components.tsx
|
|
11177
11658
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
@@ -11222,6 +11703,7 @@ function ControlButton({
|
|
|
11222
11703
|
label,
|
|
11223
11704
|
active = false,
|
|
11224
11705
|
danger = false,
|
|
11706
|
+
pressed,
|
|
11225
11707
|
onClick,
|
|
11226
11708
|
children
|
|
11227
11709
|
}) {
|
|
@@ -11236,26 +11718,49 @@ function ControlButton({
|
|
|
11236
11718
|
].filter(Boolean).join(" "),
|
|
11237
11719
|
"aria-label": label,
|
|
11238
11720
|
title: label,
|
|
11721
|
+
...pressed !== void 0 ? { "aria-pressed": pressed } : {},
|
|
11239
11722
|
onClick,
|
|
11240
11723
|
children
|
|
11241
11724
|
}
|
|
11242
11725
|
);
|
|
11243
11726
|
}
|
|
11244
11727
|
function RecordingIndicator() {
|
|
11245
|
-
const { state, label } = useRecording();
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11258
|
-
);
|
|
11728
|
+
const { state, label, canStart } = useRecording();
|
|
11729
|
+
const snapshot = useMeetSnapshot();
|
|
11730
|
+
const previous = (0, import_react10.useRef)(state);
|
|
11731
|
+
const [stoppedAt, setStoppedAt] = (0, import_react10.useState)(null);
|
|
11732
|
+
(0, import_react10.useEffect)(() => {
|
|
11733
|
+
const was = previous.current;
|
|
11734
|
+
previous.current = state;
|
|
11735
|
+
if ((was === "recording" || was === "starting") && state !== "recording" && state !== "starting") {
|
|
11736
|
+
setStoppedAt(Date.now());
|
|
11737
|
+
const timer = setTimeout(() => setStoppedAt(null), 1e4);
|
|
11738
|
+
return () => clearTimeout(timer);
|
|
11739
|
+
}
|
|
11740
|
+
return void 0;
|
|
11741
|
+
}, [state]);
|
|
11742
|
+
const meetingId = snapshot?.meeting?.id ?? null;
|
|
11743
|
+
if (label === null && stoppedAt === null) return null;
|
|
11744
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11745
|
+
label !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11746
|
+
"span",
|
|
11747
|
+
{
|
|
11748
|
+
className: `mx-meet__recording mx-meet__recording--${state}`,
|
|
11749
|
+
role: "status",
|
|
11750
|
+
"aria-live": "polite",
|
|
11751
|
+
children: [
|
|
11752
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RecordIcon, { width: 10, height: 10 }),
|
|
11753
|
+
label
|
|
11754
|
+
]
|
|
11755
|
+
}
|
|
11756
|
+
) : null,
|
|
11757
|
+
stoppedAt !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__recording", role: "status", "data-meet-notice": "recording-stopped", children: "Recording stopped" }) : null,
|
|
11758
|
+
state === "processing" ? (
|
|
11759
|
+
// rec-processing: the host is told it is being made ready (the indicator says Processing…).
|
|
11760
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__recording", role: "status", "data-meet-notice": "recording-processing", children: "Your recording is being processed. A link appears here when it is ready." })
|
|
11761
|
+
) : null,
|
|
11762
|
+
state === "available" && canStart && meetingId !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "mx-meet__link", href: `/meetings/${meetingId}`, target: "_blank", rel: "noreferrer", children: "View recording" }) : null
|
|
11763
|
+
] });
|
|
11259
11764
|
}
|
|
11260
11765
|
function RoleBadge({
|
|
11261
11766
|
role,
|
|
@@ -11407,6 +11912,7 @@ function ControlBar({ onLeave }) {
|
|
|
11407
11912
|
const controls = useHostControls();
|
|
11408
11913
|
const queue = useHandQueue();
|
|
11409
11914
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
11915
|
+
const live = useLivePolicy();
|
|
11410
11916
|
const unread = useUnreadChat(panel === "chat");
|
|
11411
11917
|
const [whiteboard, setWhiteboard] = (0, import_react10.useState)(false);
|
|
11412
11918
|
const app = useMeetAppPanel();
|
|
@@ -11472,9 +11978,10 @@ function ControlBar({ onLeave }) {
|
|
|
11472
11978
|
) }) }) : null,
|
|
11473
11979
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreakoutBanner, {}),
|
|
11474
11980
|
panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
11981
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SpeakingWhileMutedNudge, {}),
|
|
11475
11982
|
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
11476
11983
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
11477
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11984
|
+
live.micLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11478
11985
|
ControlButton,
|
|
11479
11986
|
{
|
|
11480
11987
|
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
|
|
@@ -11483,7 +11990,8 @@ function ControlBar({ onLeave }) {
|
|
|
11483
11990
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, {})
|
|
11484
11991
|
}
|
|
11485
11992
|
),
|
|
11486
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11993
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AudioSettingsControl, {}),
|
|
11994
|
+
live.cameraLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11487
11995
|
ControlButton,
|
|
11488
11996
|
{
|
|
11489
11997
|
label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
|
|
@@ -11492,6 +12000,7 @@ function ControlBar({ onLeave }) {
|
|
|
11492
12000
|
children: room.local?.cameraEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CameraIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CameraOffIcon, {})
|
|
11493
12001
|
}
|
|
11494
12002
|
),
|
|
12003
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CaptionsControl, {}),
|
|
11495
12004
|
room.canShareScreen || room.local?.screenSharing === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11496
12005
|
ControlButton,
|
|
11497
12006
|
{
|
|
@@ -11556,6 +12065,7 @@ function ControlBar({ onLeave }) {
|
|
|
11556
12065
|
onOpen: openActivity
|
|
11557
12066
|
}
|
|
11558
12067
|
),
|
|
12068
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TakeNotesControl, {}),
|
|
11559
12069
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
|
|
11560
12070
|
recording.canStart ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlButton, { label: "Start recording", onClick: () => void recording.start(), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RecordIcon, {}) }) : null,
|
|
11561
12071
|
recording.canStop ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ControlButton, { label: "Stop recording", active: true, onClick: () => void recording.stop(), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RecordIcon, {}) }) : null,
|
|
@@ -11596,6 +12106,13 @@ function ControlBar({ onLeave }) {
|
|
|
11596
12106
|
]
|
|
11597
12107
|
}
|
|
11598
12108
|
),
|
|
12109
|
+
!room.canShareScreen && room.local?.screenSharing !== true ? (
|
|
12110
|
+
// `share-unsupported-mobile`: the control is absent; the menu says where it lives.
|
|
12111
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__menu-note", role: "note", "data-meet-notice": "share-unsupported", children: [
|
|
12112
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
12113
|
+
" Present from a computer"
|
|
12114
|
+
] })
|
|
12115
|
+
) : null,
|
|
11599
12116
|
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11600
12117
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
11601
12118
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -11611,6 +12128,23 @@ function ControlBar({ onLeave }) {
|
|
|
11611
12128
|
]
|
|
11612
12129
|
}
|
|
11613
12130
|
),
|
|
12131
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12132
|
+
MenuItem,
|
|
12133
|
+
{
|
|
12134
|
+
onSelect: () => {
|
|
12135
|
+
setPopover("none");
|
|
12136
|
+
void controls.run({
|
|
12137
|
+
action: "set_av_lock",
|
|
12138
|
+
value: snapshot?.livePolicy.avLock === "both" ? "off" : "both"
|
|
12139
|
+
});
|
|
12140
|
+
},
|
|
12141
|
+
children: [
|
|
12142
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
12143
|
+
" ",
|
|
12144
|
+
snapshot?.livePolicy.avLock === "both" ? "Turn on attendees' microphone and camera" : "Turn off attendees' microphone and camera"
|
|
12145
|
+
]
|
|
12146
|
+
}
|
|
12147
|
+
),
|
|
11614
12148
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11615
12149
|
MenuItem,
|
|
11616
12150
|
{
|
|
@@ -11853,6 +12387,94 @@ function Toast({ text: text3, failed, onDone }) {
|
|
|
11853
12387
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
11854
12388
|
] });
|
|
11855
12389
|
}
|
|
12390
|
+
function AudioSettingsControl() {
|
|
12391
|
+
const host = useMeetHost();
|
|
12392
|
+
const devices = useDeviceSnapshot();
|
|
12393
|
+
const [open, setOpen] = (0, import_react10.useState)(false);
|
|
12394
|
+
const list = (devices?.devices.audioinput ?? []).filter((device) => device.deviceId.length > 0);
|
|
12395
|
+
if (host === null || list.length < 2) return null;
|
|
12396
|
+
const current = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
12397
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
12398
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12399
|
+
ControlButton,
|
|
12400
|
+
{
|
|
12401
|
+
label: "Audio settings",
|
|
12402
|
+
active: open,
|
|
12403
|
+
onClick: () => {
|
|
12404
|
+
if (!open) void host.devices.refresh();
|
|
12405
|
+
setOpen(!open);
|
|
12406
|
+
},
|
|
12407
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronUpIcon, {})
|
|
12408
|
+
}
|
|
12409
|
+
),
|
|
12410
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Popover, { label: "Microphone", onClose: () => setOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__menu", role: "menu", "aria-label": "Microphone", children: list.map((device) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12411
|
+
"button",
|
|
12412
|
+
{
|
|
12413
|
+
type: "button",
|
|
12414
|
+
role: "menuitemradio",
|
|
12415
|
+
"aria-checked": device.deviceId === current,
|
|
12416
|
+
className: "mx-meet__menu-item",
|
|
12417
|
+
onClick: () => {
|
|
12418
|
+
setOpen(false);
|
|
12419
|
+
host.devices.select("audioinput", device.deviceId);
|
|
12420
|
+
void host.engine.switchDevice("audioinput", device.deviceId).catch(() => void 0);
|
|
12421
|
+
},
|
|
12422
|
+
children: [
|
|
12423
|
+
device.deviceId === current ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CheckIcon, { width: 16, height: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__menu-gap" }),
|
|
12424
|
+
device.label.length > 0 ? device.label : "Microphone"
|
|
12425
|
+
]
|
|
12426
|
+
},
|
|
12427
|
+
device.deviceId
|
|
12428
|
+
)) }) }) : null
|
|
12429
|
+
] });
|
|
12430
|
+
}
|
|
12431
|
+
function SpeakingWhileMutedNudge() {
|
|
12432
|
+
const room = useRoom();
|
|
12433
|
+
const host = useMeetHost();
|
|
12434
|
+
const devices = useDeviceSnapshot();
|
|
12435
|
+
const microphone = useLocalMedia().microphone;
|
|
12436
|
+
const mutedByMe = host !== null && room.local != null && room.local.micEnabled !== true && microphone.unavailable === null;
|
|
12437
|
+
const mic = devices?.selected.audioinput ?? host?.devices.resolve("audioinput") ?? null;
|
|
12438
|
+
const stream = useMicrophoneStream(mutedByMe ? mic : "off");
|
|
12439
|
+
const level = useAudioLevel(stream);
|
|
12440
|
+
const latest = (0, import_react10.useRef)(0);
|
|
12441
|
+
latest.current = level;
|
|
12442
|
+
const [shown, setShown] = (0, import_react10.useState)(false);
|
|
12443
|
+
const [detector] = (0, import_react10.useState)(() => createSpeakingWhileMutedDetector());
|
|
12444
|
+
(0, import_react10.useEffect)(() => {
|
|
12445
|
+
if (!mutedByMe) {
|
|
12446
|
+
detector.reset();
|
|
12447
|
+
setShown(false);
|
|
12448
|
+
return void 0;
|
|
12449
|
+
}
|
|
12450
|
+
const timer = setInterval(() => {
|
|
12451
|
+
if (detector.feed(latest.current, Date.now())) setShown(true);
|
|
12452
|
+
}, 150);
|
|
12453
|
+
return () => clearInterval(timer);
|
|
12454
|
+
}, [mutedByMe, detector]);
|
|
12455
|
+
(0, import_react10.useEffect)(() => {
|
|
12456
|
+
if (!shown) return void 0;
|
|
12457
|
+
const timer = setTimeout(() => setShown(false), 8e3);
|
|
12458
|
+
return () => clearTimeout(timer);
|
|
12459
|
+
}, [shown]);
|
|
12460
|
+
if (!shown || !mutedByMe) return null;
|
|
12461
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "speaking-while-muted", children: [
|
|
12462
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
12463
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "You're muted" }),
|
|
12464
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12465
|
+
"button",
|
|
12466
|
+
{
|
|
12467
|
+
type: "button",
|
|
12468
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
12469
|
+
onClick: () => {
|
|
12470
|
+
setShown(false);
|
|
12471
|
+
void room.toggleMicrophone();
|
|
12472
|
+
},
|
|
12473
|
+
children: "Unmute"
|
|
12474
|
+
}
|
|
12475
|
+
)
|
|
12476
|
+
] });
|
|
12477
|
+
}
|
|
11856
12478
|
function MediaNotices() {
|
|
11857
12479
|
const media = useLocalMedia();
|
|
11858
12480
|
const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
|
|
@@ -11938,9 +12560,45 @@ function MeetingOverlays() {
|
|
|
11938
12560
|
const reactions = useReactions();
|
|
11939
12561
|
const { notice, dismiss } = useModerationNotice();
|
|
11940
12562
|
const room = useRoom();
|
|
12563
|
+
const live = useLivePolicy();
|
|
12564
|
+
const snapshot = useMeetSnapshot();
|
|
12565
|
+
const controls = useHostControls();
|
|
12566
|
+
const isHostNow = useRole() === "host";
|
|
11941
12567
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11942
12568
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ReactionOverlay, { reactions }),
|
|
11943
12569
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MediaNotices, {}),
|
|
12570
|
+
snapshot !== null && snapshot.phase === "in_call" && snapshot.participants.length >= 2 && !snapshot.participants.some((p) => p.role === "host" || p.role === "cohost") ? (
|
|
12571
|
+
// meeting-without-host: the call goes on; host-only controls are simply not on screen.
|
|
12572
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "host-absent", children: "The host has left the meeting. Host controls are not available until a host is back." })
|
|
12573
|
+
) : null,
|
|
12574
|
+
live.consent !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__sheet-backdrop", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__sheet", role: "alertdialog", "aria-label": "This meeting is being recorded", "data-meet-notice": "recording-consent", children: [
|
|
12575
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "mx-meet__panel-title", children: "This meeting is being recorded" }),
|
|
12576
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__confirm-text", children: live.consent === "ok_or_leave" ? "By staying you agree to be recorded. You can leave now if you do not agree." : "Until you agree to the recording you can watch and listen, but not use your microphone or camera." }),
|
|
12577
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
12578
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__send", onClick: live.agree, children: live.consent === "ok_or_leave" ? "OK" : "I agree" }),
|
|
12579
|
+
live.consent === "ok_or_leave" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => void room.leave(), children: "Leave meeting" }) : null
|
|
12580
|
+
] })
|
|
12581
|
+
] }) }) : null,
|
|
12582
|
+
snapshot?.canReclaim === true && !isHostNow ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "host-reclaim", children: [
|
|
12583
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "You started this meeting. Take back the host?" }),
|
|
12584
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
12585
|
+
"button",
|
|
12586
|
+
{
|
|
12587
|
+
type: "button",
|
|
12588
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
12589
|
+
onClick: () => void controls.run({ action: "reclaim_host" }),
|
|
12590
|
+
children: "Reclaim host"
|
|
12591
|
+
}
|
|
12592
|
+
)
|
|
12593
|
+
] }) : null,
|
|
12594
|
+
live.micLocked || live.cameraLocked ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "mic-locked", children: live.micLocked && live.cameraLocked ? "Your microphone and camera are turned off by the organizer." : live.micLocked ? "Your microphone is turned off by the organizer." : "Your camera is turned off by the organizer." }) : null,
|
|
12595
|
+
live.timeWarning && live.minutesLeft !== null && live.minutesLeft > 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "time-warning", children: [
|
|
12596
|
+
"This meeting will end in ",
|
|
12597
|
+
live.minutesLeft,
|
|
12598
|
+
" ",
|
|
12599
|
+
live.minutesLeft === 1 ? "minute" : "minutes",
|
|
12600
|
+
" (time limit)."
|
|
12601
|
+
] }) : null,
|
|
11944
12602
|
notice !== null && notice.kind !== "removed" ? (
|
|
11945
12603
|
// A host's request ("the host is asking you to unmute"), not an error:
|
|
11946
12604
|
// an assertive status, so it is announced at once without being filed
|
|
@@ -12031,6 +12689,7 @@ function ChatPanel({
|
|
|
12031
12689
|
}) {
|
|
12032
12690
|
const snapshot = useMeetSnapshot();
|
|
12033
12691
|
const room = useRoom();
|
|
12692
|
+
const live = useLivePolicy();
|
|
12034
12693
|
const [draft, setDraft] = (0, import_react10.useState)("");
|
|
12035
12694
|
const [recipient, setRecipient] = (0, import_react10.useState)(to);
|
|
12036
12695
|
const listRef = (0, import_react10.useRef)(null);
|
|
@@ -12055,7 +12714,7 @@ function ChatPanel({
|
|
|
12055
12714
|
snapshot?.chatLoaded !== true && messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { className: "mx-meet__panel-note", children: "Reading earlier messages\u2026" }) : messages.length === 0 && snapshot?.chatHistoryError == null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { className: "mx-meet__panel-note", children: "No messages yet. Messages to everyone stay with the meeting, so people who join later see them." }) : null,
|
|
12056
12715
|
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChatLine, { message, local, people }, message.id))
|
|
12057
12716
|
] }),
|
|
12058
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12717
|
+
!live.canChat ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__panel-note", role: "status", "data-meet-notice": "chat-restricted", children: live.chatMode === "off" ? "Chat is turned off for this meeting." : "Chat is limited to the host and co-hosts in this meeting." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12059
12718
|
"form",
|
|
12060
12719
|
{
|
|
12061
12720
|
className: "mx-meet__composer mx-meet__composer--chat",
|
|
@@ -12427,6 +13086,13 @@ function PersonMenu({
|
|
|
12427
13086
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
12428
13087
|
" Make co-host"
|
|
12429
13088
|
] }) : null,
|
|
13089
|
+
!participant.isAgent && participant.role !== "cohost" && participant.role !== "host" ? participant.role === "presenter" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(MenuItem, { onSelect: () => act({ action: "revoke_presenter", identity: participant.identity }), children: [
|
|
13090
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
13091
|
+
" Remove presenter"
|
|
13092
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(MenuItem, { onSelect: () => act({ action: "make_presenter", identity: participant.identity }), children: [
|
|
13093
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
13094
|
+
" Make presenter"
|
|
13095
|
+
] }) : null,
|
|
12430
13096
|
!participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
12431
13097
|
MenuItem,
|
|
12432
13098
|
{
|
|
@@ -12658,14 +13324,20 @@ async function playTestSound(sinkId) {
|
|
|
12658
13324
|
audio.pause();
|
|
12659
13325
|
await context.close();
|
|
12660
13326
|
}
|
|
12661
|
-
function useMicrophoneStream(deviceId2) {
|
|
13327
|
+
function useMicrophoneStream(deviceId2, report, attempt = 0) {
|
|
12662
13328
|
const [stream, setStream] = (0, import_react10.useState)(null);
|
|
13329
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
13330
|
+
reportRef.current = report;
|
|
12663
13331
|
(0, import_react10.useEffect)(() => {
|
|
12664
|
-
if (deviceId2 === "off")
|
|
13332
|
+
if (deviceId2 === "off") {
|
|
13333
|
+
reportRef.current?.("off");
|
|
13334
|
+
return void 0;
|
|
13335
|
+
}
|
|
12665
13336
|
let live = true;
|
|
12666
13337
|
let opened = null;
|
|
12667
13338
|
const media = globalThis.navigator?.mediaDevices;
|
|
12668
13339
|
if (media === void 0) return void 0;
|
|
13340
|
+
reportRef.current?.("prompting");
|
|
12669
13341
|
void media.getUserMedia({ audio: deviceId2 === null ? true : { deviceId: { exact: deviceId2 } } }).then((next) => {
|
|
12670
13342
|
if (!live) {
|
|
12671
13343
|
next.getTracks().forEach((track) => track.stop());
|
|
@@ -12673,15 +13345,49 @@ function useMicrophoneStream(deviceId2) {
|
|
|
12673
13345
|
}
|
|
12674
13346
|
opened = next;
|
|
12675
13347
|
setStream(next);
|
|
12676
|
-
|
|
13348
|
+
reportRef.current?.("on");
|
|
13349
|
+
}).catch((thrown) => {
|
|
13350
|
+
setStream(null);
|
|
13351
|
+
if (live) reportRef.current?.({ error: thrown });
|
|
13352
|
+
});
|
|
12677
13353
|
return () => {
|
|
12678
13354
|
live = false;
|
|
12679
13355
|
opened?.getTracks().forEach((track) => track.stop());
|
|
12680
13356
|
setStream(null);
|
|
12681
13357
|
};
|
|
12682
|
-
}, [deviceId2]);
|
|
13358
|
+
}, [deviceId2, attempt]);
|
|
12683
13359
|
return stream;
|
|
12684
13360
|
}
|
|
13361
|
+
function PreviewStatus({ onRetry }) {
|
|
13362
|
+
const { region, where } = useMediaSupervisor();
|
|
13363
|
+
const [waited, setWaited] = (0, import_react10.useState)(false);
|
|
13364
|
+
const prompting = region.preview.microphone.state === "prompting" || region.preview.camera.state === "prompting";
|
|
13365
|
+
(0, import_react10.useEffect)(() => {
|
|
13366
|
+
if (!prompting) {
|
|
13367
|
+
setWaited(false);
|
|
13368
|
+
return void 0;
|
|
13369
|
+
}
|
|
13370
|
+
const timer = setTimeout(() => setWaited(true), 800);
|
|
13371
|
+
return () => clearTimeout(timer);
|
|
13372
|
+
}, [prompting]);
|
|
13373
|
+
const refused = ["microphone", "camera"].flatMap((kind) => {
|
|
13374
|
+
const preview = region.preview[kind];
|
|
13375
|
+
const reason = region.permission[kind] === "denied" ? "denied" : preview.state === "unavailable" ? preview.unavailable?.reason ?? "failed" : null;
|
|
13376
|
+
return reason === null ? [] : [{ kind, reason }];
|
|
13377
|
+
});
|
|
13378
|
+
if (refused.length === 0) {
|
|
13379
|
+
if (!prompting || !waited) return null;
|
|
13380
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__panel-note", role: "status", "data-meet-notice": "permission-prompt", children: "Allow camera and microphone when your browser asks" });
|
|
13381
|
+
}
|
|
13382
|
+
const first = refused[0];
|
|
13383
|
+
const both = refused.length === 2 && refused[0].reason === refused[1].reason;
|
|
13384
|
+
const label = both ? deviceChipLabel("camera", first.reason).replace(/^Camera/, "Camera and mic") : refused.map((r) => deviceChipLabel(r.kind, r.reason)).join(" \xB7 ");
|
|
13385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__warning", role: "status", "data-meet-notice": deviceNoticeCode(first.kind, first.reason), children: [
|
|
13386
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: label }),
|
|
13387
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { className: "mx-meet__fix-steps", children: deviceFixSteps(first.kind, first.reason, where).map((step) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: step }, step)) }),
|
|
13388
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, children: "Try again" })
|
|
13389
|
+
] });
|
|
13390
|
+
}
|
|
12685
13391
|
function KnockNotice({ onView }) {
|
|
12686
13392
|
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
12687
13393
|
if (knocker === null) return null;
|
|
@@ -12932,8 +13638,19 @@ function Captions({ lines = 3 }) {
|
|
|
12932
13638
|
const ai = useMeetAi();
|
|
12933
13639
|
const visible = ai.transcript.slice(-lines);
|
|
12934
13640
|
const translation = useCaptionTranslation(visible);
|
|
12935
|
-
|
|
12936
|
-
|
|
13641
|
+
const captions = useCaptions();
|
|
13642
|
+
if (!captions.on) return null;
|
|
13643
|
+
if (captions.state === "unavailable") {
|
|
13644
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__caption", children: [
|
|
13645
|
+
"Captions are unavailable. ",
|
|
13646
|
+
captions.reason,
|
|
13647
|
+
captions.remedy !== null ? ` ${captions.remedy}` : ""
|
|
13648
|
+
] }) });
|
|
13649
|
+
}
|
|
13650
|
+
if (visible.length === 0) {
|
|
13651
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": captions.state, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__caption mx-meet__caption--interim", children: captions.state === "starting" ? "Starting captions. They appear as people speak." : "Captions are on. They appear as people speak." }) });
|
|
13652
|
+
}
|
|
13653
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": "live", children: [
|
|
12937
13654
|
visible.map((line) => {
|
|
12938
13655
|
const translated = translation.language === null ? void 0 : translation.translations[line.id];
|
|
12939
13656
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -12956,7 +13673,7 @@ function Captions({ lines = 3 }) {
|
|
|
12956
13673
|
function ConsentNotice() {
|
|
12957
13674
|
const notice = useConsentNotice();
|
|
12958
13675
|
if (notice === null) return null;
|
|
12959
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__consent", role: "status", children: notice });
|
|
13676
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__consent", role: "status", "data-meet-notice": "notes-started", children: notice });
|
|
12960
13677
|
}
|
|
12961
13678
|
function AttendanceNotice() {
|
|
12962
13679
|
const media = useLocalMedia();
|
|
@@ -13000,8 +13717,15 @@ function PreJoinBody({
|
|
|
13000
13717
|
const [joining, setJoining] = (0, import_react10.useState)(false);
|
|
13001
13718
|
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
13002
13719
|
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
13003
|
-
const
|
|
13004
|
-
const
|
|
13720
|
+
const { reportPreview } = useMediaSupervisor();
|
|
13721
|
+
const [attempt, setAttempt] = (0, import_react10.useState)(0);
|
|
13722
|
+
const preview = useCameraPreview(
|
|
13723
|
+
mode === "video" ? camera : void 0,
|
|
13724
|
+
effects.effects,
|
|
13725
|
+
(outcome) => reportPreview("camera", outcome),
|
|
13726
|
+
attempt
|
|
13727
|
+
);
|
|
13728
|
+
const micStream = useMicrophoneStream(microphone ? mic : "off", (outcome) => reportPreview("microphone", outcome), attempt);
|
|
13005
13729
|
const level = useAudioLevel(micStream);
|
|
13006
13730
|
const [speakerTesting, setSpeakerTesting] = (0, import_react10.useState)(false);
|
|
13007
13731
|
(0, import_react10.useEffect)(() => {
|
|
@@ -13045,6 +13769,7 @@ function PreJoinBody({
|
|
|
13045
13769
|
] }),
|
|
13046
13770
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
13047
13771
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
13772
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PreviewStatus, { onRetry: () => setAttempt((n) => n + 1) }),
|
|
13048
13773
|
notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
13049
13774
|
devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
13050
13775
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
@@ -13147,16 +13872,22 @@ function PreJoinBody({
|
|
|
13147
13872
|
] })
|
|
13148
13873
|
] });
|
|
13149
13874
|
}
|
|
13150
|
-
function useCameraPreview(deviceId2, effects) {
|
|
13875
|
+
function useCameraPreview(deviceId2, effects, report, attempt = 0) {
|
|
13876
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
13877
|
+
reportRef.current = report;
|
|
13151
13878
|
const [preview, setPreview] = (0, import_react10.useState)(null);
|
|
13152
13879
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
13153
13880
|
const current = (0, import_react10.useRef)(null);
|
|
13154
13881
|
const initialEffect = (0, import_react10.useRef)(toVideoEffect(effects));
|
|
13155
13882
|
initialEffect.current = toVideoEffect(effects);
|
|
13156
13883
|
(0, import_react10.useEffect)(() => {
|
|
13157
|
-
if (deviceId2 === void 0)
|
|
13884
|
+
if (deviceId2 === void 0) {
|
|
13885
|
+
reportRef.current?.("off");
|
|
13886
|
+
return void 0;
|
|
13887
|
+
}
|
|
13158
13888
|
let live = true;
|
|
13159
13889
|
setError(null);
|
|
13890
|
+
reportRef.current?.("prompting");
|
|
13160
13891
|
void createCameraPreview(deviceId2, initialEffect.current).then((created) => {
|
|
13161
13892
|
if (!live) {
|
|
13162
13893
|
created.stop();
|
|
@@ -13164,8 +13895,10 @@ function useCameraPreview(deviceId2, effects) {
|
|
|
13164
13895
|
}
|
|
13165
13896
|
current.current = created;
|
|
13166
13897
|
setPreview(created);
|
|
13898
|
+
reportRef.current?.("on");
|
|
13167
13899
|
}).catch((thrown) => {
|
|
13168
13900
|
if (!live) return;
|
|
13901
|
+
reportRef.current?.({ error: thrown });
|
|
13169
13902
|
const failure = thrown;
|
|
13170
13903
|
const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
|
|
13171
13904
|
const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
|
|
@@ -13177,7 +13910,7 @@ function useCameraPreview(deviceId2, effects) {
|
|
|
13177
13910
|
current.current = null;
|
|
13178
13911
|
setPreview(null);
|
|
13179
13912
|
};
|
|
13180
|
-
}, [deviceId2]);
|
|
13913
|
+
}, [deviceId2, attempt]);
|
|
13181
13914
|
return {
|
|
13182
13915
|
preview,
|
|
13183
13916
|
error,
|
|
@@ -13317,6 +14050,14 @@ function MeetSkinRoom({
|
|
|
13317
14050
|
}
|
|
13318
14051
|
function ShareNotice() {
|
|
13319
14052
|
const { share, actions } = useMeetCore();
|
|
14053
|
+
const [soundHintClosed, setSoundHintClosed] = (0, import_react10.useState)(null);
|
|
14054
|
+
if (share.state === "presenting" && share.audio === false && soundHintClosed !== share.at) {
|
|
14055
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-audio-missing", children: [
|
|
14056
|
+
"Sound not shared",
|
|
14057
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__remedy", children: "To include sound, share a tab and turn on Also share tab audio" }),
|
|
14058
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setSoundHintClosed(share.at), children: "Dismiss" })
|
|
14059
|
+
] });
|
|
14060
|
+
}
|
|
13320
14061
|
if (share.state !== "refused") return null;
|
|
13321
14062
|
const text3 = share.reason === "unsupported" ? "You can't share your screen from this browser" : share.reason === "blocked" ? "Screen sharing is blocked" : "Couldn't start sharing your screen";
|
|
13322
14063
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-unavailable", children: [
|
|
@@ -13429,6 +14170,27 @@ function ExitScreen({ onLeave }) {
|
|
|
13429
14170
|
] });
|
|
13430
14171
|
break;
|
|
13431
14172
|
case "disconnected":
|
|
14173
|
+
if (failure?.stage === "join" && (failure.reason ?? null) === null) {
|
|
14174
|
+
title = "Can't connect to the meeting";
|
|
14175
|
+
line = connection === "offline" ? "You're offline. Check your internet connection." : "Check your network. A VPN, firewall or proxy may be blocking the call.";
|
|
14176
|
+
notice = "cannot-connect";
|
|
14177
|
+
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
14178
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
14179
|
+
import_design_system.Button,
|
|
14180
|
+
{
|
|
14181
|
+
type: "button",
|
|
14182
|
+
disabled: busy,
|
|
14183
|
+
onClick: () => {
|
|
14184
|
+
setBusy(true);
|
|
14185
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
14186
|
+
},
|
|
14187
|
+
children: "Try again"
|
|
14188
|
+
}
|
|
14189
|
+
),
|
|
14190
|
+
home
|
|
14191
|
+
] });
|
|
14192
|
+
break;
|
|
14193
|
+
}
|
|
13432
14194
|
title = "You lost your connection to the meeting";
|
|
13433
14195
|
line = connection === "offline" ? "You're offline. You'll rejoin automatically when your internet is back." : connection === "server_unreachable" ? "Can't reach the meeting server. You'll rejoin automatically when it answers." : failure?.remedy ?? "Check your internet connection, then rejoin.";
|
|
13434
14196
|
notice = "disconnected";
|