@ai-matrx/meet 0.11.29 → 0.11.31

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog — `@ai-matrx/meet`
2
2
 
3
+ ## 0.11.31
4
+
5
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
6
+ `git diff npm/meet/v0.11.30..npm/meet/v0.11.31 -- apps/shared/meet`).
7
+ No source changes intended and no consumer action required.
8
+
9
+ ## 0.11.30
10
+
11
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
12
+ `git diff npm/meet/v0.11.29..npm/meet/v0.11.30 -- apps/shared/meet`).
13
+ No source changes intended and no consumer action required.
14
+
3
15
  ## 0.11.29
4
16
 
5
17
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/core.cjs CHANGED
@@ -47,6 +47,7 @@ __export(core_exports, {
47
47
  GUEST_RECORD_ACCESS_METADATA_KEY: () => GUEST_RECORD_ACCESS_METADATA_KEY,
48
48
  GUEST_RECORD_NOT_SHARED: () => GUEST_RECORD_NOT_SHARED,
49
49
  IDLE_MEDIA: () => IDLE_MEDIA,
50
+ IDLE_MEDIA_REGION: () => IDLE_MEDIA_REGION,
50
51
  IDLE_PHASES: () => IDLE_PHASES,
51
52
  IDLE_TRACK: () => IDLE_TRACK,
52
53
  JOIN_REFUSAL_REASONS: () => JOIN_REFUSAL_REASONS,
@@ -117,6 +118,7 @@ __export(core_exports, {
117
118
  captionSegment: () => captionSegment,
118
119
  captureHeldOf: () => captureHeldOf,
119
120
  changesPatch: () => changesPatch,
121
+ classifyDeviceFailure: () => classifyDeviceFailure,
120
122
  clockPosition: () => clockPosition,
121
123
  collabFromState: () => collabFromState,
122
124
  computeStage: () => computeStage,
@@ -128,6 +130,7 @@ __export(core_exports, {
128
130
  createDeviceManager: () => createDeviceManager,
129
131
  createInRoomClient: () => createInRoomClient,
130
132
  createLiveKitDriver: () => createLiveKitDriver,
133
+ createMediaSupervisor: () => createMediaSupervisor,
131
134
  createMeetAi: () => createMeetAi,
132
135
  createMeetApi: () => createMeetApi,
133
136
  createMeetRepository: () => createMeetRepository,
@@ -147,6 +150,12 @@ __export(core_exports, {
147
150
  createWebResumeStorage: () => createWebResumeStorage,
148
151
  createWebRoomTokenStorage: () => createWebRoomTokenStorage,
149
152
  defaultCaptionLanguage: () => defaultCaptionLanguage,
153
+ detectBrowser: () => detectBrowser,
154
+ detectOs: () => detectOs,
155
+ deviceChipLabel: () => deviceChipLabel,
156
+ deviceFixSteps: () => deviceFixSteps,
157
+ deviceNoticeCode: () => deviceNoticeCode,
158
+ deviceUnavailableReason: () => deviceUnavailableReason,
150
159
  displayNameFor: () => displayNameFor,
151
160
  fileDownloadPath: () => fileDownloadPath,
152
161
  focusFromRow: () => focusFromRow,
@@ -258,6 +267,7 @@ __export(core_exports, {
258
267
  untranslatedLines: () => untranslatedLines,
259
268
  userTopic: () => userTopic,
260
269
  wasRefused: () => wasRefused,
270
+ webMediaPlatform: () => webMediaPlatform,
261
271
  withItem: () => withItem,
262
272
  wrapUpStatus: () => wrapUpStatus
263
273
  });
@@ -2864,7 +2874,7 @@ function createMeetRepository(options) {
2864
2874
  guard("meet.meetingBySlug", () => db.rpc(RPCS.meetingBySlug, { p_slug: slug })),
2865
2875
  readLinkRun(slug)
2866
2876
  ]);
2867
- const record = withRunNoteTaker(projectMeeting(row));
2877
+ const record = await withRunNoteTaker(projectMeeting(row));
2868
2878
  return linkRun === void 0 ? record : { ...record, linkRun };
2869
2879
  },
2870
2880
  async meeting(id) {
@@ -3612,13 +3622,19 @@ function recordingLabel(state) {
3612
3622
  }
3613
3623
 
3614
3624
  // src/core/attendance.ts
3625
+ var IDLE_MEDIA_REGION = {
3626
+ permission: { microphone: "unknown", camera: "unknown" },
3627
+ audioOutput: "ok",
3628
+ notice: null,
3629
+ offer: null
3630
+ };
3615
3631
  var IDLE_TRACK = { enabled: false, unavailable: null };
3616
3632
  var IDLE_MEDIA = {
3617
3633
  microphone: IDLE_TRACK,
3618
3634
  camera: IDLE_TRACK
3619
3635
  };
3620
- function unavailableFrom(error) {
3621
- return { code: error.code, message: error.message, remedy: error.remedy };
3636
+ function unavailableFrom(error, reason) {
3637
+ return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
3622
3638
  }
3623
3639
  var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
3624
3640
  function isListenOnly(media) {
@@ -3680,6 +3696,7 @@ var EMPTY = {
3680
3696
  wrapUp: null,
3681
3697
  noteTakerRefusal: null,
3682
3698
  media: IDLE_MEDIA,
3699
+ mediaRegion: IDLE_MEDIA_REGION,
3683
3700
  failure: null,
3684
3701
  collab: EMPTY_COLLAB,
3685
3702
  breakoutRoomId: null
@@ -3897,7 +3914,7 @@ function createMeetStore(initial = {}) {
3897
3914
  },
3898
3915
  setLocalTrack(kind, next) {
3899
3916
  const current = state.media[kind];
3900
- if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
3917
+ if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
3901
3918
  return;
3902
3919
  }
3903
3920
  commit({ ...state, media: { ...state.media, [kind]: next } });
@@ -4716,6 +4733,12 @@ function createLiveKitDriver(handlers, options = {}) {
4716
4733
  roster();
4717
4734
  }).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) => {
4718
4735
  handlers.roomMetadata?.(metadata ?? "");
4736
+ }).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
4737
+ handlers.audioPlayback?.(canPlay);
4738
+ }).on(RoomEvent.MediaDevicesChanged, () => {
4739
+ handlers.devicesChanged?.();
4740
+ }).on(RoomEvent.MediaDevicesError, (error, kind) => {
4741
+ handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
4719
4742
  }).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
4720
4743
  handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
4721
4744
  }).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
@@ -4812,6 +4835,7 @@ function createLiveKitDriver(handlers, options = {}) {
4812
4835
  }
4813
4836
  projectRoster?.();
4814
4837
  handlers.roomMetadata?.(target.metadata ?? "");
4838
+ handlers.audioPlayback?.(target.canPlaybackAudio);
4815
4839
  },
4816
4840
  async disconnect() {
4817
4841
  if (room === null) return;
@@ -4938,6 +4962,11 @@ function createLiveKitDriver(handlers, options = {}) {
4938
4962
  } catch (error) {
4939
4963
  throw normalizeDeviceError(error, "meet.switchDevice");
4940
4964
  }
4965
+ },
4966
+ async startAudio() {
4967
+ const target = room;
4968
+ if (target === null) return;
4969
+ await target.startAudio();
4941
4970
  }
4942
4971
  };
4943
4972
  }
@@ -6033,6 +6062,342 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
6033
6062
  return await response.json();
6034
6063
  }
6035
6064
 
6065
+ // src/core/media-supervisor.ts
6066
+ function classifyDeviceFailure(error, permission) {
6067
+ if (permission === "denied") return "denied";
6068
+ switch (error.code) {
6069
+ case "permission-denied":
6070
+ if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
6071
+ return "denied";
6072
+ case "device-not-found":
6073
+ return "none";
6074
+ case "device-in-use":
6075
+ return "busy";
6076
+ case "insecure-context":
6077
+ return "blocked_site";
6078
+ default:
6079
+ return "failed";
6080
+ }
6081
+ }
6082
+ function deviceNoticeCode(kind, reason) {
6083
+ if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
6084
+ return kind === "microphone" ? "mic-blocked" : "camera-blocked";
6085
+ }
6086
+ return "device-lost";
6087
+ }
6088
+ function deviceUnavailableReason(track, permission) {
6089
+ if (track.enabled) return null;
6090
+ if (permission === "denied") return "denied";
6091
+ const refused = track.unavailable;
6092
+ if (refused === null) return null;
6093
+ return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
6094
+ }
6095
+ function deviceChipLabel(kind, reason) {
6096
+ const what = kind === "microphone" ? "Mic" : "Camera";
6097
+ switch (reason) {
6098
+ case "denied":
6099
+ case "blocked_site":
6100
+ case "blocked_os":
6101
+ return `${what} blocked`;
6102
+ case "none":
6103
+ return kind === "microphone" ? "No microphone found" : "No camera found";
6104
+ case "busy":
6105
+ return `${what} in use by another app`;
6106
+ case "revoked":
6107
+ return `${what} access was turned off`;
6108
+ default:
6109
+ return `${what} didn't start`;
6110
+ }
6111
+ }
6112
+ function deviceFixSteps(kind, reason, where) {
6113
+ const device = kind === "microphone" ? "Microphone" : "Camera";
6114
+ const lower = device.toLowerCase();
6115
+ switch (reason) {
6116
+ case "denied":
6117
+ case "revoked":
6118
+ case "blocked_site":
6119
+ switch (where.browser) {
6120
+ case "firefox":
6121
+ return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
6122
+ case "safari":
6123
+ 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"];
6124
+ default:
6125
+ return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
6126
+ }
6127
+ case "blocked_os": {
6128
+ const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
6129
+ if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
6130
+ if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
6131
+ return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
6132
+ }
6133
+ case "busy":
6134
+ return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
6135
+ case "none":
6136
+ return [`Connect a ${lower}`, "Press Try again"];
6137
+ default:
6138
+ return [`Pick another ${lower} in Settings`, "Press Try again"];
6139
+ }
6140
+ }
6141
+ function detectBrowser(userAgent) {
6142
+ if (/Edg\//.test(userAgent)) return "edge";
6143
+ if (/Firefox\//.test(userAgent)) return "firefox";
6144
+ if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
6145
+ if (/Safari\//.test(userAgent)) return "safari";
6146
+ return "other";
6147
+ }
6148
+ function detectOs(userAgent, maxTouchPoints = 0) {
6149
+ if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
6150
+ if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
6151
+ if (/Android/.test(userAgent)) return "android";
6152
+ if (/Windows/.test(userAgent)) return "windows";
6153
+ if (/Linux|CrOS/.test(userAgent)) return "linux";
6154
+ return "other";
6155
+ }
6156
+ function webMediaPlatform() {
6157
+ const scope = globalThis;
6158
+ const nav = scope.navigator;
6159
+ if (nav === void 0) return null;
6160
+ const ua = nav.userAgent ?? "";
6161
+ const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
6162
+ const query = async (kind) => {
6163
+ try {
6164
+ return await nav.permissions?.query({ name: kind }) ?? null;
6165
+ } catch {
6166
+ return null;
6167
+ }
6168
+ };
6169
+ return {
6170
+ browser: detectBrowser(ua),
6171
+ os: detectOs(ua, nav.maxTouchPoints ?? 0),
6172
+ async queryPermission(kind) {
6173
+ return reading((await query(kind))?.state);
6174
+ },
6175
+ watchPermission(kind, onChange) {
6176
+ let status = null;
6177
+ let stopped = false;
6178
+ void query(kind).then((found) => {
6179
+ if (stopped || found === null) return;
6180
+ status = found;
6181
+ found.onchange = () => onChange(reading(found.state));
6182
+ });
6183
+ return () => {
6184
+ stopped = true;
6185
+ if (status !== null) status.onchange = null;
6186
+ };
6187
+ },
6188
+ async listDevices() {
6189
+ try {
6190
+ return await nav.mediaDevices?.enumerateDevices() ?? [];
6191
+ } catch {
6192
+ return [];
6193
+ }
6194
+ },
6195
+ onVisible(callback) {
6196
+ const doc = scope.document;
6197
+ if (doc === void 0) return () => void 0;
6198
+ const handler = () => {
6199
+ if (doc.visibilityState === "visible") callback();
6200
+ };
6201
+ doc.addEventListener("visibilitychange", handler);
6202
+ return () => doc.removeEventListener("visibilitychange", handler);
6203
+ }
6204
+ };
6205
+ }
6206
+ var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
6207
+ function isAlias(deviceId) {
6208
+ return deviceId === "default" || deviceId === "communications" || deviceId === "";
6209
+ }
6210
+ function createMediaSupervisor(options) {
6211
+ const { store, platform } = options;
6212
+ let unsubscribers = [];
6213
+ let started = false;
6214
+ let noticeId = 0;
6215
+ let known = null;
6216
+ let inUse = { audioinput: null, videoinput: null };
6217
+ function region() {
6218
+ return store.snapshot().mediaRegion;
6219
+ }
6220
+ function setRegion(next) {
6221
+ const current = region();
6222
+ const merged = { ...current, ...next };
6223
+ 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) {
6224
+ return;
6225
+ }
6226
+ store.patch({ mediaRegion: merged });
6227
+ }
6228
+ function setPermission(kind, reading) {
6229
+ const current = region().permission;
6230
+ if (current[kind] === reading) return;
6231
+ setRegion({ permission: { ...current, [kind]: reading } });
6232
+ const track = store.snapshot().media[kind];
6233
+ if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
6234
+ store.setLocalTrack(kind, {
6235
+ enabled: false,
6236
+ unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
6237
+ });
6238
+ }
6239
+ }
6240
+ async function snapshotDevices() {
6241
+ const list = platform === null ? [] : await platform.listDevices();
6242
+ const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
6243
+ for (const device of list) {
6244
+ if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
6245
+ }
6246
+ return out;
6247
+ }
6248
+ function readInUse() {
6249
+ const driver = options.driver();
6250
+ if (driver === null) return;
6251
+ const active = driver.activeDevices();
6252
+ inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
6253
+ }
6254
+ async function handleDevicesChanged() {
6255
+ if (platform === null) return;
6256
+ const before = known;
6257
+ const now = await snapshotDevices();
6258
+ known = now;
6259
+ const driver = options.driver();
6260
+ if (driver === null || before === null) {
6261
+ readInUse();
6262
+ return;
6263
+ }
6264
+ for (const kind of ["microphone", "camera"]) {
6265
+ const deviceKind = INPUT_KIND[kind];
6266
+ const previous = inUse[deviceKind];
6267
+ const track = store.snapshot().media[kind];
6268
+ const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
6269
+ if (gone) {
6270
+ const from = before[deviceKind].get(previous) ?? "";
6271
+ const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
6272
+ if (remaining.length === 0) {
6273
+ store.setLocalTrack(kind, {
6274
+ enabled: false,
6275
+ unavailable: {
6276
+ code: "device-not-found",
6277
+ message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
6278
+ remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
6279
+ reason: "none"
6280
+ }
6281
+ });
6282
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
6283
+ continue;
6284
+ }
6285
+ if (kind === "camera") {
6286
+ const [nextId, nextLabel] = remaining[0];
6287
+ try {
6288
+ await driver.switchDevice("videoinput", nextId);
6289
+ if (track.enabled || track.unavailable !== null) {
6290
+ await driver.setCameraEnabled(true);
6291
+ store.setLocalTrack("camera", { enabled: true, unavailable: null });
6292
+ }
6293
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
6294
+ } catch (error) {
6295
+ const refused = await unavailable(error, "camera", "meet.camera(fallback)");
6296
+ store.setLocalTrack("camera", { enabled: false, unavailable: refused });
6297
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
6298
+ }
6299
+ } else {
6300
+ const active = driver.activeDevices().audioinput;
6301
+ const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
6302
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
6303
+ }
6304
+ continue;
6305
+ }
6306
+ const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
6307
+ if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
6308
+ setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
6309
+ }
6310
+ }
6311
+ readInUse();
6312
+ }
6313
+ async function unavailable(error, kind, operation) {
6314
+ const normalized = normalizeDeviceError(error, operation);
6315
+ const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
6316
+ if (permission !== "unknown") setPermission(kind, permission);
6317
+ return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
6318
+ }
6319
+ return {
6320
+ start() {
6321
+ if (started || platform === null) return;
6322
+ started = true;
6323
+ for (const kind of ["microphone", "camera"]) {
6324
+ void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
6325
+ unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
6326
+ }
6327
+ unsubscribers.push(
6328
+ platform.onVisible(() => {
6329
+ const driver = options.driver();
6330
+ if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
6331
+ driver.startAudio().catch(() => void 0);
6332
+ })
6333
+ );
6334
+ },
6335
+ stop() {
6336
+ for (const off of unsubscribers) off();
6337
+ unsubscribers = [];
6338
+ started = false;
6339
+ },
6340
+ unavailable,
6341
+ onAudioPlayback(canPlay) {
6342
+ setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
6343
+ },
6344
+ onDevicesChanged() {
6345
+ void handleDevicesChanged().catch((error) => {
6346
+ options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
6347
+ });
6348
+ },
6349
+ onDeviceError(error, deviceKind) {
6350
+ if (deviceKind === null) return;
6351
+ const kind = deviceKind === "audioinput" ? "microphone" : "camera";
6352
+ void unavailable(error, kind, `meet.${kind}`).then((refused) => {
6353
+ const track = store.snapshot().media[kind];
6354
+ if (track.enabled) return;
6355
+ store.setLocalTrack(kind, { enabled: false, unavailable: refused });
6356
+ });
6357
+ },
6358
+ onConnected() {
6359
+ void snapshotDevices().then((now) => {
6360
+ known = now;
6361
+ readInUse();
6362
+ });
6363
+ },
6364
+ onDisconnected() {
6365
+ known = null;
6366
+ inUse = { audioinput: null, videoinput: null };
6367
+ setRegion({ audioOutput: "ok", notice: null, offer: null });
6368
+ },
6369
+ async enableAudio() {
6370
+ const driver = options.driver();
6371
+ if (driver?.startAudio === void 0) return;
6372
+ try {
6373
+ await driver.startAudio();
6374
+ setRegion({ audioOutput: "ok" });
6375
+ } catch (error) {
6376
+ options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
6377
+ }
6378
+ },
6379
+ async acceptOffer() {
6380
+ const offer = region().offer;
6381
+ const driver = options.driver();
6382
+ setRegion({ offer: null });
6383
+ if (offer === null || driver === null) return;
6384
+ try {
6385
+ await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
6386
+ readInUse();
6387
+ } catch (error) {
6388
+ const refused = await unavailable(error, offer.kind, "meet.switchDevice");
6389
+ options.report("warn", refused.message, refused.remedy);
6390
+ }
6391
+ },
6392
+ dismissOffer() {
6393
+ setRegion({ offer: null });
6394
+ },
6395
+ dismissNotice() {
6396
+ setRegion({ notice: null });
6397
+ }
6398
+ };
6399
+ }
6400
+
6036
6401
  // src/core/room.ts
6037
6402
  function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
6038
6403
  return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
@@ -6112,9 +6477,9 @@ function createRoomEngine(options) {
6112
6477
  const departures = /* @__PURE__ */ new Map();
6113
6478
  function requestAsNow() {
6114
6479
  if (lastRequest === null) return null;
6115
- const media = store.snapshot().media;
6480
+ const media2 = store.snapshot().media;
6116
6481
  if (store.snapshot().phase !== "in_call") return lastRequest;
6117
- return { ...lastRequest, mode: media.camera.enabled ? "video" : "audio", microphone: media.microphone.enabled };
6482
+ return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
6118
6483
  }
6119
6484
  async function dropRoom() {
6120
6485
  const target = driver;
@@ -6125,6 +6490,7 @@ function createRoomEngine(options) {
6125
6490
  }
6126
6491
  store.setLocalTrack("microphone", IDLE_TRACK);
6127
6492
  store.setLocalTrack("camera", IDLE_TRACK);
6493
+ media.onDisconnected();
6128
6494
  store.patch({ share: NO_SHARE, connection: "stable" });
6129
6495
  clearGhosts();
6130
6496
  await target?.disconnect();
@@ -6190,7 +6556,22 @@ function createRoomEngine(options) {
6190
6556
  report("error", normalized.message, normalized.remedy);
6191
6557
  return normalized;
6192
6558
  }
6559
+ const media = createMediaSupervisor({
6560
+ store,
6561
+ platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
6562
+ driver: () => driver,
6563
+ report
6564
+ });
6193
6565
  const handlers = {
6566
+ audioPlayback(canPlay) {
6567
+ media.onAudioPlayback(canPlay);
6568
+ },
6569
+ devicesChanged() {
6570
+ media.onDevicesChanged();
6571
+ },
6572
+ mediaDeviceError(error, kind) {
6573
+ media.onDeviceError(error, kind);
6574
+ },
6194
6575
  participants(list) {
6195
6576
  const joinedAt = new Date(now()).toISOString();
6196
6577
  const before = new Set(store.snapshot().participants.map((p) => p.identity));
@@ -6456,6 +6837,7 @@ function createRoomEngine(options) {
6456
6837
  store.dispatch({ type: "connected" });
6457
6838
  store.setLocalTrack("microphone", IDLE_TRACK);
6458
6839
  store.setLocalTrack("camera", IDLE_TRACK);
6840
+ media.start();
6459
6841
  const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
6460
6842
  store.patch({ effects });
6461
6843
  if (!effects.noiseSuppression || effects.background !== "none") {
@@ -6480,6 +6862,7 @@ function createRoomEngine(options) {
6480
6862
  if (request.microphone !== false) {
6481
6863
  await enableTrack(created, "microphone", true);
6482
6864
  }
6865
+ media.onConnected();
6483
6866
  }
6484
6867
  async function enableTrack(target, kind, enabled) {
6485
6868
  try {
@@ -6489,10 +6872,10 @@ function createRoomEngine(options) {
6489
6872
  store.setLocalTrack(kind, state);
6490
6873
  return state;
6491
6874
  } catch (error) {
6492
- const normalized = normalizeDeviceError(error, `meet.${kind}`);
6493
- const state = { enabled: false, unavailable: unavailableFrom(normalized) };
6875
+ const refused = await media.unavailable(error, kind, `meet.${kind}`);
6876
+ const state = { enabled: false, unavailable: refused };
6494
6877
  store.setLocalTrack(kind, state);
6495
- report("warn", normalized.message, normalized.remedy);
6878
+ report("warn", refused.message, refused.remedy);
6496
6879
  return state;
6497
6880
  }
6498
6881
  }
@@ -6801,6 +7184,10 @@ function createRoomEngine(options) {
6801
7184
  if (driver === null) return;
6802
7185
  await driver.switchDevice(kind, deviceId);
6803
7186
  },
7187
+ enableAudio: () => media.enableAudio(),
7188
+ acceptDeviceOffer: () => media.acceptOffer(),
7189
+ dismissDeviceOffer: () => media.dismissOffer(),
7190
+ dismissDeviceNotice: () => media.dismissNotice(),
6804
7191
  pin(who) {
6805
7192
  store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
6806
7193
  },
@@ -6815,6 +7202,7 @@ function createRoomEngine(options) {
6815
7202
  async dispose() {
6816
7203
  disposed = true;
6817
7204
  supervisor.dispose();
7205
+ media.stop();
6818
7206
  clearGhosts();
6819
7207
  if (driver !== null) {
6820
7208
  const target = driver;
@@ -7799,22 +8187,10 @@ function observedRole(role, standing) {
7799
8187
  if (role === "host" || role === "cohost") return role;
7800
8188
  return isLinkFollower(standing) ? "guest" : "participant";
7801
8189
  }
7802
- function observedDevice(track) {
8190
+ function observedDevice(track, permission = "unknown") {
7803
8191
  if (track.enabled) return "on";
7804
- const refused = track.unavailable;
7805
- if (refused === null) return "muted_by_me";
7806
- switch (refused.code) {
7807
- case "permission-denied":
7808
- return "unavailable:denied";
7809
- case "device-not-found":
7810
- return "unavailable:none";
7811
- case "device-in-use":
7812
- return "unavailable:busy";
7813
- case "insecure-context":
7814
- return "unavailable:blocked_site";
7815
- default:
7816
- return "unavailable:failed";
7817
- }
8192
+ const reason = deviceUnavailableReason(track, permission);
8193
+ return reason === null ? "muted_by_me" : `unavailable:${reason}`;
7818
8194
  }
7819
8195
  function endedRecord(snapshot, given) {
7820
8196
  const record = snapshot?.meeting ?? given;
@@ -7847,9 +8223,10 @@ function observe(input) {
7847
8223
  hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
7848
8224
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
7849
8225
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
7850
- audioBlocked: null,
7851
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,
7852
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null
8226
+ // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
8227
+ audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
8228
+ camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
8229
+ microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
7853
8230
  };
7854
8231
  }
7855
8232
  function staticObservation(phase, reason = null) {
@@ -7914,9 +8291,9 @@ function participantAttributes(p) {
7914
8291
  if (quality !== null) attrs["data-meet-participant-connection"] = quality;
7915
8292
  return attrs;
7916
8293
  }
7917
- function microphoneNoticeCode(track) {
7918
- const refused = track.unavailable;
7919
- if (refused === null) return null;
7920
- return refused.code === "permission-denied" || refused.code === "insecure-context" ? "mic-blocked" : "device-lost";
8294
+ function microphoneNoticeCode(track, permission = "unknown") {
8295
+ const reason = deviceUnavailableReason(track, permission);
8296
+ if (reason === null) return null;
8297
+ return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
7921
8298
  }
7922
8299
  //# sourceMappingURL=core.cjs.map