@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/skins/meet.cjs
CHANGED
|
@@ -44,9 +44,11 @@ __export(meet_exports, {
|
|
|
44
44
|
CameraOffIcon: () => CameraOffIcon,
|
|
45
45
|
CaptionLanguageSettings: () => CaptionLanguageSettings,
|
|
46
46
|
Captions: () => Captions,
|
|
47
|
+
CaptionsIcon: () => CaptionsIcon,
|
|
47
48
|
ChatIcon: () => ChatIcon,
|
|
48
49
|
ChatPanel: () => ChatPanel,
|
|
49
50
|
CheckIcon: () => CheckIcon,
|
|
51
|
+
ChevronUpIcon: () => ChevronUpIcon,
|
|
50
52
|
CloseIcon: () => CloseIcon,
|
|
51
53
|
ConnectionDot: () => ConnectionDot,
|
|
52
54
|
ConsentNotice: () => ConsentNotice,
|
|
@@ -67,6 +69,7 @@ __export(meet_exports, {
|
|
|
67
69
|
MicIcon: () => MicIcon,
|
|
68
70
|
MicOffIcon: () => MicOffIcon,
|
|
69
71
|
MoreIcon: () => MoreIcon,
|
|
72
|
+
NotesIcon: () => NotesIcon,
|
|
70
73
|
PanelShell: () => PanelShell,
|
|
71
74
|
ParticipantTile: () => ParticipantTile,
|
|
72
75
|
PencilIcon: () => PencilIcon,
|
|
@@ -265,6 +268,8 @@ var MEET_ROUTES = {
|
|
|
265
268
|
noteTakerStop: "/api/v1/meet/agent/stop",
|
|
266
269
|
/** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
|
|
267
270
|
intelligenceAsk: "/api/v1/meet/intelligence/ask",
|
|
271
|
+
/** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
|
|
272
|
+
noteTakerCaptions: "/api/v1/meet/agent/captions",
|
|
268
273
|
/** A host ends the meeting for everyone. Served by MRI-A6. */
|
|
269
274
|
meetingEnd: "/api/v1/meet/end",
|
|
270
275
|
/**
|
|
@@ -360,12 +365,39 @@ function recordingLabel(state) {
|
|
|
360
365
|
}
|
|
361
366
|
}
|
|
362
367
|
|
|
368
|
+
// src/core/live-policy.ts
|
|
369
|
+
var DEFAULT_LIVE_POLICY = {
|
|
370
|
+
chatMode: "everyone",
|
|
371
|
+
avLock: "off",
|
|
372
|
+
timeLimitMinutes: 0,
|
|
373
|
+
timeLimitWarningMinutes: 5,
|
|
374
|
+
recordingConsent: "notice"
|
|
375
|
+
};
|
|
376
|
+
var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
377
|
+
function isLockedFor(policy, role, kind) {
|
|
378
|
+
if (MODERATORS.has(role)) return false;
|
|
379
|
+
return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
|
|
380
|
+
}
|
|
381
|
+
function consentPending(policy, role, recordingOn, given) {
|
|
382
|
+
return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
|
|
383
|
+
}
|
|
384
|
+
function canChat(policy, role) {
|
|
385
|
+
if (policy.chatMode === "everyone") return true;
|
|
386
|
+
return policy.chatMode === "hosts_only" && MODERATORS.has(role);
|
|
387
|
+
}
|
|
388
|
+
function minutesLeft(policy, startedAtMs, nowMs) {
|
|
389
|
+
if (policy.timeLimitMinutes <= 0 || startedAtMs === null) return null;
|
|
390
|
+
return Math.ceil((startedAtMs + policy.timeLimitMinutes * 6e4 - nowMs) / 6e4);
|
|
391
|
+
}
|
|
392
|
+
|
|
363
393
|
// src/core/attendance.ts
|
|
394
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
364
395
|
var IDLE_MEDIA_REGION = {
|
|
365
396
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
366
397
|
audioOutput: "ok",
|
|
367
398
|
notice: null,
|
|
368
|
-
offer: null
|
|
399
|
+
offer: null,
|
|
400
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
369
401
|
};
|
|
370
402
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
371
403
|
var IDLE_MEDIA = {
|
|
@@ -452,9 +484,17 @@ function autoAssign(identities, rooms, exclude = []) {
|
|
|
452
484
|
return out;
|
|
453
485
|
}
|
|
454
486
|
function breakoutRemainingMs(plan, now) {
|
|
455
|
-
if (plan === null || plan.state !== "open"
|
|
456
|
-
const
|
|
457
|
-
|
|
487
|
+
if (plan === null || plan.state !== "open") return null;
|
|
488
|
+
const ends = [plan.endsAt, plan.closingAt].map((stamp) => stamp === null ? Number.NaN : Date.parse(stamp)).filter((time) => !Number.isNaN(time));
|
|
489
|
+
if (ends.length === 0) return null;
|
|
490
|
+
return Math.max(0, Math.min(...ends) - now);
|
|
491
|
+
}
|
|
492
|
+
var BREAKOUT_COUNTDOWN_MS = 3e4;
|
|
493
|
+
function breakoutClosingMs(plan, now) {
|
|
494
|
+
if (plan === null || plan.state !== "open") return null;
|
|
495
|
+
const remaining = breakoutRemainingMs(plan, now);
|
|
496
|
+
if (remaining === null) return null;
|
|
497
|
+
return plan.closingAt !== null || remaining <= BREAKOUT_COUNTDOWN_MS ? remaining : null;
|
|
458
498
|
}
|
|
459
499
|
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
460
500
|
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
@@ -578,7 +618,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
578
618
|
case "safari":
|
|
579
619
|
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"];
|
|
580
620
|
default:
|
|
581
|
-
return ["Click the icon
|
|
621
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
582
622
|
}
|
|
583
623
|
case "blocked_os": {
|
|
584
624
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -662,14 +702,31 @@ function webMediaPlatform() {
|
|
|
662
702
|
|
|
663
703
|
// src/core/observation.ts
|
|
664
704
|
function observedRole(role, standing) {
|
|
665
|
-
if (role === "host" || role === "cohost") return role;
|
|
705
|
+
if (role === "host" || role === "cohost" || role === "presenter") return role;
|
|
666
706
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
667
707
|
}
|
|
668
|
-
function observedDevice(track, permission = "unknown") {
|
|
708
|
+
function observedDevice(track, permission = "unknown", held = null) {
|
|
709
|
+
if (held === "locked") return "locked";
|
|
669
710
|
if (track.enabled) return "on";
|
|
711
|
+
if (held === "muted_by_host") return "muted_by_host";
|
|
670
712
|
const reason = deviceUnavailableReason(track, permission);
|
|
671
713
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
672
714
|
}
|
|
715
|
+
function observedPreview(preview, permission = "unknown") {
|
|
716
|
+
if (permission === "denied") return "unavailable:denied";
|
|
717
|
+
switch (preview.state) {
|
|
718
|
+
case "on":
|
|
719
|
+
return "on";
|
|
720
|
+
case "off":
|
|
721
|
+
return "muted_by_me";
|
|
722
|
+
case "prompting":
|
|
723
|
+
return "prompting";
|
|
724
|
+
case "unavailable":
|
|
725
|
+
return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
|
|
726
|
+
default:
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
673
730
|
function endedRecord(snapshot, given) {
|
|
674
731
|
const record = snapshot?.meeting ?? given;
|
|
675
732
|
return record !== null && record.endedAt !== null;
|
|
@@ -693,20 +750,34 @@ function observe(input) {
|
|
|
693
750
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
694
751
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
695
752
|
const deviceInPlay = phase === "joining" || inCall;
|
|
753
|
+
const region = snapshot?.mediaRegion;
|
|
754
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
696
755
|
return {
|
|
697
756
|
phase,
|
|
698
757
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
699
758
|
role,
|
|
700
|
-
|
|
759
|
+
// `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
|
|
760
|
+
// person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
|
|
761
|
+
connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
|
|
701
762
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
702
763
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
703
764
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
704
765
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
705
766
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
706
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
707
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
767
|
+
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,
|
|
768
|
+
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
|
|
708
769
|
};
|
|
709
770
|
}
|
|
771
|
+
function lockedFor(snapshot, kind, inCall) {
|
|
772
|
+
if (!inCall) return null;
|
|
773
|
+
const role = effectiveRole(snapshot);
|
|
774
|
+
if (isLockedFor(snapshot.livePolicy, role, kind)) return "locked";
|
|
775
|
+
if (snapshot.livePolicy.recordingConsent === "view_only_until_agree" && consentPending(snapshot.livePolicy, role, snapshot.recording === "recording", snapshot.recordingConsentGiven)) {
|
|
776
|
+
return "locked";
|
|
777
|
+
}
|
|
778
|
+
if (kind === "microphone" && snapshot.mutedByHost) return "muted_by_host";
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
710
781
|
function staticObservation(phase, reason = null) {
|
|
711
782
|
return {
|
|
712
783
|
phase,
|
|
@@ -796,7 +867,7 @@ function exactNoteTakerParticipant(meeting, participants) {
|
|
|
796
867
|
}
|
|
797
868
|
function noteTakerStatus(args) {
|
|
798
869
|
const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
|
|
799
|
-
const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
|
|
870
|
+
const raw = args.meeting?.metadata?.[NOTE_TAKER_METADATA_KEY];
|
|
800
871
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
801
872
|
const recorded = readState(row["state"]);
|
|
802
873
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
@@ -1218,7 +1289,8 @@ function useRoom() {
|
|
|
1218
1289
|
failure: snapshot?.failure ?? null,
|
|
1219
1290
|
secondDevice: snapshot?.secondDevice ?? null,
|
|
1220
1291
|
local,
|
|
1221
|
-
|
|
1292
|
+
// An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
|
|
1293
|
+
canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null),
|
|
1222
1294
|
join: (0, import_react5.useCallback)(
|
|
1223
1295
|
async (request) => requireEngine(engine, "join").join(request),
|
|
1224
1296
|
[engine]
|
|
@@ -1549,6 +1621,45 @@ function useMeetAi() {
|
|
|
1549
1621
|
}, [ai, meeting, dispatchNoteTaker])
|
|
1550
1622
|
};
|
|
1551
1623
|
}
|
|
1624
|
+
function useCaptions() {
|
|
1625
|
+
const host = useMeetHost();
|
|
1626
|
+
const snapshot = useMeetSnapshot();
|
|
1627
|
+
const ai = useMeetAi();
|
|
1628
|
+
const on = snapshot?.captionsOn === true;
|
|
1629
|
+
const refusal = snapshot?.captionsRefusal ?? null;
|
|
1630
|
+
const taker = ai.noteTaker;
|
|
1631
|
+
const meeting = snapshot?.meeting ?? null;
|
|
1632
|
+
const request = (0, import_react5.useCallback)(() => {
|
|
1633
|
+
if (host === null || meeting === null) return;
|
|
1634
|
+
void host.ai.requestCaptions({ meetingId: meeting.id });
|
|
1635
|
+
}, [host, meeting]);
|
|
1636
|
+
const toggle = (0, import_react5.useCallback)(() => {
|
|
1637
|
+
if (host === null) return;
|
|
1638
|
+
const next = !on;
|
|
1639
|
+
host.store.patch({ captionsOn: next, captionsRefusal: null });
|
|
1640
|
+
if (next && taker.state !== "joining" && taker.state !== "transcribing") request();
|
|
1641
|
+
}, [host, on, taker.state, request]);
|
|
1642
|
+
let state = "off";
|
|
1643
|
+
let reason = null;
|
|
1644
|
+
let remedy = null;
|
|
1645
|
+
if (on) {
|
|
1646
|
+
if (taker.state === "transcribing" && taker.present) {
|
|
1647
|
+
state = "live";
|
|
1648
|
+
} else if (taker.state === "joining") {
|
|
1649
|
+
state = "starting";
|
|
1650
|
+
} else if (refusal !== null && refusal.reason.length > 0) {
|
|
1651
|
+
state = "unavailable";
|
|
1652
|
+
reason = refusal.reason;
|
|
1653
|
+
remedy = refusal.remedy.length > 0 ? refusal.remedy : null;
|
|
1654
|
+
} else if (taker.state === "ended" || taker.state === "handoff_pending") {
|
|
1655
|
+
state = "unavailable";
|
|
1656
|
+
reason = noteTakerLabel(taker);
|
|
1657
|
+
} else {
|
|
1658
|
+
state = "starting";
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return { on, state, reason, remedy, toggle };
|
|
1662
|
+
}
|
|
1552
1663
|
function useEndMeeting() {
|
|
1553
1664
|
const host = useMeetHost();
|
|
1554
1665
|
const snapshot = useMeetSnapshot();
|
|
@@ -1586,7 +1697,7 @@ function useConsentNotice() {
|
|
|
1586
1697
|
const snapshot = useMeetSnapshot();
|
|
1587
1698
|
const agent = noteTakerParticipant(snapshot?.participants ?? []);
|
|
1588
1699
|
if (agent === null) return null;
|
|
1589
|
-
return
|
|
1700
|
+
return `AI notes are on. ${agent.displayName} is in this meeting, transcribing what is said and writing notes everyone here can read.`;
|
|
1590
1701
|
}
|
|
1591
1702
|
function useMediaSupervisor() {
|
|
1592
1703
|
const host = useMeetHost();
|
|
@@ -1605,6 +1716,12 @@ function useMediaSupervisor() {
|
|
|
1605
1716
|
retry: (0, import_react5.useCallback)(
|
|
1606
1717
|
async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
|
|
1607
1718
|
[engine]
|
|
1719
|
+
),
|
|
1720
|
+
reportPreview: (0, import_react5.useCallback)(
|
|
1721
|
+
(kind, outcome) => {
|
|
1722
|
+
void engine?.previewDevice(kind, outcome);
|
|
1723
|
+
},
|
|
1724
|
+
[engine]
|
|
1608
1725
|
)
|
|
1609
1726
|
};
|
|
1610
1727
|
}
|
|
@@ -1834,6 +1951,38 @@ function useGuestRecordAccess(meeting) {
|
|
|
1834
1951
|
)
|
|
1835
1952
|
};
|
|
1836
1953
|
}
|
|
1954
|
+
function useLivePolicy() {
|
|
1955
|
+
const snapshot = useMeetSnapshot();
|
|
1956
|
+
const role = useRole();
|
|
1957
|
+
const [now, setNow] = (0, import_react5.useState)(() => Date.now());
|
|
1958
|
+
const limited = (snapshot?.livePolicy.timeLimitMinutes ?? 0) > 0;
|
|
1959
|
+
(0, import_react5.useEffect)(() => {
|
|
1960
|
+
if (!limited) return void 0;
|
|
1961
|
+
const timer = setInterval(() => setNow(Date.now()), 1e3);
|
|
1962
|
+
return () => clearInterval(timer);
|
|
1963
|
+
}, [limited]);
|
|
1964
|
+
const policy = snapshot?.livePolicy ?? DEFAULT_LIVE_POLICY;
|
|
1965
|
+
const left = minutesLeft(policy, snapshot?.sessionStartedAt ?? null, now);
|
|
1966
|
+
const host = useMeetHost();
|
|
1967
|
+
const pending = consentPending(policy, role, snapshot?.recording === "recording", snapshot?.recordingConsentGiven === true);
|
|
1968
|
+
const viewOnly = pending && policy.recordingConsent === "view_only_until_agree";
|
|
1969
|
+
const idle = snapshot !== null && snapshot.recording !== "recording" && snapshot.recording !== "starting" && snapshot.recording !== "stopping";
|
|
1970
|
+
const given = snapshot?.recordingConsentGiven === true;
|
|
1971
|
+
const store = host?.store ?? null;
|
|
1972
|
+
(0, import_react5.useEffect)(() => {
|
|
1973
|
+
if (idle && given) store?.patch({ recordingConsentGiven: false });
|
|
1974
|
+
}, [idle, given, store]);
|
|
1975
|
+
return {
|
|
1976
|
+
canChat: canChat(policy, role),
|
|
1977
|
+
chatMode: policy.chatMode,
|
|
1978
|
+
micLocked: isLockedFor(policy, role, "microphone") || viewOnly,
|
|
1979
|
+
cameraLocked: isLockedFor(policy, role, "camera") || viewOnly,
|
|
1980
|
+
consent: pending ? policy.recordingConsent === "ok_or_leave" ? "ok_or_leave" : "view_only_until_agree" : null,
|
|
1981
|
+
agree: () => store?.patch({ recordingConsentGiven: true }),
|
|
1982
|
+
minutesLeft: left,
|
|
1983
|
+
timeWarning: left !== null && left <= policy.timeLimitWarningMinutes
|
|
1984
|
+
};
|
|
1985
|
+
}
|
|
1837
1986
|
|
|
1838
1987
|
// src/react/collab-hooks.ts
|
|
1839
1988
|
var import_react7 = require("react");
|
|
@@ -1893,6 +2042,39 @@ function useCollabPanelRequest(onRequest) {
|
|
|
1893
2042
|
// src/core/reactions.ts
|
|
1894
2043
|
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
1895
2044
|
|
|
2045
|
+
// src/core/speaking-detector.ts
|
|
2046
|
+
function createSpeakingWhileMutedDetector(options = {}) {
|
|
2047
|
+
const threshold = options.threshold ?? 0.06;
|
|
2048
|
+
const holdMs = options.holdMs ?? 600;
|
|
2049
|
+
const gapMs = options.gapMs ?? 400;
|
|
2050
|
+
const cooldownMs = options.cooldownMs ?? 2e4;
|
|
2051
|
+
let speechStart = null;
|
|
2052
|
+
let lastLoud = null;
|
|
2053
|
+
let firedAt = null;
|
|
2054
|
+
return {
|
|
2055
|
+
feed(level, at) {
|
|
2056
|
+
if (level >= threshold) {
|
|
2057
|
+
if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
|
|
2058
|
+
lastLoud = at;
|
|
2059
|
+
} else if (lastLoud !== null && at - lastLoud > gapMs) {
|
|
2060
|
+
speechStart = null;
|
|
2061
|
+
lastLoud = null;
|
|
2062
|
+
}
|
|
2063
|
+
if (speechStart === null || at - speechStart < holdMs) return false;
|
|
2064
|
+
if (firedAt !== null && at - firedAt < cooldownMs) return false;
|
|
2065
|
+
firedAt = at;
|
|
2066
|
+
speechStart = null;
|
|
2067
|
+
lastLoud = null;
|
|
2068
|
+
return true;
|
|
2069
|
+
},
|
|
2070
|
+
reset() {
|
|
2071
|
+
speechStart = null;
|
|
2072
|
+
lastLoud = null;
|
|
2073
|
+
firedAt = null;
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
|
|
1896
2078
|
// src/react/facade.ts
|
|
1897
2079
|
var NO_ACTIONS = {
|
|
1898
2080
|
join: async () => void 0,
|
|
@@ -2049,6 +2231,18 @@ function SparklesIcon(props) {
|
|
|
2049
2231
|
/* @__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" })
|
|
2050
2232
|
] });
|
|
2051
2233
|
}
|
|
2234
|
+
function CaptionsIcon(props) {
|
|
2235
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
2236
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "3", y: "5", width: "18", height: "14", rx: "2" }),
|
|
2237
|
+
/* @__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" })
|
|
2238
|
+
] });
|
|
2239
|
+
}
|
|
2240
|
+
function NotesIcon(props) {
|
|
2241
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
2242
|
+
/* @__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" }),
|
|
2243
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M14 7l3 3" })
|
|
2244
|
+
] });
|
|
2245
|
+
}
|
|
2052
2246
|
function LockIcon(props) {
|
|
2053
2247
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(Icon, { ...props, children: [
|
|
2054
2248
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { x: "4", y: "10", width: "16", height: "11", rx: "2" }),
|
|
@@ -2196,6 +2390,9 @@ function UndoIcon(props) {
|
|
|
2196
2390
|
/* @__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" })
|
|
2197
2391
|
] });
|
|
2198
2392
|
}
|
|
2393
|
+
function ChevronUpIcon(props) {
|
|
2394
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Icon, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "m6 15 6-6 6 6" }) });
|
|
2395
|
+
}
|
|
2199
2396
|
|
|
2200
2397
|
// src/skins/meet/collab.tsx
|
|
2201
2398
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
@@ -2967,7 +3164,7 @@ function BreakoutRun({ plan }) {
|
|
|
2967
3164
|
type: "button",
|
|
2968
3165
|
className: "mx-meet__primary mx-meet__primary--small",
|
|
2969
3166
|
onClick: () => void act({ action: "breakout_close" }).then(show),
|
|
2970
|
-
children: "
|
|
3167
|
+
children: "Close rooms"
|
|
2971
3168
|
}
|
|
2972
3169
|
) : null
|
|
2973
3170
|
] });
|
|
@@ -2985,9 +3182,14 @@ function BreakoutBanner() {
|
|
|
2985
3182
|
const { collab, act, moveTo, localIdentity, breakoutRoomId } = useCollab();
|
|
2986
3183
|
const plan = collab?.breakout ?? null;
|
|
2987
3184
|
const open = plan?.state === "open" ? plan : null;
|
|
2988
|
-
const now = useNow(open?.endsAt != null);
|
|
3185
|
+
const now = useNow(open?.endsAt != null || open?.closingAt != null);
|
|
2989
3186
|
const remaining = breakoutRemainingMs(open, now);
|
|
3187
|
+
const closing = breakoutClosingMs(open, now);
|
|
2990
3188
|
const assigned = assignedRoom(open, localIdentity);
|
|
3189
|
+
const [later, setLater] = (0, import_react9.useState)(null);
|
|
3190
|
+
const [autoSince, setAutoSince] = (0, import_react9.useState)(null);
|
|
3191
|
+
const AUTO_MOVE_MS = 1e4;
|
|
3192
|
+
const autoLeft = open?.moveMode === "auto" && assigned !== null && autoSince?.id === open.id && later !== open.id ? Math.max(0, AUTO_MOVE_MS - (now - autoSince.at)) : null;
|
|
2991
3193
|
const [dismissedBroadcast, setDismissedBroadcast] = (0, import_react9.useState)(null);
|
|
2992
3194
|
const [failure, setFailure] = (0, import_react9.useState)(null);
|
|
2993
3195
|
const expireAsked = (0, import_react9.useRef)(null);
|
|
@@ -3000,6 +3202,19 @@ function BreakoutBanner() {
|
|
|
3000
3202
|
if ((here ?? null) === request.roomId) return;
|
|
3001
3203
|
void moveTo(request.roomId).then((result) => setFailure(result.ok ? null : result.message));
|
|
3002
3204
|
}, [moveAt, host, moveTo]);
|
|
3205
|
+
const joinAssigned = async () => {
|
|
3206
|
+
if (assigned === null) return;
|
|
3207
|
+
const said = await act({ action: "breakout_join", roomId: assigned.id });
|
|
3208
|
+
const result = said.ok ? await moveTo(assigned.id) : said;
|
|
3209
|
+
setFailure(result.ok ? null : result.message);
|
|
3210
|
+
};
|
|
3211
|
+
(0, import_react9.useEffect)(() => {
|
|
3212
|
+
if (open === null || open.moveMode !== "auto" || assigned === null || breakoutRoomId !== null) return;
|
|
3213
|
+
if (autoSince?.id !== open.id) setAutoSince({ id: open.id, at: Date.now() });
|
|
3214
|
+
}, [open, assigned, breakoutRoomId, autoSince]);
|
|
3215
|
+
(0, import_react9.useEffect)(() => {
|
|
3216
|
+
if (autoLeft === 0) void joinAssigned();
|
|
3217
|
+
}, [autoLeft]);
|
|
3003
3218
|
(0, import_react9.useEffect)(() => {
|
|
3004
3219
|
if (open === null || remaining === null || remaining > 0) return;
|
|
3005
3220
|
const asked = expireAsked.current;
|
|
@@ -3013,27 +3228,25 @@ function BreakoutBanner() {
|
|
|
3013
3228
|
if (open === null && failure === null) return null;
|
|
3014
3229
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "mx-meet__breakout-banner", role: "status", children: [
|
|
3015
3230
|
failure !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "mx-meet__collab-error", children: failure }) : null,
|
|
3231
|
+
closing !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { role: "status", "data-meet-notice": "breakout-closing", children: [
|
|
3232
|
+
"Rooms closing in ",
|
|
3233
|
+
Math.ceil(closing / 1e3),
|
|
3234
|
+
room !== null ? " \u2014 you return to the main meeting" : ""
|
|
3235
|
+
] }) : null,
|
|
3016
3236
|
room !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
3017
3237
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: room.name }),
|
|
3018
|
-
remaining !== null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
3238
|
+
remaining !== null && closing === null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
3019
3239
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void moveTo(null), children: "Return to main room" })
|
|
3240
|
+
] }) : assigned !== null && later !== open?.id ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
3241
|
+
autoLeft !== null ? `You're being moved to ${assigned.name} in ${Math.ceil(autoLeft / 1e3)} s` : `You're invited to ${assigned.name}`,
|
|
3242
|
+
remaining !== null && closing === null ? ` \xB7 ${clock(remaining)} left` : "",
|
|
3243
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void joinAssigned(), children: "Join room" }),
|
|
3244
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => setLater(open?.id ?? null), children: "Later" })
|
|
3020
3245
|
] }) : assigned !== null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { children: [
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
"button",
|
|
3026
|
-
{
|
|
3027
|
-
type: "button",
|
|
3028
|
-
className: "mx-meet__primary mx-meet__primary--small",
|
|
3029
|
-
onClick: () => void act({ action: "breakout_join", roomId: assigned.id }).then(async (said) => {
|
|
3030
|
-
const result = said.ok ? await moveTo(assigned.id) : said;
|
|
3031
|
-
setFailure(result.ok ? null : result.message);
|
|
3032
|
-
}),
|
|
3033
|
-
children: "Join"
|
|
3034
|
-
}
|
|
3035
|
-
)
|
|
3036
|
-
] }) : open?.allowChoose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Breakout rooms are open \u2014 pick one under Activities." }) : null,
|
|
3246
|
+
assigned.name,
|
|
3247
|
+
" is open",
|
|
3248
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: "mx-meet__chip", onClick: () => void joinAssigned(), children: "Join room" })
|
|
3249
|
+
] }) : open?.allowChoose ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Rooms are open \u2014 pick one under Activities." }) : null,
|
|
3037
3250
|
showBroadcast ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "mx-meet__breakout-broadcast", children: [
|
|
3038
3251
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("strong", { children: [
|
|
3039
3252
|
broadcast.by,
|
|
@@ -3081,6 +3294,33 @@ function CaptionLanguageSettings() {
|
|
|
3081
3294
|
collab?.captionTranslationError != null ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mx-meet__panel-note mx-meet__collab-error", children: collab.captionTranslationError }) : null
|
|
3082
3295
|
] });
|
|
3083
3296
|
}
|
|
3297
|
+
function CaptionsControl() {
|
|
3298
|
+
const captions = useCaptions();
|
|
3299
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3300
|
+
ControlButton,
|
|
3301
|
+
{
|
|
3302
|
+
label: captions.on ? "Turn off captions" : "Turn on captions",
|
|
3303
|
+
active: captions.on,
|
|
3304
|
+
pressed: captions.on,
|
|
3305
|
+
onClick: captions.toggle,
|
|
3306
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CaptionsIcon, {})
|
|
3307
|
+
}
|
|
3308
|
+
);
|
|
3309
|
+
}
|
|
3310
|
+
function TakeNotesControl() {
|
|
3311
|
+
const ai = useMeetAi();
|
|
3312
|
+
if (!ai.canControlNoteTaker) return null;
|
|
3313
|
+
const running = ai.noteTaker.state === "joining" || ai.noteTaker.state === "transcribing";
|
|
3314
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
3315
|
+
ControlButton,
|
|
3316
|
+
{
|
|
3317
|
+
label: running ? "Stop taking notes" : "Take notes for me",
|
|
3318
|
+
active: running,
|
|
3319
|
+
onClick: () => void (running ? ai.stopNoteTaker() : ai.startNoteTaker()),
|
|
3320
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(NotesIcon, {})
|
|
3321
|
+
}
|
|
3322
|
+
);
|
|
3323
|
+
}
|
|
3084
3324
|
|
|
3085
3325
|
// src/skins/meet/components.tsx
|
|
3086
3326
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
@@ -3131,6 +3371,7 @@ function ControlButton({
|
|
|
3131
3371
|
label,
|
|
3132
3372
|
active = false,
|
|
3133
3373
|
danger = false,
|
|
3374
|
+
pressed,
|
|
3134
3375
|
onClick,
|
|
3135
3376
|
children
|
|
3136
3377
|
}) {
|
|
@@ -3145,26 +3386,49 @@ function ControlButton({
|
|
|
3145
3386
|
].filter(Boolean).join(" "),
|
|
3146
3387
|
"aria-label": label,
|
|
3147
3388
|
title: label,
|
|
3389
|
+
...pressed !== void 0 ? { "aria-pressed": pressed } : {},
|
|
3148
3390
|
onClick,
|
|
3149
3391
|
children
|
|
3150
3392
|
}
|
|
3151
3393
|
);
|
|
3152
3394
|
}
|
|
3153
3395
|
function RecordingIndicator() {
|
|
3154
|
-
const { state, label } = useRecording();
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
]
|
|
3396
|
+
const { state, label, canStart } = useRecording();
|
|
3397
|
+
const snapshot = useMeetSnapshot();
|
|
3398
|
+
const previous = (0, import_react10.useRef)(state);
|
|
3399
|
+
const [stoppedAt, setStoppedAt] = (0, import_react10.useState)(null);
|
|
3400
|
+
(0, import_react10.useEffect)(() => {
|
|
3401
|
+
const was = previous.current;
|
|
3402
|
+
previous.current = state;
|
|
3403
|
+
if ((was === "recording" || was === "starting") && state !== "recording" && state !== "starting") {
|
|
3404
|
+
setStoppedAt(Date.now());
|
|
3405
|
+
const timer = setTimeout(() => setStoppedAt(null), 1e4);
|
|
3406
|
+
return () => clearTimeout(timer);
|
|
3166
3407
|
}
|
|
3167
|
-
|
|
3408
|
+
return void 0;
|
|
3409
|
+
}, [state]);
|
|
3410
|
+
const meetingId = snapshot?.meeting?.id ?? null;
|
|
3411
|
+
if (label === null && stoppedAt === null) return null;
|
|
3412
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
3413
|
+
label !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3414
|
+
"span",
|
|
3415
|
+
{
|
|
3416
|
+
className: `mx-meet__recording mx-meet__recording--${state}`,
|
|
3417
|
+
role: "status",
|
|
3418
|
+
"aria-live": "polite",
|
|
3419
|
+
children: [
|
|
3420
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RecordIcon, { width: 10, height: 10 }),
|
|
3421
|
+
label
|
|
3422
|
+
]
|
|
3423
|
+
}
|
|
3424
|
+
) : null,
|
|
3425
|
+
stoppedAt !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__recording", role: "status", "data-meet-notice": "recording-stopped", children: "Recording stopped" }) : null,
|
|
3426
|
+
state === "processing" ? (
|
|
3427
|
+
// rec-processing: the host is told it is being made ready (the indicator says Processing…).
|
|
3428
|
+
/* @__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." })
|
|
3429
|
+
) : null,
|
|
3430
|
+
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
|
|
3431
|
+
] });
|
|
3168
3432
|
}
|
|
3169
3433
|
function ConnectionDot({ quality }) {
|
|
3170
3434
|
if (quality === "excellent" || quality === "good" || quality === "unknown") return null;
|
|
@@ -3320,6 +3584,7 @@ function ControlBar({ onLeave }) {
|
|
|
3320
3584
|
const controls = useHostControls();
|
|
3321
3585
|
const queue = useHandQueue();
|
|
3322
3586
|
const micUnavailable = useLocalMedia().microphone.unavailable;
|
|
3587
|
+
const live = useLivePolicy();
|
|
3323
3588
|
const unread = useUnreadChat(panel === "chat");
|
|
3324
3589
|
const [whiteboard, setWhiteboard] = (0, import_react10.useState)(false);
|
|
3325
3590
|
const app = useMeetAppPanel();
|
|
@@ -3385,9 +3650,10 @@ function ControlBar({ onLeave }) {
|
|
|
3385
3650
|
) }) }) : null,
|
|
3386
3651
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreakoutBanner, {}),
|
|
3387
3652
|
panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
3653
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SpeakingWhileMutedNudge, {}),
|
|
3388
3654
|
controls.notice !== null && panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Toast, { text: controls.notice.text, failed: controls.notice.failed, onDone: controls.clearNotice }) : null,
|
|
3389
3655
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
3390
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3656
|
+
live.micLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3391
3657
|
ControlButton,
|
|
3392
3658
|
{
|
|
3393
3659
|
label: micUnavailable !== null ? `Unmute \u2014 ${micUnavailable.remedy}` : room.local?.micEnabled === true ? "Mute (\u2318D)" : "Unmute (\u2318D)",
|
|
@@ -3396,7 +3662,8 @@ function ControlBar({ onLeave }) {
|
|
|
3396
3662
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, {})
|
|
3397
3663
|
}
|
|
3398
3664
|
),
|
|
3399
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3665
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AudioSettingsControl, {}),
|
|
3666
|
+
live.cameraLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3400
3667
|
ControlButton,
|
|
3401
3668
|
{
|
|
3402
3669
|
label: room.local?.cameraEnabled === true ? "Stop video (\u2318E)" : "Start video (\u2318E)",
|
|
@@ -3405,6 +3672,7 @@ function ControlBar({ onLeave }) {
|
|
|
3405
3672
|
children: room.local?.cameraEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CameraIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CameraOffIcon, {})
|
|
3406
3673
|
}
|
|
3407
3674
|
),
|
|
3675
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CaptionsControl, {}),
|
|
3408
3676
|
room.canShareScreen || room.local?.screenSharing === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3409
3677
|
ControlButton,
|
|
3410
3678
|
{
|
|
@@ -3469,6 +3737,7 @@ function ControlBar({ onLeave }) {
|
|
|
3469
3737
|
onOpen: openActivity
|
|
3470
3738
|
}
|
|
3471
3739
|
),
|
|
3740
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TakeNotesControl, {}),
|
|
3472
3741
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AiControl, { active: panel === "ai", onToggle: () => toggle("ai") }),
|
|
3473
3742
|
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,
|
|
3474
3743
|
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,
|
|
@@ -3509,6 +3778,13 @@ function ControlBar({ onLeave }) {
|
|
|
3509
3778
|
]
|
|
3510
3779
|
}
|
|
3511
3780
|
),
|
|
3781
|
+
!room.canShareScreen && room.local?.screenSharing !== true ? (
|
|
3782
|
+
// `share-unsupported-mobile`: the control is absent; the menu says where it lives.
|
|
3783
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__menu-note", role: "note", "data-meet-notice": "share-unsupported", children: [
|
|
3784
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
3785
|
+
" Present from a computer"
|
|
3786
|
+
] })
|
|
3787
|
+
) : null,
|
|
3512
3788
|
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
3513
3789
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
3514
3790
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -3524,6 +3800,23 @@ function ControlBar({ onLeave }) {
|
|
|
3524
3800
|
]
|
|
3525
3801
|
}
|
|
3526
3802
|
),
|
|
3803
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3804
|
+
MenuItem,
|
|
3805
|
+
{
|
|
3806
|
+
onSelect: () => {
|
|
3807
|
+
setPopover("none");
|
|
3808
|
+
void controls.run({
|
|
3809
|
+
action: "set_av_lock",
|
|
3810
|
+
value: snapshot?.livePolicy.avLock === "both" ? "off" : "both"
|
|
3811
|
+
});
|
|
3812
|
+
},
|
|
3813
|
+
children: [
|
|
3814
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
3815
|
+
" ",
|
|
3816
|
+
snapshot?.livePolicy.avLock === "both" ? "Turn on attendees' microphone and camera" : "Turn off attendees' microphone and camera"
|
|
3817
|
+
]
|
|
3818
|
+
}
|
|
3819
|
+
),
|
|
3527
3820
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
3528
3821
|
MenuItem,
|
|
3529
3822
|
{
|
|
@@ -3766,6 +4059,94 @@ function Toast({ text, failed, onDone }) {
|
|
|
3766
4059
|
/* @__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 }) })
|
|
3767
4060
|
] });
|
|
3768
4061
|
}
|
|
4062
|
+
function AudioSettingsControl() {
|
|
4063
|
+
const host = useMeetHost();
|
|
4064
|
+
const devices = useDeviceSnapshot();
|
|
4065
|
+
const [open, setOpen] = (0, import_react10.useState)(false);
|
|
4066
|
+
const list = (devices?.devices.audioinput ?? []).filter((device) => device.deviceId.length > 0);
|
|
4067
|
+
if (host === null || list.length < 2) return null;
|
|
4068
|
+
const current = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
4069
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "mx-meet__anchor", children: [
|
|
4070
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4071
|
+
ControlButton,
|
|
4072
|
+
{
|
|
4073
|
+
label: "Audio settings",
|
|
4074
|
+
active: open,
|
|
4075
|
+
onClick: () => {
|
|
4076
|
+
if (!open) void host.devices.refresh();
|
|
4077
|
+
setOpen(!open);
|
|
4078
|
+
},
|
|
4079
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronUpIcon, {})
|
|
4080
|
+
}
|
|
4081
|
+
),
|
|
4082
|
+
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)(
|
|
4083
|
+
"button",
|
|
4084
|
+
{
|
|
4085
|
+
type: "button",
|
|
4086
|
+
role: "menuitemradio",
|
|
4087
|
+
"aria-checked": device.deviceId === current,
|
|
4088
|
+
className: "mx-meet__menu-item",
|
|
4089
|
+
onClick: () => {
|
|
4090
|
+
setOpen(false);
|
|
4091
|
+
host.devices.select("audioinput", device.deviceId);
|
|
4092
|
+
void host.engine.switchDevice("audioinput", device.deviceId).catch(() => void 0);
|
|
4093
|
+
},
|
|
4094
|
+
children: [
|
|
4095
|
+
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" }),
|
|
4096
|
+
device.label.length > 0 ? device.label : "Microphone"
|
|
4097
|
+
]
|
|
4098
|
+
},
|
|
4099
|
+
device.deviceId
|
|
4100
|
+
)) }) }) : null
|
|
4101
|
+
] });
|
|
4102
|
+
}
|
|
4103
|
+
function SpeakingWhileMutedNudge() {
|
|
4104
|
+
const room = useRoom();
|
|
4105
|
+
const host = useMeetHost();
|
|
4106
|
+
const devices = useDeviceSnapshot();
|
|
4107
|
+
const microphone = useLocalMedia().microphone;
|
|
4108
|
+
const mutedByMe = host !== null && room.local != null && room.local.micEnabled !== true && microphone.unavailable === null;
|
|
4109
|
+
const mic = devices?.selected.audioinput ?? host?.devices.resolve("audioinput") ?? null;
|
|
4110
|
+
const stream = useMicrophoneStream(mutedByMe ? mic : "off");
|
|
4111
|
+
const level = useAudioLevel(stream);
|
|
4112
|
+
const latest = (0, import_react10.useRef)(0);
|
|
4113
|
+
latest.current = level;
|
|
4114
|
+
const [shown, setShown] = (0, import_react10.useState)(false);
|
|
4115
|
+
const [detector] = (0, import_react10.useState)(() => createSpeakingWhileMutedDetector());
|
|
4116
|
+
(0, import_react10.useEffect)(() => {
|
|
4117
|
+
if (!mutedByMe) {
|
|
4118
|
+
detector.reset();
|
|
4119
|
+
setShown(false);
|
|
4120
|
+
return void 0;
|
|
4121
|
+
}
|
|
4122
|
+
const timer = setInterval(() => {
|
|
4123
|
+
if (detector.feed(latest.current, Date.now())) setShown(true);
|
|
4124
|
+
}, 150);
|
|
4125
|
+
return () => clearInterval(timer);
|
|
4126
|
+
}, [mutedByMe, detector]);
|
|
4127
|
+
(0, import_react10.useEffect)(() => {
|
|
4128
|
+
if (!shown) return void 0;
|
|
4129
|
+
const timer = setTimeout(() => setShown(false), 8e3);
|
|
4130
|
+
return () => clearTimeout(timer);
|
|
4131
|
+
}, [shown]);
|
|
4132
|
+
if (!shown || !mutedByMe) return null;
|
|
4133
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "speaking-while-muted", children: [
|
|
4134
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 16, height: 16 }),
|
|
4135
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "You're muted" }),
|
|
4136
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4137
|
+
"button",
|
|
4138
|
+
{
|
|
4139
|
+
type: "button",
|
|
4140
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
4141
|
+
onClick: () => {
|
|
4142
|
+
setShown(false);
|
|
4143
|
+
void room.toggleMicrophone();
|
|
4144
|
+
},
|
|
4145
|
+
children: "Unmute"
|
|
4146
|
+
}
|
|
4147
|
+
)
|
|
4148
|
+
] });
|
|
4149
|
+
}
|
|
3769
4150
|
function MediaNotices() {
|
|
3770
4151
|
const media = useLocalMedia();
|
|
3771
4152
|
const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
|
|
@@ -3851,9 +4232,45 @@ function MeetingOverlays() {
|
|
|
3851
4232
|
const reactions = useReactions();
|
|
3852
4233
|
const { notice, dismiss } = useModerationNotice();
|
|
3853
4234
|
const room = useRoom();
|
|
4235
|
+
const live = useLivePolicy();
|
|
4236
|
+
const snapshot = useMeetSnapshot();
|
|
4237
|
+
const controls = useHostControls();
|
|
4238
|
+
const isHostNow = useRole() === "host";
|
|
3854
4239
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
3855
4240
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ReactionOverlay, { reactions }),
|
|
3856
4241
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MediaNotices, {}),
|
|
4242
|
+
snapshot !== null && snapshot.phase === "in_call" && snapshot.participants.length >= 2 && !snapshot.participants.some((p) => p.role === "host" || p.role === "cohost") ? (
|
|
4243
|
+
// meeting-without-host: the call goes on; host-only controls are simply not on screen.
|
|
4244
|
+
/* @__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." })
|
|
4245
|
+
) : null,
|
|
4246
|
+
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: [
|
|
4247
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "mx-meet__panel-title", children: "This meeting is being recorded" }),
|
|
4248
|
+
/* @__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." }),
|
|
4249
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__menu-actions", children: [
|
|
4250
|
+
/* @__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" }),
|
|
4251
|
+
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
|
|
4252
|
+
] })
|
|
4253
|
+
] }) }) : null,
|
|
4254
|
+
snapshot?.canReclaim === true && !isHostNow ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__notice-bar", role: "status", "data-meet-notice": "host-reclaim", children: [
|
|
4255
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "You started this meeting. Take back the host?" }),
|
|
4256
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
4257
|
+
"button",
|
|
4258
|
+
{
|
|
4259
|
+
type: "button",
|
|
4260
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
4261
|
+
onClick: () => void controls.run({ action: "reclaim_host" }),
|
|
4262
|
+
children: "Reclaim host"
|
|
4263
|
+
}
|
|
4264
|
+
)
|
|
4265
|
+
] }) : null,
|
|
4266
|
+
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,
|
|
4267
|
+
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: [
|
|
4268
|
+
"This meeting will end in ",
|
|
4269
|
+
live.minutesLeft,
|
|
4270
|
+
" ",
|
|
4271
|
+
live.minutesLeft === 1 ? "minute" : "minutes",
|
|
4272
|
+
" (time limit)."
|
|
4273
|
+
] }) : null,
|
|
3857
4274
|
notice !== null && notice.kind !== "removed" ? (
|
|
3858
4275
|
// A host's request ("the host is asking you to unmute"), not an error:
|
|
3859
4276
|
// an assertive status, so it is announced at once without being filed
|
|
@@ -3944,6 +4361,7 @@ function ChatPanel({
|
|
|
3944
4361
|
}) {
|
|
3945
4362
|
const snapshot = useMeetSnapshot();
|
|
3946
4363
|
const room = useRoom();
|
|
4364
|
+
const live = useLivePolicy();
|
|
3947
4365
|
const [draft, setDraft] = (0, import_react10.useState)("");
|
|
3948
4366
|
const [recipient, setRecipient] = (0, import_react10.useState)(to);
|
|
3949
4367
|
const listRef = (0, import_react10.useRef)(null);
|
|
@@ -3968,7 +4386,7 @@ function ChatPanel({
|
|
|
3968
4386
|
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,
|
|
3969
4387
|
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChatLine, { message, local, people }, message.id))
|
|
3970
4388
|
] }),
|
|
3971
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4389
|
+
!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)(
|
|
3972
4390
|
"form",
|
|
3973
4391
|
{
|
|
3974
4392
|
className: "mx-meet__composer mx-meet__composer--chat",
|
|
@@ -4340,6 +4758,13 @@ function PersonMenu({
|
|
|
4340
4758
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PeopleIcon, { width: 16, height: 16 }),
|
|
4341
4759
|
" Make co-host"
|
|
4342
4760
|
] }) : null,
|
|
4761
|
+
!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: [
|
|
4762
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
4763
|
+
" Remove presenter"
|
|
4764
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(MenuItem, { onSelect: () => act({ action: "make_presenter", identity: participant.identity }), children: [
|
|
4765
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScreenShareIcon, { width: 16, height: 16 }),
|
|
4766
|
+
" Make presenter"
|
|
4767
|
+
] }) : null,
|
|
4343
4768
|
!participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
4344
4769
|
MenuItem,
|
|
4345
4770
|
{
|
|
@@ -4571,14 +4996,20 @@ async function playTestSound(sinkId) {
|
|
|
4571
4996
|
audio.pause();
|
|
4572
4997
|
await context.close();
|
|
4573
4998
|
}
|
|
4574
|
-
function useMicrophoneStream(deviceId) {
|
|
4999
|
+
function useMicrophoneStream(deviceId, report, attempt = 0) {
|
|
4575
5000
|
const [stream, setStream] = (0, import_react10.useState)(null);
|
|
5001
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
5002
|
+
reportRef.current = report;
|
|
4576
5003
|
(0, import_react10.useEffect)(() => {
|
|
4577
|
-
if (deviceId === "off")
|
|
5004
|
+
if (deviceId === "off") {
|
|
5005
|
+
reportRef.current?.("off");
|
|
5006
|
+
return void 0;
|
|
5007
|
+
}
|
|
4578
5008
|
let live = true;
|
|
4579
5009
|
let opened = null;
|
|
4580
5010
|
const media = globalThis.navigator?.mediaDevices;
|
|
4581
5011
|
if (media === void 0) return void 0;
|
|
5012
|
+
reportRef.current?.("prompting");
|
|
4582
5013
|
void media.getUserMedia({ audio: deviceId === null ? true : { deviceId: { exact: deviceId } } }).then((next) => {
|
|
4583
5014
|
if (!live) {
|
|
4584
5015
|
next.getTracks().forEach((track) => track.stop());
|
|
@@ -4586,15 +5017,49 @@ function useMicrophoneStream(deviceId) {
|
|
|
4586
5017
|
}
|
|
4587
5018
|
opened = next;
|
|
4588
5019
|
setStream(next);
|
|
4589
|
-
|
|
5020
|
+
reportRef.current?.("on");
|
|
5021
|
+
}).catch((thrown) => {
|
|
5022
|
+
setStream(null);
|
|
5023
|
+
if (live) reportRef.current?.({ error: thrown });
|
|
5024
|
+
});
|
|
4590
5025
|
return () => {
|
|
4591
5026
|
live = false;
|
|
4592
5027
|
opened?.getTracks().forEach((track) => track.stop());
|
|
4593
5028
|
setStream(null);
|
|
4594
5029
|
};
|
|
4595
|
-
}, [deviceId]);
|
|
5030
|
+
}, [deviceId, attempt]);
|
|
4596
5031
|
return stream;
|
|
4597
5032
|
}
|
|
5033
|
+
function PreviewStatus({ onRetry }) {
|
|
5034
|
+
const { region, where } = useMediaSupervisor();
|
|
5035
|
+
const [waited, setWaited] = (0, import_react10.useState)(false);
|
|
5036
|
+
const prompting = region.preview.microphone.state === "prompting" || region.preview.camera.state === "prompting";
|
|
5037
|
+
(0, import_react10.useEffect)(() => {
|
|
5038
|
+
if (!prompting) {
|
|
5039
|
+
setWaited(false);
|
|
5040
|
+
return void 0;
|
|
5041
|
+
}
|
|
5042
|
+
const timer = setTimeout(() => setWaited(true), 800);
|
|
5043
|
+
return () => clearTimeout(timer);
|
|
5044
|
+
}, [prompting]);
|
|
5045
|
+
const refused = ["microphone", "camera"].flatMap((kind) => {
|
|
5046
|
+
const preview = region.preview[kind];
|
|
5047
|
+
const reason = region.permission[kind] === "denied" ? "denied" : preview.state === "unavailable" ? preview.unavailable?.reason ?? "failed" : null;
|
|
5048
|
+
return reason === null ? [] : [{ kind, reason }];
|
|
5049
|
+
});
|
|
5050
|
+
if (refused.length === 0) {
|
|
5051
|
+
if (!prompting || !waited) return null;
|
|
5052
|
+
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" });
|
|
5053
|
+
}
|
|
5054
|
+
const first = refused[0];
|
|
5055
|
+
const both = refused.length === 2 && refused[0].reason === refused[1].reason;
|
|
5056
|
+
const label = both ? deviceChipLabel("camera", first.reason).replace(/^Camera/, "Camera and mic") : refused.map((r) => deviceChipLabel(r.kind, r.reason)).join(" \xB7 ");
|
|
5057
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__warning", role: "status", "data-meet-notice": deviceNoticeCode(first.kind, first.reason), children: [
|
|
5058
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: label }),
|
|
5059
|
+
/* @__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)) }),
|
|
5060
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_design_system.Button, { type: "button", variant: "outline", size: "sm", onClick: onRetry, children: "Try again" })
|
|
5061
|
+
] });
|
|
5062
|
+
}
|
|
4598
5063
|
function KnockNotice({ onView }) {
|
|
4599
5064
|
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
4600
5065
|
if (knocker === null) return null;
|
|
@@ -4845,8 +5310,19 @@ function Captions({ lines = 3 }) {
|
|
|
4845
5310
|
const ai = useMeetAi();
|
|
4846
5311
|
const visible = ai.transcript.slice(-lines);
|
|
4847
5312
|
const translation = useCaptionTranslation(visible);
|
|
4848
|
-
|
|
4849
|
-
|
|
5313
|
+
const captions = useCaptions();
|
|
5314
|
+
if (!captions.on) return null;
|
|
5315
|
+
if (captions.state === "unavailable") {
|
|
5316
|
+
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: [
|
|
5317
|
+
"Captions are unavailable. ",
|
|
5318
|
+
captions.reason,
|
|
5319
|
+
captions.remedy !== null ? ` ${captions.remedy}` : ""
|
|
5320
|
+
] }) });
|
|
5321
|
+
}
|
|
5322
|
+
if (visible.length === 0) {
|
|
5323
|
+
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." }) });
|
|
5324
|
+
}
|
|
5325
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": "live", children: [
|
|
4850
5326
|
visible.map((line) => {
|
|
4851
5327
|
const translated = translation.language === null ? void 0 : translation.translations[line.id];
|
|
4852
5328
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -4869,7 +5345,7 @@ function Captions({ lines = 3 }) {
|
|
|
4869
5345
|
function ConsentNotice() {
|
|
4870
5346
|
const notice = useConsentNotice();
|
|
4871
5347
|
if (notice === null) return null;
|
|
4872
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__consent", role: "status", children: notice });
|
|
5348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__consent", role: "status", "data-meet-notice": "notes-started", children: notice });
|
|
4873
5349
|
}
|
|
4874
5350
|
function AttendanceNotice() {
|
|
4875
5351
|
const media = useLocalMedia();
|
|
@@ -4913,8 +5389,15 @@ function PreJoinBody({
|
|
|
4913
5389
|
const [joining, setJoining] = (0, import_react10.useState)(false);
|
|
4914
5390
|
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
4915
5391
|
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
4916
|
-
const
|
|
4917
|
-
const
|
|
5392
|
+
const { reportPreview } = useMediaSupervisor();
|
|
5393
|
+
const [attempt, setAttempt] = (0, import_react10.useState)(0);
|
|
5394
|
+
const preview = useCameraPreview(
|
|
5395
|
+
mode === "video" ? camera : void 0,
|
|
5396
|
+
effects.effects,
|
|
5397
|
+
(outcome) => reportPreview("camera", outcome),
|
|
5398
|
+
attempt
|
|
5399
|
+
);
|
|
5400
|
+
const micStream = useMicrophoneStream(microphone ? mic : "off", (outcome) => reportPreview("microphone", outcome), attempt);
|
|
4918
5401
|
const level = useAudioLevel(micStream);
|
|
4919
5402
|
const [speakerTesting, setSpeakerTesting] = (0, import_react10.useState)(false);
|
|
4920
5403
|
(0, import_react10.useEffect)(() => {
|
|
@@ -4958,6 +5441,7 @@ function PreJoinBody({
|
|
|
4958
5441
|
] }),
|
|
4959
5442
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
4960
5443
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h1", { className: "mx-meet__prejoin-title", children: "Ready to join?" }),
|
|
5444
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PreviewStatus, { onRetry: () => setAttempt((n) => n + 1) }),
|
|
4961
5445
|
notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
4962
5446
|
devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
4963
5447
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
@@ -5060,16 +5544,22 @@ function PreJoinBody({
|
|
|
5060
5544
|
] })
|
|
5061
5545
|
] });
|
|
5062
5546
|
}
|
|
5063
|
-
function useCameraPreview(deviceId, effects) {
|
|
5547
|
+
function useCameraPreview(deviceId, effects, report, attempt = 0) {
|
|
5548
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
5549
|
+
reportRef.current = report;
|
|
5064
5550
|
const [preview, setPreview] = (0, import_react10.useState)(null);
|
|
5065
5551
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
5066
5552
|
const current = (0, import_react10.useRef)(null);
|
|
5067
5553
|
const initialEffect = (0, import_react10.useRef)(toVideoEffect(effects));
|
|
5068
5554
|
initialEffect.current = toVideoEffect(effects);
|
|
5069
5555
|
(0, import_react10.useEffect)(() => {
|
|
5070
|
-
if (deviceId === void 0)
|
|
5556
|
+
if (deviceId === void 0) {
|
|
5557
|
+
reportRef.current?.("off");
|
|
5558
|
+
return void 0;
|
|
5559
|
+
}
|
|
5071
5560
|
let live = true;
|
|
5072
5561
|
setError(null);
|
|
5562
|
+
reportRef.current?.("prompting");
|
|
5073
5563
|
void createCameraPreview(deviceId, initialEffect.current).then((created) => {
|
|
5074
5564
|
if (!live) {
|
|
5075
5565
|
created.stop();
|
|
@@ -5077,8 +5567,10 @@ function useCameraPreview(deviceId, effects) {
|
|
|
5077
5567
|
}
|
|
5078
5568
|
current.current = created;
|
|
5079
5569
|
setPreview(created);
|
|
5570
|
+
reportRef.current?.("on");
|
|
5080
5571
|
}).catch((thrown) => {
|
|
5081
5572
|
if (!live) return;
|
|
5573
|
+
reportRef.current?.({ error: thrown });
|
|
5082
5574
|
const failure = thrown;
|
|
5083
5575
|
const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
|
|
5084
5576
|
const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
|
|
@@ -5090,7 +5582,7 @@ function useCameraPreview(deviceId, effects) {
|
|
|
5090
5582
|
current.current = null;
|
|
5091
5583
|
setPreview(null);
|
|
5092
5584
|
};
|
|
5093
|
-
}, [deviceId]);
|
|
5585
|
+
}, [deviceId, attempt]);
|
|
5094
5586
|
return {
|
|
5095
5587
|
preview,
|
|
5096
5588
|
error,
|
|
@@ -5230,6 +5722,14 @@ function MeetSkinRoom({
|
|
|
5230
5722
|
}
|
|
5231
5723
|
function ShareNotice() {
|
|
5232
5724
|
const { share, actions } = useMeetCore();
|
|
5725
|
+
const [soundHintClosed, setSoundHintClosed] = (0, import_react10.useState)(null);
|
|
5726
|
+
if (share.state === "presenting" && share.audio === false && soundHintClosed !== share.at) {
|
|
5727
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-audio-missing", children: [
|
|
5728
|
+
"Sound not shared",
|
|
5729
|
+
/* @__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" }),
|
|
5730
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__link", onClick: () => setSoundHintClosed(share.at), children: "Dismiss" })
|
|
5731
|
+
] });
|
|
5732
|
+
}
|
|
5233
5733
|
if (share.state !== "refused") return null;
|
|
5234
5734
|
const text = 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";
|
|
5235
5735
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-unavailable", children: [
|
|
@@ -5342,6 +5842,27 @@ function ExitScreen({ onLeave }) {
|
|
|
5342
5842
|
] });
|
|
5343
5843
|
break;
|
|
5344
5844
|
case "disconnected":
|
|
5845
|
+
if (failure?.stage === "join" && (failure.reason ?? null) === null) {
|
|
5846
|
+
title = "Can't connect to the meeting";
|
|
5847
|
+
line = connection === "offline" ? "You're offline. Check your internet connection." : "Check your network. A VPN, firewall or proxy may be blocking the call.";
|
|
5848
|
+
notice = "cannot-connect";
|
|
5849
|
+
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
5850
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
5851
|
+
import_design_system.Button,
|
|
5852
|
+
{
|
|
5853
|
+
type: "button",
|
|
5854
|
+
disabled: busy,
|
|
5855
|
+
onClick: () => {
|
|
5856
|
+
setBusy(true);
|
|
5857
|
+
void actions.rejoin().catch(() => void 0).finally(() => setBusy(false));
|
|
5858
|
+
},
|
|
5859
|
+
children: "Try again"
|
|
5860
|
+
}
|
|
5861
|
+
),
|
|
5862
|
+
home
|
|
5863
|
+
] });
|
|
5864
|
+
break;
|
|
5865
|
+
}
|
|
5345
5866
|
title = "You lost your connection to the meeting";
|
|
5346
5867
|
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.";
|
|
5347
5868
|
notice = "disconnected";
|