@ai-matrx/meet 0.11.50 → 0.11.52

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/dist/react.d.cts CHANGED
@@ -341,6 +341,8 @@ interface UseCaptionsResult {
341
341
  readonly reason: string | null;
342
342
  readonly remedy: string | null;
343
343
  readonly toggle: () => void;
344
+ /** Try again after `unavailable`: asks for captions afresh and restarts the start ceiling. */
345
+ readonly retry: () => void;
344
346
  }
345
347
  declare function useCaptions(): UseCaptionsResult;
346
348
  /**
package/dist/react.d.ts CHANGED
@@ -341,6 +341,8 @@ interface UseCaptionsResult {
341
341
  readonly reason: string | null;
342
342
  readonly remedy: string | null;
343
343
  readonly toggle: () => void;
344
+ /** Try again after `unavailable`: asks for captions afresh and restarts the start ceiling. */
345
+ readonly retry: () => void;
344
346
  }
345
347
  declare function useCaptions(): UseCaptionsResult;
346
348
  /**
package/dist/react.js CHANGED
@@ -151,7 +151,8 @@ var DEFAULT_CONNECTION_POLICY = {
151
151
  autoRejoin: true,
152
152
  autoRejoinMaxAttempts: 5,
153
153
  reconnectGraceSeconds: 30,
154
- videoAutoOffAfterPoorSeconds: 20
154
+ videoAutoOffAfterPoorSeconds: 20,
155
+ captionsStartTimeoutSeconds: 20
155
156
  };
156
157
  function clampInt(value, min, max, fallback) {
157
158
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
@@ -166,7 +167,8 @@ function connectionPolicyFrom(raw) {
166
167
  autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
167
168
  autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
168
169
  reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds),
169
- videoAutoOffAfterPoorSeconds: clampInt(row["video_auto_off_after_poor_seconds"], 0, 600, d.videoAutoOffAfterPoorSeconds)
170
+ videoAutoOffAfterPoorSeconds: clampInt(row["video_auto_off_after_poor_seconds"], 0, 600, d.videoAutoOffAfterPoorSeconds),
171
+ captionsStartTimeoutSeconds: clampInt(row["captions_start_timeout_seconds"], 1, 600, d.captionsStartTimeoutSeconds)
170
172
  };
171
173
  }
172
174
  var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
@@ -4030,6 +4032,7 @@ var EMPTY = {
4030
4032
  noteTakerRefusal: null,
4031
4033
  captionsOn: false,
4032
4034
  captionsRefusal: null,
4035
+ captionsStartTimeoutSeconds: 20,
4033
4036
  media: IDLE_MEDIA,
4034
4037
  mediaRegion: IDLE_MEDIA_REGION,
4035
4038
  failure: null,
@@ -5781,6 +5784,16 @@ var ALIVE_TOPIC = "mx.alive";
5781
5784
  function encodeBeat(beat) {
5782
5785
  return JSON.stringify({ q: beat.q, vp: beat.vp });
5783
5786
  }
5787
+ function encodeBye() {
5788
+ return JSON.stringify({ bye: true });
5789
+ }
5790
+ function isBye(payload) {
5791
+ try {
5792
+ return JSON.parse(payload).bye === true;
5793
+ } catch {
5794
+ return false;
5795
+ }
5796
+ }
5784
5797
  function decodeBeat(payload) {
5785
5798
  try {
5786
5799
  const raw = JSON.parse(payload);
@@ -6007,6 +6020,7 @@ function createRoomEngine(options) {
6007
6020
  const autoOff = createVideoAutoOff(() => connectionPolicy.videoAutoOffAfterPoorSeconds * 1e3, now);
6008
6021
  const lkQuality = /* @__PURE__ */ new Map();
6009
6022
  let silent = /* @__PURE__ */ new Set();
6023
+ const saidBye = /* @__PURE__ */ new Set();
6010
6024
  let healthTimer = null;
6011
6025
  let lastBeatAt = 0;
6012
6026
  let wasUnstable = false;
@@ -6037,6 +6051,7 @@ function createRoomEngine(options) {
6037
6051
  assessor.reset();
6038
6052
  autoOff.reset();
6039
6053
  silent = /* @__PURE__ */ new Set();
6054
+ saidBye.clear();
6040
6055
  lkQuality.clear();
6041
6056
  lastBeatAt = 0;
6042
6057
  wasUnstable = false;
@@ -6094,6 +6109,13 @@ function createRoomEngine(options) {
6094
6109
  await target?.disconnect();
6095
6110
  }
6096
6111
  async function leaveRoom() {
6112
+ const target = driver;
6113
+ if (target !== null && store.snapshot().phase === "in_call") {
6114
+ await Promise.race([
6115
+ target.publishData(ALIVE_TOPIC, encodeBye()).catch(() => void 0),
6116
+ new Promise((resolve) => setTimeout(resolve, 300))
6117
+ ]);
6118
+ }
6097
6119
  store.dispatch({ type: "leave" });
6098
6120
  await dropRoom();
6099
6121
  options.onLeft?.(null);
@@ -6189,8 +6211,6 @@ function createRoomEngine(options) {
6189
6211
  },
6190
6212
  participantLeft(who, reason) {
6191
6213
  departures.set(who, reason);
6192
- liveness.forget(who);
6193
- silent.delete(who);
6194
6214
  },
6195
6215
  roomMetadata(metadata) {
6196
6216
  let parsed = {};
@@ -6233,8 +6253,12 @@ function createRoomEngine(options) {
6233
6253
  const snap = store.snapshot();
6234
6254
  if (snap.phase !== "in_call" || snap.connection !== "stable") return;
6235
6255
  startHealth();
6256
+ const before = assessor.current();
6236
6257
  const quality = assessor.push(reading);
6237
6258
  const path = pathOf(reading);
6259
+ if (quality !== before) {
6260
+ report("info", `meet: this connection reads ${quality} (round trip ${reading.rttMs ?? "?"} ms, path ${path})`, "Diagnostic only.");
6261
+ }
6238
6262
  patchLocalNetwork({ quality, ...path !== "unknown" ? { path } : {} });
6239
6263
  refreshQualities();
6240
6264
  const cameraOn = snap.media.camera.enabled;
@@ -6249,6 +6273,11 @@ function createRoomEngine(options) {
6249
6273
  connectionState(state) {
6250
6274
  supervisor.driverState(state);
6251
6275
  if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
6276
+ if (state === "connected") {
6277
+ startHealth();
6278
+ lastBeatAt = 0;
6279
+ healthTick();
6280
+ }
6252
6281
  },
6253
6282
  transcription(event) {
6254
6283
  const snapshot = store.snapshot();
@@ -6340,6 +6369,10 @@ function createRoomEngine(options) {
6340
6369
  }
6341
6370
  const sender = personOf(from ?? "");
6342
6371
  if (from !== null) {
6372
+ if (topic === ALIVE_TOPIC && isBye(payload)) {
6373
+ saidBye.add(from);
6374
+ return;
6375
+ }
6343
6376
  const beat = topic === ALIVE_TOPIC ? decodeBeat(payload) : null;
6344
6377
  const before = liveness.reported(from);
6345
6378
  liveness.heard(from, beat);
@@ -6454,6 +6487,7 @@ function createRoomEngine(options) {
6454
6487
  current = token;
6455
6488
  lastServerUrl = token.serverUrl;
6456
6489
  connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
6490
+ store.patch({ captionsStartTimeoutSeconds: connectionPolicy.captionsStartTimeoutSeconds });
6457
6491
  if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
6458
6492
  store.patch({ canReclaim: token.canReclaim === true });
6459
6493
  store.patch({
@@ -6571,10 +6605,17 @@ function createRoomEngine(options) {
6571
6605
  const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
6572
6606
  for (const [who, reason] of departures) {
6573
6607
  const last = previous.get(who);
6574
- if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
6608
+ const dropped = !saidBye.has(who) && (isUnannouncedDeparture(reason) || silent.has(who));
6609
+ report("info", `meet: ${who} left the room (${reason ?? "no reason"}${dropped ? ", held as reconnecting" : ""})`, "Diagnostic only.");
6610
+ if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && dropped) {
6575
6611
  ghosts.set(who, { participant: last, until: at + graceMs });
6576
6612
  }
6577
6613
  }
6614
+ for (const who of departures.keys()) {
6615
+ liveness.forget(who);
6616
+ silent.delete(who);
6617
+ saidBye.delete(who);
6618
+ }
6578
6619
  departures.clear();
6579
6620
  liveRoster = list;
6580
6621
  const localNow = list.find((p) => p.isLocal);
@@ -8901,6 +8942,22 @@ function createMeetingRecordCache(options = {}) {
8901
8942
  };
8902
8943
  }
8903
8944
 
8945
+ // src/core/captions-state.ts
8946
+ var CAPTIONS_TIMEOUT_REASON = "Captions are unavailable right now.";
8947
+ function resolveCaptionsState(i) {
8948
+ if (!i.on) return { state: "off", reason: null, remedy: null };
8949
+ if (i.takerState === "transcribing" && i.takerPresent) return { state: "live", reason: null, remedy: null };
8950
+ if (i.takerState === "joining" && !i.timedOut) return { state: "starting", reason: null, remedy: null };
8951
+ if (i.refusal !== null && i.refusal.reason.length > 0) {
8952
+ return { state: "unavailable", reason: i.refusal.reason, remedy: i.refusal.remedy.length > 0 ? i.refusal.remedy : null };
8953
+ }
8954
+ if (i.takerState === "ended" || i.takerState === "handoff_pending") {
8955
+ return { state: "unavailable", reason: i.takerLabel, remedy: null };
8956
+ }
8957
+ if (i.timedOut) return { state: "unavailable", reason: CAPTIONS_TIMEOUT_REASON, remedy: null };
8958
+ return { state: "starting", reason: null, remedy: null };
8959
+ }
8960
+
8904
8961
  // src/core/layout.ts
8905
8962
  var DEFAULT_PAGE_SIZE = 12;
8906
8963
  function orderParticipants(participants, activeSpeakers) {
@@ -8991,7 +9048,8 @@ function useRoom() {
8991
9048
  local,
8992
9049
  // An observer (role `viewer`) publishes nothing: no Share screen to press and be refused (MD-16).
8993
9050
  sharingAllowed: snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot)),
8994
- canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
9051
+ canShareScreen: (engine?.supportsScreenShare() ?? true) && !isObserverRole(snapshot?.grants.role ?? null) && // The server's word that sharing was taken away arrives before the rules refresh does: the control goes at once.
9052
+ !(snapshot?.share.state === "ended" && snapshot.share.reason === "not_allowed") && (snapshot === null || canShareUnder(snapshot.livePolicy, effectiveRole(snapshot), effectiveStanding(snapshot))),
8995
9053
  otherPresenter: otherPresenterOf(snapshot),
8996
9054
  join: useCallback2(
8997
9055
  async (request) => requireEngine(engine, "join").join(request),
@@ -9388,32 +9446,37 @@ function useCaptions() {
9388
9446
  if (host === null || meeting === null) return;
9389
9447
  void host.ai.requestCaptions({ meetingId: meeting.id });
9390
9448
  }, [host, meeting]);
9449
+ const [timedOut, setTimedOut] = useState3(false);
9450
+ const timeoutSeconds = snapshot?.captionsStartTimeoutSeconds ?? 20;
9451
+ const live = taker.state === "transcribing" && taker.present;
9452
+ const [attempt, setAttempt] = useState3(0);
9453
+ useEffect3(() => {
9454
+ setTimedOut(false);
9455
+ if (!on || live) return void 0;
9456
+ const id = setTimeout(() => setTimedOut(true), timeoutSeconds * 1e3);
9457
+ return () => clearTimeout(id);
9458
+ }, [on, live, timeoutSeconds, attempt]);
9391
9459
  const toggle = useCallback2(() => {
9392
9460
  if (host === null) return;
9393
9461
  const next = !on;
9394
9462
  host.store.patch({ captionsOn: next, captionsRefusal: null });
9395
9463
  if (next && taker.state !== "joining" && taker.state !== "transcribing") request();
9396
9464
  }, [host, on, taker.state, request]);
9397
- let state = "off";
9398
- let reason = null;
9399
- let remedy = null;
9400
- if (on) {
9401
- if (taker.state === "transcribing" && taker.present) {
9402
- state = "live";
9403
- } else if (taker.state === "joining") {
9404
- state = "starting";
9405
- } else if (refusal2 !== null && refusal2.reason.length > 0) {
9406
- state = "unavailable";
9407
- reason = refusal2.reason;
9408
- remedy = refusal2.remedy.length > 0 ? refusal2.remedy : null;
9409
- } else if (taker.state === "ended" || taker.state === "handoff_pending") {
9410
- state = "unavailable";
9411
- reason = noteTakerLabel(taker);
9412
- } else {
9413
- state = "starting";
9414
- }
9415
- }
9416
- return { on, state, reason, remedy, toggle };
9465
+ const retry = useCallback2(() => {
9466
+ if (host === null) return;
9467
+ host.store.patch({ captionsOn: true, captionsRefusal: null });
9468
+ setAttempt((n) => n + 1);
9469
+ request();
9470
+ }, [host, request]);
9471
+ const view = resolveCaptionsState({
9472
+ on,
9473
+ takerState: taker.state,
9474
+ takerPresent: taker.present,
9475
+ refusal: refusal2,
9476
+ takerLabel: on ? noteTakerLabel(taker) : "",
9477
+ timedOut
9478
+ });
9479
+ return { on, state: view.state, reason: view.reason, remedy: view.remedy, toggle, retry };
9417
9480
  }
9418
9481
  function useEndMeeting() {
9419
9482
  const host = useMeetHost();
@@ -13932,11 +13995,14 @@ function Captions({ lines = 3 }) {
13932
13995
  const captions = useCaptions();
13933
13996
  if (!captions.on) return null;
13934
13997
  if (captions.state === "unavailable") {
13935
- return /* @__PURE__ */ jsx6("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: /* @__PURE__ */ jsxs4("p", { className: "mx-meet__caption", children: [
13936
- "Captions are unavailable. ",
13937
- captions.reason,
13938
- captions.remedy !== null ? ` ${captions.remedy}` : ""
13939
- ] }) });
13998
+ return /* @__PURE__ */ jsxs4("div", { className: "mx-meet__captions", role: "status", "data-meet-notice": "captions-unavailable", children: [
13999
+ /* @__PURE__ */ jsxs4("p", { className: "mx-meet__caption", children: [
14000
+ (captions.reason ?? "").startsWith("Captions are unavailable") ? "" : "Captions are unavailable. ",
14001
+ captions.reason,
14002
+ captions.remedy !== null ? ` ${captions.remedy}` : ""
14003
+ ] }),
14004
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__caption-retry", onClick: captions.retry, children: "Try again" })
14005
+ ] });
13940
14006
  }
13941
14007
  if (visible.length === 0) {
13942
14008
  return /* @__PURE__ */ jsx6("div", { className: "mx-meet__captions", "aria-live": "polite", "aria-label": "Live captions", "data-meet-caption": captions.state, children: /* @__PURE__ */ jsx6("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." }) });