@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/core.js CHANGED
@@ -3502,11 +3502,13 @@ function consentPending(policy, role, recordingOn, given) {
3502
3502
  }
3503
3503
 
3504
3504
  // src/core/attendance.ts
3505
+ var IDLE_PREVIEW = { state: "idle", unavailable: null };
3505
3506
  var IDLE_MEDIA_REGION = {
3506
3507
  permission: { microphone: "unknown", camera: "unknown" },
3507
3508
  audioOutput: "ok",
3508
3509
  notice: null,
3509
- offer: null
3510
+ offer: null,
3511
+ preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
3510
3512
  };
3511
3513
  var IDLE_TRACK = { enabled: false, unavailable: null };
3512
3514
  var IDLE_MEDIA = {
@@ -4433,6 +4435,47 @@ function gridColumns(count2) {
4433
4435
  return 5;
4434
4436
  }
4435
4437
 
4438
+ // src/core/capabilities.ts
4439
+ var IN_APP = [
4440
+ [/Instagram/i, "Instagram"],
4441
+ [/FBAN|FBAV|FB_IAB/i, "Facebook"],
4442
+ [/LinkedInApp/i, "LinkedIn"],
4443
+ [/MicroMessenger/i, "WeChat"],
4444
+ [/Snapchat/i, "Snapchat"],
4445
+ [/\bLine\//i, "LINE"],
4446
+ [/Twitter|TwitterAndroid/i, "X"],
4447
+ [/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
4448
+ ];
4449
+ function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
4450
+ if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
4451
+ return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
4452
+ }
4453
+ function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
4454
+ if (scope === null || scope.navigator === void 0) {
4455
+ return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
4456
+ }
4457
+ const ua = scope.navigator.userAgent ?? "";
4458
+ const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
4459
+ const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
4460
+ const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
4461
+ return {
4462
+ canJoin: webrtc,
4463
+ reason: webrtc ? null : "no_webrtc",
4464
+ inAppBrowser,
4465
+ mobile,
4466
+ canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
4467
+ };
4468
+ }
4469
+ function unsupportedWords(capabilities) {
4470
+ if (capabilities.inAppBrowser !== null) {
4471
+ return {
4472
+ title: `${capabilities.inAppBrowser}'s browser can't join calls`,
4473
+ body: "Open this link in Chrome, Safari, Edge or Firefox"
4474
+ };
4475
+ }
4476
+ return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
4477
+ }
4478
+
4436
4479
  // src/core/room-driver.ts
4437
4480
  var DATA_TOPICS = {
4438
4481
  reaction: "mx.reaction",
@@ -4587,7 +4630,15 @@ function createLiveKitDriver(handlers, options = {}) {
4587
4630
  created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
4588
4631
  handlers.participantLeft?.(participant.identity, reasonName(reason));
4589
4632
  roster();
4590
- }).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) => {
4633
+ }).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
4634
+ if (publication.source === Track.Source.ScreenShare) {
4635
+ const ended = () => {
4636
+ void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
4637
+ };
4638
+ publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
4639
+ }
4640
+ roster();
4641
+ }).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
4591
4642
  handlers.roomMetadata?.(metadata ?? "");
4592
4643
  }).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
4593
4644
  handlers.audioPlayback?.(canPlay);
@@ -4728,7 +4779,10 @@ function createLiveKitDriver(handlers, options = {}) {
4728
4779
  },
4729
4780
  async setScreenShareEnabled(enabled) {
4730
4781
  try {
4731
- await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(enabled);
4782
+ await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
4783
+ enabled,
4784
+ enabled ? { audio: true, systemAudio: "include" } : void 0
4785
+ );
4732
4786
  } catch (error) {
4733
4787
  throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
4734
4788
  }
@@ -4767,8 +4821,13 @@ function createLiveKitDriver(handlers, options = {}) {
4767
4821
  supportsVideoEffects() {
4768
4822
  return videoEffectsSupported();
4769
4823
  },
4824
+ screenShareHasAudio() {
4825
+ const target = room;
4826
+ if (target === null) return false;
4827
+ return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
4828
+ },
4770
4829
  supportsScreenShare() {
4771
- return typeof navigator !== "undefined" && typeof navigator.mediaDevices?.getDisplayMedia === "function";
4830
+ return detectCapabilities().canShareScreen;
4772
4831
  },
4773
4832
  attach(identity, kind, element) {
4774
4833
  const target = requireRoom("attach");
@@ -6073,7 +6132,7 @@ function deviceFixSteps(kind, reason, where) {
6073
6132
  case "safari":
6074
6133
  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"];
6075
6134
  default:
6076
- return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
6135
+ return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
6077
6136
  }
6078
6137
  case "blocked_os": {
6079
6138
  const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
@@ -6171,7 +6230,7 @@ function createMediaSupervisor(options) {
6171
6230
  function setRegion(next) {
6172
6231
  const current = region();
6173
6232
  const merged = { ...current, ...next };
6174
- 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) {
6233
+ 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) {
6175
6234
  return;
6176
6235
  }
6177
6236
  store.patch({ mediaRegion: merged });
@@ -6267,7 +6326,7 @@ function createMediaSupervisor(options) {
6267
6326
  if (permission !== "unknown") setPermission(kind, permission);
6268
6327
  return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
6269
6328
  }
6270
- return {
6329
+ const api = {
6271
6330
  start() {
6272
6331
  if (started || platform === null) return;
6273
6332
  started = true;
@@ -6289,6 +6348,16 @@ function createMediaSupervisor(options) {
6289
6348
  started = false;
6290
6349
  },
6291
6350
  unavailable,
6351
+ async preview(kind, outcome) {
6352
+ api.start();
6353
+ let next;
6354
+ if (typeof outcome === "string") next = { state: outcome, unavailable: null };
6355
+ else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
6356
+ const current = region().preview;
6357
+ const was = current[kind];
6358
+ if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
6359
+ setRegion({ preview: { ...region().preview, [kind]: next } });
6360
+ },
6292
6361
  onAudioPlayback(canPlay) {
6293
6362
  setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
6294
6363
  },
@@ -6347,6 +6416,7 @@ function createMediaSupervisor(options) {
6347
6416
  setRegion({ notice: null });
6348
6417
  }
6349
6418
  };
6419
+ return api;
6350
6420
  }
6351
6421
 
6352
6422
  // src/core/room.ts
@@ -6508,7 +6578,8 @@ function createRoomEngine(options) {
6508
6578
  message: normalized.message,
6509
6579
  remedy: normalized.remedy,
6510
6580
  reason: normalized.reason,
6511
- retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
6581
+ retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
6582
+ stage: "join"
6512
6583
  }
6513
6584
  });
6514
6585
  store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
@@ -6899,6 +6970,10 @@ function createRoomEngine(options) {
6899
6970
  }
6900
6971
  departures.clear();
6901
6972
  liveRoster = list;
6973
+ const localNow = list.find((p) => p.isLocal);
6974
+ if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
6975
+ store.patch({ share: NO_SHARE });
6976
+ }
6902
6977
  const merged = mergeGhosts(list, ghosts, at);
6903
6978
  ghosts = merged.kept;
6904
6979
  const people = /* @__PURE__ */ new Map();
@@ -7118,7 +7193,10 @@ function createRoomEngine(options) {
7118
7193
  try {
7119
7194
  await driver.setScreenShareEnabled(enabled);
7120
7195
  ambiguousShareDenials = 0;
7121
- store.patch({ share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now() } });
7196
+ const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
7197
+ store.patch({
7198
+ share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
7199
+ });
7122
7200
  } catch (error) {
7123
7201
  const kind = classifyScreenShareError(error);
7124
7202
  if (kind === "ambiguous") {
@@ -7185,13 +7263,14 @@ function createRoomEngine(options) {
7185
7263
  return driver?.supportsVideoEffects?.() ?? false;
7186
7264
  },
7187
7265
  supportsScreenShare() {
7188
- return driver?.supportsScreenShare?.() ?? true;
7266
+ return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
7189
7267
  },
7190
7268
  async switchDevice(kind, deviceId) {
7191
7269
  if (driver === null) return;
7192
7270
  await driver.switchDevice(kind, deviceId);
7193
7271
  },
7194
7272
  enableAudio: () => media.enableAudio(),
7273
+ previewDevice: (kind, outcome) => media.preview(kind, outcome),
7195
7274
  acceptDeviceOffer: () => media.acceptOffer(),
7196
7275
  dismissDeviceOffer: () => media.dismissOffer(),
7197
7276
  dismissDeviceNotice: () => media.dismissNotice(),
@@ -8222,6 +8301,21 @@ function observedDevice(track, permission = "unknown", held = null) {
8222
8301
  const reason = deviceUnavailableReason(track, permission);
8223
8302
  return reason === null ? "muted_by_me" : `unavailable:${reason}`;
8224
8303
  }
8304
+ function observedPreview(preview, permission = "unknown") {
8305
+ if (permission === "denied") return "unavailable:denied";
8306
+ switch (preview.state) {
8307
+ case "on":
8308
+ return "on";
8309
+ case "off":
8310
+ return "muted_by_me";
8311
+ case "prompting":
8312
+ return "prompting";
8313
+ case "unavailable":
8314
+ return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
8315
+ default:
8316
+ return null;
8317
+ }
8318
+ }
8225
8319
  function endedRecord(snapshot, given) {
8226
8320
  const record = snapshot?.meeting ?? given;
8227
8321
  return record !== null && record.endedAt !== null;
@@ -8245,18 +8339,22 @@ function observe(input) {
8245
8339
  const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
8246
8340
  const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
8247
8341
  const deviceInPlay = phase === "joining" || inCall;
8342
+ const region = snapshot?.mediaRegion;
8343
+ const atPrejoin = phase === "prejoin" && region !== void 0;
8248
8344
  return {
8249
8345
  phase,
8250
8346
  reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
8251
8347
  role,
8252
- connection: snapshot?.connection ?? "stable",
8348
+ // `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
8349
+ // person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
8350
+ connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
8253
8351
  hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
8254
8352
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
8255
8353
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
8256
8354
  // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
8257
8355
  audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
8258
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : null,
8259
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : null
8356
+ 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,
8357
+ 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
8260
8358
  };
8261
8359
  }
8262
8360
  function lockedFor(snapshot, kind, inCall) {
@@ -8336,6 +8434,39 @@ function microphoneNoticeCode(track, permission = "unknown") {
8336
8434
  if (reason === null) return null;
8337
8435
  return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
8338
8436
  }
8437
+
8438
+ // src/core/speaking-detector.ts
8439
+ function createSpeakingWhileMutedDetector(options = {}) {
8440
+ const threshold = options.threshold ?? 0.06;
8441
+ const holdMs = options.holdMs ?? 600;
8442
+ const gapMs = options.gapMs ?? 400;
8443
+ const cooldownMs = options.cooldownMs ?? 2e4;
8444
+ let speechStart = null;
8445
+ let lastLoud = null;
8446
+ let firedAt = null;
8447
+ return {
8448
+ feed(level, at) {
8449
+ if (level >= threshold) {
8450
+ if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
8451
+ lastLoud = at;
8452
+ } else if (lastLoud !== null && at - lastLoud > gapMs) {
8453
+ speechStart = null;
8454
+ lastLoud = null;
8455
+ }
8456
+ if (speechStart === null || at - speechStart < holdMs) return false;
8457
+ if (firedAt !== null && at - firedAt < cooldownMs) return false;
8458
+ firedAt = at;
8459
+ speechStart = null;
8460
+ lastLoud = null;
8461
+ return true;
8462
+ },
8463
+ reset() {
8464
+ speechStart = null;
8465
+ lastLoud = null;
8466
+ firedAt = null;
8467
+ }
8468
+ };
8469
+ }
8339
8470
  export {
8340
8471
  ATTENDANCE_CSV_HEADER,
8341
8472
  CALL_INVITE_KIND,
@@ -8453,6 +8584,7 @@ export {
8453
8584
  createReconnectPolicy,
8454
8585
  createRecordingController,
8455
8586
  createRoomEngine,
8587
+ createSpeakingWhileMutedDetector,
8456
8588
  createTokenClient,
8457
8589
  createWebDeviceStorage,
8458
8590
  createWebEffectsStorage,
@@ -8461,6 +8593,7 @@ export {
8461
8593
  createWebRoomTokenStorage,
8462
8594
  defaultCaptionLanguage,
8463
8595
  detectBrowser,
8596
+ detectCapabilities,
8464
8597
  detectOs,
8465
8598
  deviceChipLabel,
8466
8599
  deviceFixSteps,
@@ -8485,6 +8618,7 @@ export {
8485
8618
  isLinkFollower,
8486
8619
  isListenOnly,
8487
8620
  isLivePhase,
8621
+ isMobileUserAgent,
8488
8622
  isNoteTaker,
8489
8623
  isObserverRole,
8490
8624
  isRealName,
@@ -8576,6 +8710,7 @@ export {
8576
8710
  transcriptDocument,
8577
8711
  unavailableFrom,
8578
8712
  uniqueAttendees,
8713
+ unsupportedWords,
8579
8714
  untranslatedLines,
8580
8715
  userTopic,
8581
8716
  wasRefused,