@ai-matrx/meet 0.11.43 → 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 +6 -0
- package/dist/core.cjs +148 -13
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +96 -1
- package/dist/core.d.ts +96 -1
- package/dist/core.js +148 -13
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +148 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +148 -13
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +385 -25
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +791 -431
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +262 -15
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -1
- package/dist/skins/meet.d.ts +2 -1
- package/dist/skins/meet.js +609 -362
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -5
package/dist/skins/meet.cjs
CHANGED
|
@@ -48,6 +48,7 @@ __export(meet_exports, {
|
|
|
48
48
|
ChatIcon: () => ChatIcon,
|
|
49
49
|
ChatPanel: () => ChatPanel,
|
|
50
50
|
CheckIcon: () => CheckIcon,
|
|
51
|
+
ChevronUpIcon: () => ChevronUpIcon,
|
|
51
52
|
CloseIcon: () => CloseIcon,
|
|
52
53
|
ConnectionDot: () => ConnectionDot,
|
|
53
54
|
ConsentNotice: () => ConsentNotice,
|
|
@@ -390,11 +391,13 @@ function minutesLeft(policy, startedAtMs, nowMs) {
|
|
|
390
391
|
}
|
|
391
392
|
|
|
392
393
|
// src/core/attendance.ts
|
|
394
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
393
395
|
var IDLE_MEDIA_REGION = {
|
|
394
396
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
395
397
|
audioOutput: "ok",
|
|
396
398
|
notice: null,
|
|
397
|
-
offer: null
|
|
399
|
+
offer: null,
|
|
400
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
398
401
|
};
|
|
399
402
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
400
403
|
var IDLE_MEDIA = {
|
|
@@ -615,7 +618,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
615
618
|
case "safari":
|
|
616
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"];
|
|
617
620
|
default:
|
|
618
|
-
return ["Click the icon
|
|
621
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
619
622
|
}
|
|
620
623
|
case "blocked_os": {
|
|
621
624
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -709,6 +712,21 @@ function observedDevice(track, permission = "unknown", held = null) {
|
|
|
709
712
|
const reason = deviceUnavailableReason(track, permission);
|
|
710
713
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
711
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
|
+
}
|
|
712
730
|
function endedRecord(snapshot, given) {
|
|
713
731
|
const record = snapshot?.meeting ?? given;
|
|
714
732
|
return record !== null && record.endedAt !== null;
|
|
@@ -732,18 +750,22 @@ function observe(input) {
|
|
|
732
750
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
733
751
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
734
752
|
const deviceInPlay = phase === "joining" || inCall;
|
|
753
|
+
const region = snapshot?.mediaRegion;
|
|
754
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
735
755
|
return {
|
|
736
756
|
phase,
|
|
737
757
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
738
758
|
role,
|
|
739
|
-
|
|
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,
|
|
740
762
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
741
763
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
742
764
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
743
765
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
744
766
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
745
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : null,
|
|
746
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : 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
|
|
747
769
|
};
|
|
748
770
|
}
|
|
749
771
|
function lockedFor(snapshot, kind, inCall) {
|
|
@@ -1267,7 +1289,8 @@ function useRoom() {
|
|
|
1267
1289
|
failure: snapshot?.failure ?? null,
|
|
1268
1290
|
secondDevice: snapshot?.secondDevice ?? null,
|
|
1269
1291
|
local,
|
|
1270
|
-
|
|
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),
|
|
1271
1294
|
join: (0, import_react5.useCallback)(
|
|
1272
1295
|
async (request) => requireEngine(engine, "join").join(request),
|
|
1273
1296
|
[engine]
|
|
@@ -1693,6 +1716,12 @@ function useMediaSupervisor() {
|
|
|
1693
1716
|
retry: (0, import_react5.useCallback)(
|
|
1694
1717
|
async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
|
|
1695
1718
|
[engine]
|
|
1719
|
+
),
|
|
1720
|
+
reportPreview: (0, import_react5.useCallback)(
|
|
1721
|
+
(kind, outcome) => {
|
|
1722
|
+
void engine?.previewDevice(kind, outcome);
|
|
1723
|
+
},
|
|
1724
|
+
[engine]
|
|
1696
1725
|
)
|
|
1697
1726
|
};
|
|
1698
1727
|
}
|
|
@@ -2013,6 +2042,39 @@ function useCollabPanelRequest(onRequest) {
|
|
|
2013
2042
|
// src/core/reactions.ts
|
|
2014
2043
|
var MEET_REACTIONS = ["\u{1F496}", "\u{1F44D}", "\u{1F389}", "\u{1F44F}", "\u{1F602}", "\u{1F62E}", "\u{1F622}", "\u{1F914}", "\u{1F44E}"];
|
|
2015
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
|
+
|
|
2016
2078
|
// src/react/facade.ts
|
|
2017
2079
|
var NO_ACTIONS = {
|
|
2018
2080
|
join: async () => void 0,
|
|
@@ -2328,6 +2390,9 @@ function UndoIcon(props) {
|
|
|
2328
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" })
|
|
2329
2391
|
] });
|
|
2330
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
|
+
}
|
|
2331
2396
|
|
|
2332
2397
|
// src/skins/meet/collab.tsx
|
|
2333
2398
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
@@ -3585,6 +3650,7 @@ function ControlBar({ onLeave }) {
|
|
|
3585
3650
|
) }) }) : null,
|
|
3586
3651
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BreakoutBanner, {}),
|
|
3587
3652
|
panel !== "people" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KnockNotice, { onView: () => setPanel("people") }) : null,
|
|
3653
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SpeakingWhileMutedNudge, {}),
|
|
3588
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,
|
|
3589
3655
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__controls", children: [
|
|
3590
3656
|
live.micLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -3596,6 +3662,7 @@ function ControlBar({ onLeave }) {
|
|
|
3596
3662
|
children: room.local?.micEnabled === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, {})
|
|
3597
3663
|
}
|
|
3598
3664
|
),
|
|
3665
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AudioSettingsControl, {}),
|
|
3599
3666
|
live.cameraLocked ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
3600
3667
|
ControlButton,
|
|
3601
3668
|
{
|
|
@@ -3711,6 +3778,13 @@ function ControlBar({ onLeave }) {
|
|
|
3711
3778
|
]
|
|
3712
3779
|
}
|
|
3713
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,
|
|
3714
3788
|
controls.isHost ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
3715
3789
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-meet__menu-sep", role: "separator" }),
|
|
3716
3790
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
@@ -3985,6 +4059,94 @@ function Toast({ text, failed, onDone }) {
|
|
|
3985
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 }) })
|
|
3986
4060
|
] });
|
|
3987
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
|
+
}
|
|
3988
4150
|
function MediaNotices() {
|
|
3989
4151
|
const media = useLocalMedia();
|
|
3990
4152
|
const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
|
|
@@ -4834,14 +4996,20 @@ async function playTestSound(sinkId) {
|
|
|
4834
4996
|
audio.pause();
|
|
4835
4997
|
await context.close();
|
|
4836
4998
|
}
|
|
4837
|
-
function useMicrophoneStream(deviceId) {
|
|
4999
|
+
function useMicrophoneStream(deviceId, report, attempt = 0) {
|
|
4838
5000
|
const [stream, setStream] = (0, import_react10.useState)(null);
|
|
5001
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
5002
|
+
reportRef.current = report;
|
|
4839
5003
|
(0, import_react10.useEffect)(() => {
|
|
4840
|
-
if (deviceId === "off")
|
|
5004
|
+
if (deviceId === "off") {
|
|
5005
|
+
reportRef.current?.("off");
|
|
5006
|
+
return void 0;
|
|
5007
|
+
}
|
|
4841
5008
|
let live = true;
|
|
4842
5009
|
let opened = null;
|
|
4843
5010
|
const media = globalThis.navigator?.mediaDevices;
|
|
4844
5011
|
if (media === void 0) return void 0;
|
|
5012
|
+
reportRef.current?.("prompting");
|
|
4845
5013
|
void media.getUserMedia({ audio: deviceId === null ? true : { deviceId: { exact: deviceId } } }).then((next) => {
|
|
4846
5014
|
if (!live) {
|
|
4847
5015
|
next.getTracks().forEach((track) => track.stop());
|
|
@@ -4849,15 +5017,49 @@ function useMicrophoneStream(deviceId) {
|
|
|
4849
5017
|
}
|
|
4850
5018
|
opened = next;
|
|
4851
5019
|
setStream(next);
|
|
4852
|
-
|
|
5020
|
+
reportRef.current?.("on");
|
|
5021
|
+
}).catch((thrown) => {
|
|
5022
|
+
setStream(null);
|
|
5023
|
+
if (live) reportRef.current?.({ error: thrown });
|
|
5024
|
+
});
|
|
4853
5025
|
return () => {
|
|
4854
5026
|
live = false;
|
|
4855
5027
|
opened?.getTracks().forEach((track) => track.stop());
|
|
4856
5028
|
setStream(null);
|
|
4857
5029
|
};
|
|
4858
|
-
}, [deviceId]);
|
|
5030
|
+
}, [deviceId, attempt]);
|
|
4859
5031
|
return stream;
|
|
4860
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
|
+
}
|
|
4861
5063
|
function KnockNotice({ onView }) {
|
|
4862
5064
|
const { knocker, others, admit, dismiss } = useKnockNotice();
|
|
4863
5065
|
if (knocker === null) return null;
|
|
@@ -5187,8 +5389,15 @@ function PreJoinBody({
|
|
|
5187
5389
|
const [joining, setJoining] = (0, import_react10.useState)(false);
|
|
5188
5390
|
const camera = devices?.selected.videoinput ?? host.devices.resolve("videoinput");
|
|
5189
5391
|
const mic = devices?.selected.audioinput ?? host.devices.resolve("audioinput");
|
|
5190
|
-
const
|
|
5191
|
-
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);
|
|
5192
5401
|
const level = useAudioLevel(micStream);
|
|
5193
5402
|
const [speakerTesting, setSpeakerTesting] = (0, import_react10.useState)(false);
|
|
5194
5403
|
(0, import_react10.useEffect)(() => {
|
|
@@ -5232,6 +5441,7 @@ function PreJoinBody({
|
|
|
5232
5441
|
] }),
|
|
5233
5442
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__prejoin-panel", children: [
|
|
5234
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) }),
|
|
5235
5445
|
notice?.kind === "removed" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__warning", children: notice.text }) : null,
|
|
5236
5446
|
devices?.labelsHidden === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__panel-note", children: [
|
|
5237
5447
|
"Your browser is hiding device names until you allow camera and microphone access.",
|
|
@@ -5334,16 +5544,22 @@ function PreJoinBody({
|
|
|
5334
5544
|
] })
|
|
5335
5545
|
] });
|
|
5336
5546
|
}
|
|
5337
|
-
function useCameraPreview(deviceId, effects) {
|
|
5547
|
+
function useCameraPreview(deviceId, effects, report, attempt = 0) {
|
|
5548
|
+
const reportRef = (0, import_react10.useRef)(report);
|
|
5549
|
+
reportRef.current = report;
|
|
5338
5550
|
const [preview, setPreview] = (0, import_react10.useState)(null);
|
|
5339
5551
|
const [error, setError] = (0, import_react10.useState)(null);
|
|
5340
5552
|
const current = (0, import_react10.useRef)(null);
|
|
5341
5553
|
const initialEffect = (0, import_react10.useRef)(toVideoEffect(effects));
|
|
5342
5554
|
initialEffect.current = toVideoEffect(effects);
|
|
5343
5555
|
(0, import_react10.useEffect)(() => {
|
|
5344
|
-
if (deviceId === void 0)
|
|
5556
|
+
if (deviceId === void 0) {
|
|
5557
|
+
reportRef.current?.("off");
|
|
5558
|
+
return void 0;
|
|
5559
|
+
}
|
|
5345
5560
|
let live = true;
|
|
5346
5561
|
setError(null);
|
|
5562
|
+
reportRef.current?.("prompting");
|
|
5347
5563
|
void createCameraPreview(deviceId, initialEffect.current).then((created) => {
|
|
5348
5564
|
if (!live) {
|
|
5349
5565
|
created.stop();
|
|
@@ -5351,8 +5567,10 @@ function useCameraPreview(deviceId, effects) {
|
|
|
5351
5567
|
}
|
|
5352
5568
|
current.current = created;
|
|
5353
5569
|
setPreview(created);
|
|
5570
|
+
reportRef.current?.("on");
|
|
5354
5571
|
}).catch((thrown) => {
|
|
5355
5572
|
if (!live) return;
|
|
5573
|
+
reportRef.current?.({ error: thrown });
|
|
5356
5574
|
const failure = thrown;
|
|
5357
5575
|
const message = (failure.message ?? "").replace(/^meet\.[A-Za-z.()]+:\s*/, "");
|
|
5358
5576
|
const sentence = message.length > 0 ? message.charAt(0).toUpperCase() + message.slice(1) : "";
|
|
@@ -5364,7 +5582,7 @@ function useCameraPreview(deviceId, effects) {
|
|
|
5364
5582
|
current.current = null;
|
|
5365
5583
|
setPreview(null);
|
|
5366
5584
|
};
|
|
5367
|
-
}, [deviceId]);
|
|
5585
|
+
}, [deviceId, attempt]);
|
|
5368
5586
|
return {
|
|
5369
5587
|
preview,
|
|
5370
5588
|
error,
|
|
@@ -5504,6 +5722,14 @@ function MeetSkinRoom({
|
|
|
5504
5722
|
}
|
|
5505
5723
|
function ShareNotice() {
|
|
5506
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
|
+
}
|
|
5507
5733
|
if (share.state !== "refused") return null;
|
|
5508
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";
|
|
5509
5735
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": "share-unavailable", children: [
|
|
@@ -5616,6 +5842,27 @@ function ExitScreen({ onLeave }) {
|
|
|
5616
5842
|
] });
|
|
5617
5843
|
break;
|
|
5618
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
|
+
}
|
|
5619
5866
|
title = "You lost your connection to the meeting";
|
|
5620
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.";
|
|
5621
5868
|
notice = "disconnected";
|