@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/dist/index.cjs CHANGED
@@ -146,6 +146,7 @@ __export(src_exports, {
146
146
  createReconnectPolicy: () => createReconnectPolicy,
147
147
  createRecordingController: () => createRecordingController,
148
148
  createRoomEngine: () => createRoomEngine,
149
+ createSpeakingWhileMutedDetector: () => createSpeakingWhileMutedDetector,
149
150
  createTokenClient: () => createTokenClient,
150
151
  createWebDeviceStorage: () => createWebDeviceStorage,
151
152
  createWebEffectsStorage: () => createWebEffectsStorage,
@@ -154,6 +155,7 @@ __export(src_exports, {
154
155
  createWebRoomTokenStorage: () => createWebRoomTokenStorage,
155
156
  defaultCaptionLanguage: () => defaultCaptionLanguage,
156
157
  detectBrowser: () => detectBrowser,
158
+ detectCapabilities: () => detectCapabilities,
157
159
  detectOs: () => detectOs,
158
160
  deviceChipLabel: () => deviceChipLabel,
159
161
  deviceFixSteps: () => deviceFixSteps,
@@ -178,6 +180,7 @@ __export(src_exports, {
178
180
  isLinkFollower: () => isLinkFollower,
179
181
  isListenOnly: () => isListenOnly,
180
182
  isLivePhase: () => isLivePhase,
183
+ isMobileUserAgent: () => isMobileUserAgent,
181
184
  isNoteTaker: () => isNoteTaker,
182
185
  isObserverRole: () => isObserverRole,
183
186
  isRealName: () => isRealName,
@@ -269,6 +272,7 @@ __export(src_exports, {
269
272
  transcriptDocument: () => transcriptDocument,
270
273
  unavailableFrom: () => unavailableFrom,
271
274
  uniqueAttendees: () => uniqueAttendees,
275
+ unsupportedWords: () => unsupportedWords,
272
276
  untranslatedLines: () => untranslatedLines,
273
277
  userTopic: () => userTopic,
274
278
  wasRefused: () => wasRefused,
@@ -3782,11 +3786,13 @@ function consentPending(policy, role, recordingOn, given) {
3782
3786
  }
3783
3787
 
3784
3788
  // src/core/attendance.ts
3789
+ var IDLE_PREVIEW = { state: "idle", unavailable: null };
3785
3790
  var IDLE_MEDIA_REGION = {
3786
3791
  permission: { microphone: "unknown", camera: "unknown" },
3787
3792
  audioOutput: "ok",
3788
3793
  notice: null,
3789
- offer: null
3794
+ offer: null,
3795
+ preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
3790
3796
  };
3791
3797
  var IDLE_TRACK = { enabled: false, unavailable: null };
3792
3798
  var IDLE_MEDIA = {
@@ -4713,6 +4719,47 @@ function gridColumns(count2) {
4713
4719
  return 5;
4714
4720
  }
4715
4721
 
4722
+ // src/core/capabilities.ts
4723
+ var IN_APP = [
4724
+ [/Instagram/i, "Instagram"],
4725
+ [/FBAN|FBAV|FB_IAB/i, "Facebook"],
4726
+ [/LinkedInApp/i, "LinkedIn"],
4727
+ [/MicroMessenger/i, "WeChat"],
4728
+ [/Snapchat/i, "Snapchat"],
4729
+ [/\bLine\//i, "LINE"],
4730
+ [/Twitter|TwitterAndroid/i, "X"],
4731
+ [/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
4732
+ ];
4733
+ function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
4734
+ if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
4735
+ return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
4736
+ }
4737
+ function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
4738
+ if (scope === null || scope.navigator === void 0) {
4739
+ return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
4740
+ }
4741
+ const ua = scope.navigator.userAgent ?? "";
4742
+ const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
4743
+ const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
4744
+ const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
4745
+ return {
4746
+ canJoin: webrtc,
4747
+ reason: webrtc ? null : "no_webrtc",
4748
+ inAppBrowser,
4749
+ mobile,
4750
+ canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
4751
+ };
4752
+ }
4753
+ function unsupportedWords(capabilities) {
4754
+ if (capabilities.inAppBrowser !== null) {
4755
+ return {
4756
+ title: `${capabilities.inAppBrowser}'s browser can't join calls`,
4757
+ body: "Open this link in Chrome, Safari, Edge or Firefox"
4758
+ };
4759
+ }
4760
+ return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
4761
+ }
4762
+
4716
4763
  // src/core/room-driver.ts
4717
4764
  var DATA_TOPICS = {
4718
4765
  reaction: "mx.reaction",
@@ -4867,7 +4914,15 @@ function createLiveKitDriver(handlers, options = {}) {
4867
4914
  created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
4868
4915
  handlers.participantLeft?.(participant.identity, reasonName(reason));
4869
4916
  roster();
4870
- }).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
4917
+ }).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
4918
+ if (publication.source === Track.Source.ScreenShare) {
4919
+ const ended = () => {
4920
+ void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
4921
+ };
4922
+ publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
4923
+ }
4924
+ roster();
4925
+ }).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
4871
4926
  handlers.roomMetadata?.(metadata ?? "");
4872
4927
  }).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
4873
4928
  handlers.audioPlayback?.(canPlay);
@@ -5008,7 +5063,10 @@ function createLiveKitDriver(handlers, options = {}) {
5008
5063
  },
5009
5064
  async setScreenShareEnabled(enabled) {
5010
5065
  try {
5011
- await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(enabled);
5066
+ await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
5067
+ enabled,
5068
+ enabled ? { audio: true, systemAudio: "include" } : void 0
5069
+ );
5012
5070
  } catch (error) {
5013
5071
  throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
5014
5072
  }
@@ -5047,8 +5105,13 @@ function createLiveKitDriver(handlers, options = {}) {
5047
5105
  supportsVideoEffects() {
5048
5106
  return videoEffectsSupported();
5049
5107
  },
5108
+ screenShareHasAudio() {
5109
+ const target = room;
5110
+ if (target === null) return false;
5111
+ return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
5112
+ },
5050
5113
  supportsScreenShare() {
5051
- return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
5114
+ return detectCapabilities().canShareScreen;
5052
5115
  },
5053
5116
  attach(identity, kind, element) {
5054
5117
  const target = requireRoom("attach");
@@ -6353,7 +6416,7 @@ function deviceFixSteps(kind, reason, where) {
6353
6416
  case "safari":
6354
6417
  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"];
6355
6418
  default:
6356
- return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
6419
+ return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
6357
6420
  }
6358
6421
  case "blocked_os": {
6359
6422
  const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
@@ -6451,7 +6514,7 @@ function createMediaSupervisor(options) {
6451
6514
  function setRegion(next) {
6452
6515
  const current = region();
6453
6516
  const merged = { ...current, ...next };
6454
- if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
6517
+ if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.preview === current.preview && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
6455
6518
  return;
6456
6519
  }
6457
6520
  store.patch({ mediaRegion: merged });
@@ -6547,7 +6610,7 @@ function createMediaSupervisor(options) {
6547
6610
  if (permission !== "unknown") setPermission(kind, permission);
6548
6611
  return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
6549
6612
  }
6550
- return {
6613
+ const api = {
6551
6614
  start() {
6552
6615
  if (started || platform === null) return;
6553
6616
  started = true;
@@ -6569,6 +6632,16 @@ function createMediaSupervisor(options) {
6569
6632
  started = false;
6570
6633
  },
6571
6634
  unavailable,
6635
+ async preview(kind, outcome) {
6636
+ api.start();
6637
+ let next;
6638
+ if (typeof outcome === "string") next = { state: outcome, unavailable: null };
6639
+ else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
6640
+ const current = region().preview;
6641
+ const was = current[kind];
6642
+ if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
6643
+ setRegion({ preview: { ...region().preview, [kind]: next } });
6644
+ },
6572
6645
  onAudioPlayback(canPlay) {
6573
6646
  setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
6574
6647
  },
@@ -6627,6 +6700,7 @@ function createMediaSupervisor(options) {
6627
6700
  setRegion({ notice: null });
6628
6701
  }
6629
6702
  };
6703
+ return api;
6630
6704
  }
6631
6705
 
6632
6706
  // src/core/room.ts
@@ -6788,7 +6862,8 @@ function createRoomEngine(options) {
6788
6862
  message: normalized.message,
6789
6863
  remedy: normalized.remedy,
6790
6864
  reason: normalized.reason,
6791
- retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
6865
+ retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
6866
+ stage: "join"
6792
6867
  }
6793
6868
  });
6794
6869
  store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
@@ -7179,6 +7254,10 @@ function createRoomEngine(options) {
7179
7254
  }
7180
7255
  departures.clear();
7181
7256
  liveRoster = list;
7257
+ const localNow = list.find((p) => p.isLocal);
7258
+ if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
7259
+ store.patch({ share: NO_SHARE });
7260
+ }
7182
7261
  const merged = mergeGhosts(list, ghosts, at);
7183
7262
  ghosts = merged.kept;
7184
7263
  const people = /* @__PURE__ */ new Map();
@@ -7398,7 +7477,10 @@ function createRoomEngine(options) {
7398
7477
  try {
7399
7478
  await driver.setScreenShareEnabled(enabled);
7400
7479
  ambiguousShareDenials = 0;
7401
- store.patch({ share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now() } });
7480
+ const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
7481
+ store.patch({
7482
+ share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
7483
+ });
7402
7484
  } catch (error) {
7403
7485
  const kind = classifyScreenShareError(error);
7404
7486
  if (kind === "ambiguous") {
@@ -7465,13 +7547,14 @@ function createRoomEngine(options) {
7465
7547
  return driver?.supportsVideoEffects?.() ?? false;
7466
7548
  },
7467
7549
  supportsScreenShare() {
7468
- return driver?.supportsScreenShare?.() ?? true;
7550
+ return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
7469
7551
  },
7470
7552
  async switchDevice(kind, deviceId) {
7471
7553
  if (driver === null) return;
7472
7554
  await driver.switchDevice(kind, deviceId);
7473
7555
  },
7474
7556
  enableAudio: () => media.enableAudio(),
7557
+ previewDevice: (kind, outcome) => media.preview(kind, outcome),
7475
7558
  acceptDeviceOffer: () => media.acceptOffer(),
7476
7559
  dismissDeviceOffer: () => media.dismissOffer(),
7477
7560
  dismissDeviceNotice: () => media.dismissNotice(),
@@ -8502,6 +8585,21 @@ function observedDevice(track, permission = "unknown", held = null) {
8502
8585
  const reason = deviceUnavailableReason(track, permission);
8503
8586
  return reason === null ? "muted_by_me" : `unavailable:${reason}`;
8504
8587
  }
8588
+ function observedPreview(preview, permission = "unknown") {
8589
+ if (permission === "denied") return "unavailable:denied";
8590
+ switch (preview.state) {
8591
+ case "on":
8592
+ return "on";
8593
+ case "off":
8594
+ return "muted_by_me";
8595
+ case "prompting":
8596
+ return "prompting";
8597
+ case "unavailable":
8598
+ return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
8599
+ default:
8600
+ return null;
8601
+ }
8602
+ }
8505
8603
  function endedRecord(snapshot, given) {
8506
8604
  const record = snapshot?.meeting ?? given;
8507
8605
  return record !== null && record.endedAt !== null;
@@ -8525,18 +8623,22 @@ function observe(input) {
8525
8623
  const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
8526
8624
  const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
8527
8625
  const deviceInPlay = phase === "joining" || inCall;
8626
+ const region = snapshot?.mediaRegion;
8627
+ const atPrejoin = phase === "prejoin" && region !== void 0;
8528
8628
  return {
8529
8629
  phase,
8530
8630
  reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
8531
8631
  role,
8532
- connection: snapshot?.connection ?? "stable",
8632
+ // `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
8633
+ // person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
8634
+ connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
8533
8635
  hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
8534
8636
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
8535
8637
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
8536
8638
  // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
8537
8639
  audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
8538
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : null,
8539
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : null
8640
+ 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,
8641
+ 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
8540
8642
  };
8541
8643
  }
8542
8644
  function lockedFor(snapshot, kind, inCall) {
@@ -8616,4 +8718,37 @@ function microphoneNoticeCode(track, permission = "unknown") {
8616
8718
  if (reason === null) return null;
8617
8719
  return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
8618
8720
  }
8721
+
8722
+ // src/core/speaking-detector.ts
8723
+ function createSpeakingWhileMutedDetector(options = {}) {
8724
+ const threshold = options.threshold ?? 0.06;
8725
+ const holdMs = options.holdMs ?? 600;
8726
+ const gapMs = options.gapMs ?? 400;
8727
+ const cooldownMs = options.cooldownMs ?? 2e4;
8728
+ let speechStart = null;
8729
+ let lastLoud = null;
8730
+ let firedAt = null;
8731
+ return {
8732
+ feed(level, at) {
8733
+ if (level >= threshold) {
8734
+ if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
8735
+ lastLoud = at;
8736
+ } else if (lastLoud !== null && at - lastLoud > gapMs) {
8737
+ speechStart = null;
8738
+ lastLoud = null;
8739
+ }
8740
+ if (speechStart === null || at - speechStart < holdMs) return false;
8741
+ if (firedAt !== null && at - firedAt < cooldownMs) return false;
8742
+ firedAt = at;
8743
+ speechStart = null;
8744
+ lastLoud = null;
8745
+ return true;
8746
+ },
8747
+ reset() {
8748
+ speechStart = null;
8749
+ lastLoud = null;
8750
+ firedAt = null;
8751
+ }
8752
+ };
8753
+ }
8619
8754
  //# sourceMappingURL=index.cjs.map