@ai-matrx/meet 0.11.29 → 0.11.30

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
@@ -3347,13 +3347,19 @@ function recordingLabel(state) {
3347
3347
  }
3348
3348
 
3349
3349
  // src/core/attendance.ts
3350
+ var IDLE_MEDIA_REGION = {
3351
+ permission: { microphone: "unknown", camera: "unknown" },
3352
+ audioOutput: "ok",
3353
+ notice: null,
3354
+ offer: null
3355
+ };
3350
3356
  var IDLE_TRACK = { enabled: false, unavailable: null };
3351
3357
  var IDLE_MEDIA = {
3352
3358
  microphone: IDLE_TRACK,
3353
3359
  camera: IDLE_TRACK
3354
3360
  };
3355
- function unavailableFrom(error) {
3356
- return { code: error.code, message: error.message, remedy: error.remedy };
3361
+ function unavailableFrom(error, reason) {
3362
+ return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
3357
3363
  }
3358
3364
  var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
3359
3365
  function isListenOnly(media) {
@@ -3415,6 +3421,7 @@ var EMPTY = {
3415
3421
  wrapUp: null,
3416
3422
  noteTakerRefusal: null,
3417
3423
  media: IDLE_MEDIA,
3424
+ mediaRegion: IDLE_MEDIA_REGION,
3418
3425
  failure: null,
3419
3426
  collab: EMPTY_COLLAB,
3420
3427
  breakoutRoomId: null
@@ -3632,7 +3639,7 @@ function createMeetStore(initial = {}) {
3632
3639
  },
3633
3640
  setLocalTrack(kind, next) {
3634
3641
  const current = state.media[kind];
3635
- if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
3642
+ if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
3636
3643
  return;
3637
3644
  }
3638
3645
  commit({ ...state, media: { ...state.media, [kind]: next } });
@@ -4451,6 +4458,12 @@ function createLiveKitDriver(handlers, options = {}) {
4451
4458
  roster();
4452
4459
  }).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) => {
4453
4460
  handlers.roomMetadata?.(metadata ?? "");
4461
+ }).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
4462
+ handlers.audioPlayback?.(canPlay);
4463
+ }).on(RoomEvent.MediaDevicesChanged, () => {
4464
+ handlers.devicesChanged?.();
4465
+ }).on(RoomEvent.MediaDevicesError, (error, kind) => {
4466
+ handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
4454
4467
  }).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
4455
4468
  handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
4456
4469
  }).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
@@ -4547,6 +4560,7 @@ function createLiveKitDriver(handlers, options = {}) {
4547
4560
  }
4548
4561
  projectRoster?.();
4549
4562
  handlers.roomMetadata?.(target.metadata ?? "");
4563
+ handlers.audioPlayback?.(target.canPlaybackAudio);
4550
4564
  },
4551
4565
  async disconnect() {
4552
4566
  if (room === null) return;
@@ -4673,6 +4687,11 @@ function createLiveKitDriver(handlers, options = {}) {
4673
4687
  } catch (error) {
4674
4688
  throw normalizeDeviceError(error, "meet.switchDevice");
4675
4689
  }
4690
+ },
4691
+ async startAudio() {
4692
+ const target = room;
4693
+ if (target === null) return;
4694
+ await target.startAudio();
4676
4695
  }
4677
4696
  };
4678
4697
  }
@@ -5768,6 +5787,342 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
5768
5787
  return await response.json();
5769
5788
  }
5770
5789
 
5790
+ // src/core/media-supervisor.ts
5791
+ function classifyDeviceFailure(error, permission) {
5792
+ if (permission === "denied") return "denied";
5793
+ switch (error.code) {
5794
+ case "permission-denied":
5795
+ if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
5796
+ return "denied";
5797
+ case "device-not-found":
5798
+ return "none";
5799
+ case "device-in-use":
5800
+ return "busy";
5801
+ case "insecure-context":
5802
+ return "blocked_site";
5803
+ default:
5804
+ return "failed";
5805
+ }
5806
+ }
5807
+ function deviceNoticeCode(kind, reason) {
5808
+ if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
5809
+ return kind === "microphone" ? "mic-blocked" : "camera-blocked";
5810
+ }
5811
+ return "device-lost";
5812
+ }
5813
+ function deviceUnavailableReason(track, permission) {
5814
+ if (track.enabled) return null;
5815
+ if (permission === "denied") return "denied";
5816
+ const refused = track.unavailable;
5817
+ if (refused === null) return null;
5818
+ return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
5819
+ }
5820
+ function deviceChipLabel(kind, reason) {
5821
+ const what = kind === "microphone" ? "Mic" : "Camera";
5822
+ switch (reason) {
5823
+ case "denied":
5824
+ case "blocked_site":
5825
+ case "blocked_os":
5826
+ return `${what} blocked`;
5827
+ case "none":
5828
+ return kind === "microphone" ? "No microphone found" : "No camera found";
5829
+ case "busy":
5830
+ return `${what} in use by another app`;
5831
+ case "revoked":
5832
+ return `${what} access was turned off`;
5833
+ default:
5834
+ return `${what} didn't start`;
5835
+ }
5836
+ }
5837
+ function deviceFixSteps(kind, reason, where) {
5838
+ const device = kind === "microphone" ? "Microphone" : "Camera";
5839
+ const lower = device.toLowerCase();
5840
+ switch (reason) {
5841
+ case "denied":
5842
+ case "revoked":
5843
+ case "blocked_site":
5844
+ switch (where.browser) {
5845
+ case "firefox":
5846
+ return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
5847
+ case "safari":
5848
+ 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"];
5849
+ default:
5850
+ return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
5851
+ }
5852
+ case "blocked_os": {
5853
+ const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
5854
+ if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
5855
+ if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
5856
+ return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
5857
+ }
5858
+ case "busy":
5859
+ return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
5860
+ case "none":
5861
+ return [`Connect a ${lower}`, "Press Try again"];
5862
+ default:
5863
+ return [`Pick another ${lower} in Settings`, "Press Try again"];
5864
+ }
5865
+ }
5866
+ function detectBrowser(userAgent) {
5867
+ if (/Edg\//.test(userAgent)) return "edge";
5868
+ if (/Firefox\//.test(userAgent)) return "firefox";
5869
+ if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
5870
+ if (/Safari\//.test(userAgent)) return "safari";
5871
+ return "other";
5872
+ }
5873
+ function detectOs(userAgent, maxTouchPoints = 0) {
5874
+ if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
5875
+ if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
5876
+ if (/Android/.test(userAgent)) return "android";
5877
+ if (/Windows/.test(userAgent)) return "windows";
5878
+ if (/Linux|CrOS/.test(userAgent)) return "linux";
5879
+ return "other";
5880
+ }
5881
+ function webMediaPlatform() {
5882
+ const scope = globalThis;
5883
+ const nav = scope.navigator;
5884
+ if (nav === void 0) return null;
5885
+ const ua = nav.userAgent ?? "";
5886
+ const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
5887
+ const query = async (kind) => {
5888
+ try {
5889
+ return await nav.permissions?.query({ name: kind }) ?? null;
5890
+ } catch {
5891
+ return null;
5892
+ }
5893
+ };
5894
+ return {
5895
+ browser: detectBrowser(ua),
5896
+ os: detectOs(ua, nav.maxTouchPoints ?? 0),
5897
+ async queryPermission(kind) {
5898
+ return reading((await query(kind))?.state);
5899
+ },
5900
+ watchPermission(kind, onChange) {
5901
+ let status = null;
5902
+ let stopped = false;
5903
+ void query(kind).then((found) => {
5904
+ if (stopped || found === null) return;
5905
+ status = found;
5906
+ found.onchange = () => onChange(reading(found.state));
5907
+ });
5908
+ return () => {
5909
+ stopped = true;
5910
+ if (status !== null) status.onchange = null;
5911
+ };
5912
+ },
5913
+ async listDevices() {
5914
+ try {
5915
+ return await nav.mediaDevices?.enumerateDevices() ?? [];
5916
+ } catch {
5917
+ return [];
5918
+ }
5919
+ },
5920
+ onVisible(callback) {
5921
+ const doc = scope.document;
5922
+ if (doc === void 0) return () => void 0;
5923
+ const handler = () => {
5924
+ if (doc.visibilityState === "visible") callback();
5925
+ };
5926
+ doc.addEventListener("visibilitychange", handler);
5927
+ return () => doc.removeEventListener("visibilitychange", handler);
5928
+ }
5929
+ };
5930
+ }
5931
+ var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
5932
+ function isAlias(deviceId) {
5933
+ return deviceId === "default" || deviceId === "communications" || deviceId === "";
5934
+ }
5935
+ function createMediaSupervisor(options) {
5936
+ const { store, platform } = options;
5937
+ let unsubscribers = [];
5938
+ let started = false;
5939
+ let noticeId = 0;
5940
+ let known = null;
5941
+ let inUse = { audioinput: null, videoinput: null };
5942
+ function region() {
5943
+ return store.snapshot().mediaRegion;
5944
+ }
5945
+ function setRegion(next) {
5946
+ const current = region();
5947
+ const merged = { ...current, ...next };
5948
+ 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) {
5949
+ return;
5950
+ }
5951
+ store.patch({ mediaRegion: merged });
5952
+ }
5953
+ function setPermission(kind, reading) {
5954
+ const current = region().permission;
5955
+ if (current[kind] === reading) return;
5956
+ setRegion({ permission: { ...current, [kind]: reading } });
5957
+ const track = store.snapshot().media[kind];
5958
+ if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
5959
+ store.setLocalTrack(kind, {
5960
+ enabled: false,
5961
+ unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
5962
+ });
5963
+ }
5964
+ }
5965
+ async function snapshotDevices() {
5966
+ const list = platform === null ? [] : await platform.listDevices();
5967
+ const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
5968
+ for (const device of list) {
5969
+ if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
5970
+ }
5971
+ return out;
5972
+ }
5973
+ function readInUse() {
5974
+ const driver = options.driver();
5975
+ if (driver === null) return;
5976
+ const active = driver.activeDevices();
5977
+ inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
5978
+ }
5979
+ async function handleDevicesChanged() {
5980
+ if (platform === null) return;
5981
+ const before = known;
5982
+ const now = await snapshotDevices();
5983
+ known = now;
5984
+ const driver = options.driver();
5985
+ if (driver === null || before === null) {
5986
+ readInUse();
5987
+ return;
5988
+ }
5989
+ for (const kind of ["microphone", "camera"]) {
5990
+ const deviceKind = INPUT_KIND[kind];
5991
+ const previous = inUse[deviceKind];
5992
+ const track = store.snapshot().media[kind];
5993
+ const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
5994
+ if (gone) {
5995
+ const from = before[deviceKind].get(previous) ?? "";
5996
+ const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
5997
+ if (remaining.length === 0) {
5998
+ store.setLocalTrack(kind, {
5999
+ enabled: false,
6000
+ unavailable: {
6001
+ code: "device-not-found",
6002
+ message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
6003
+ remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
6004
+ reason: "none"
6005
+ }
6006
+ });
6007
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
6008
+ continue;
6009
+ }
6010
+ if (kind === "camera") {
6011
+ const [nextId, nextLabel] = remaining[0];
6012
+ try {
6013
+ await driver.switchDevice("videoinput", nextId);
6014
+ if (track.enabled || track.unavailable !== null) {
6015
+ await driver.setCameraEnabled(true);
6016
+ store.setLocalTrack("camera", { enabled: true, unavailable: null });
6017
+ }
6018
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
6019
+ } catch (error) {
6020
+ const refused = await unavailable(error, "camera", "meet.camera(fallback)");
6021
+ store.setLocalTrack("camera", { enabled: false, unavailable: refused });
6022
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
6023
+ }
6024
+ } else {
6025
+ const active = driver.activeDevices().audioinput;
6026
+ const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
6027
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
6028
+ }
6029
+ continue;
6030
+ }
6031
+ const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
6032
+ if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
6033
+ setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
6034
+ }
6035
+ }
6036
+ readInUse();
6037
+ }
6038
+ async function unavailable(error, kind, operation) {
6039
+ const normalized = normalizeDeviceError(error, operation);
6040
+ const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
6041
+ if (permission !== "unknown") setPermission(kind, permission);
6042
+ return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
6043
+ }
6044
+ return {
6045
+ start() {
6046
+ if (started || platform === null) return;
6047
+ started = true;
6048
+ for (const kind of ["microphone", "camera"]) {
6049
+ void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
6050
+ unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
6051
+ }
6052
+ unsubscribers.push(
6053
+ platform.onVisible(() => {
6054
+ const driver = options.driver();
6055
+ if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
6056
+ driver.startAudio().catch(() => void 0);
6057
+ })
6058
+ );
6059
+ },
6060
+ stop() {
6061
+ for (const off of unsubscribers) off();
6062
+ unsubscribers = [];
6063
+ started = false;
6064
+ },
6065
+ unavailable,
6066
+ onAudioPlayback(canPlay) {
6067
+ setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
6068
+ },
6069
+ onDevicesChanged() {
6070
+ void handleDevicesChanged().catch((error) => {
6071
+ options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
6072
+ });
6073
+ },
6074
+ onDeviceError(error, deviceKind) {
6075
+ if (deviceKind === null) return;
6076
+ const kind = deviceKind === "audioinput" ? "microphone" : "camera";
6077
+ void unavailable(error, kind, `meet.${kind}`).then((refused) => {
6078
+ const track = store.snapshot().media[kind];
6079
+ if (track.enabled) return;
6080
+ store.setLocalTrack(kind, { enabled: false, unavailable: refused });
6081
+ });
6082
+ },
6083
+ onConnected() {
6084
+ void snapshotDevices().then((now) => {
6085
+ known = now;
6086
+ readInUse();
6087
+ });
6088
+ },
6089
+ onDisconnected() {
6090
+ known = null;
6091
+ inUse = { audioinput: null, videoinput: null };
6092
+ setRegion({ audioOutput: "ok", notice: null, offer: null });
6093
+ },
6094
+ async enableAudio() {
6095
+ const driver = options.driver();
6096
+ if (driver?.startAudio === void 0) return;
6097
+ try {
6098
+ await driver.startAudio();
6099
+ setRegion({ audioOutput: "ok" });
6100
+ } catch (error) {
6101
+ options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
6102
+ }
6103
+ },
6104
+ async acceptOffer() {
6105
+ const offer = region().offer;
6106
+ const driver = options.driver();
6107
+ setRegion({ offer: null });
6108
+ if (offer === null || driver === null) return;
6109
+ try {
6110
+ await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
6111
+ readInUse();
6112
+ } catch (error) {
6113
+ const refused = await unavailable(error, offer.kind, "meet.switchDevice");
6114
+ options.report("warn", refused.message, refused.remedy);
6115
+ }
6116
+ },
6117
+ dismissOffer() {
6118
+ setRegion({ offer: null });
6119
+ },
6120
+ dismissNotice() {
6121
+ setRegion({ notice: null });
6122
+ }
6123
+ };
6124
+ }
6125
+
5771
6126
  // src/core/room.ts
5772
6127
  function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
5773
6128
  return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
@@ -5847,9 +6202,9 @@ function createRoomEngine(options) {
5847
6202
  const departures = /* @__PURE__ */ new Map();
5848
6203
  function requestAsNow() {
5849
6204
  if (lastRequest === null) return null;
5850
- const media = store.snapshot().media;
6205
+ const media2 = store.snapshot().media;
5851
6206
  if (store.snapshot().phase !== "in_call") return lastRequest;
5852
- return { ...lastRequest, mode: media.camera.enabled ? "video" : "audio", microphone: media.microphone.enabled };
6207
+ return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
5853
6208
  }
5854
6209
  async function dropRoom() {
5855
6210
  const target = driver;
@@ -5860,6 +6215,7 @@ function createRoomEngine(options) {
5860
6215
  }
5861
6216
  store.setLocalTrack("microphone", IDLE_TRACK);
5862
6217
  store.setLocalTrack("camera", IDLE_TRACK);
6218
+ media.onDisconnected();
5863
6219
  store.patch({ share: NO_SHARE, connection: "stable" });
5864
6220
  clearGhosts();
5865
6221
  await target?.disconnect();
@@ -5925,7 +6281,22 @@ function createRoomEngine(options) {
5925
6281
  report("error", normalized.message, normalized.remedy);
5926
6282
  return normalized;
5927
6283
  }
6284
+ const media = createMediaSupervisor({
6285
+ store,
6286
+ platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
6287
+ driver: () => driver,
6288
+ report
6289
+ });
5928
6290
  const handlers = {
6291
+ audioPlayback(canPlay) {
6292
+ media.onAudioPlayback(canPlay);
6293
+ },
6294
+ devicesChanged() {
6295
+ media.onDevicesChanged();
6296
+ },
6297
+ mediaDeviceError(error, kind) {
6298
+ media.onDeviceError(error, kind);
6299
+ },
5929
6300
  participants(list) {
5930
6301
  const joinedAt = new Date(now()).toISOString();
5931
6302
  const before = new Set(store.snapshot().participants.map((p) => p.identity));
@@ -6191,6 +6562,7 @@ function createRoomEngine(options) {
6191
6562
  store.dispatch({ type: "connected" });
6192
6563
  store.setLocalTrack("microphone", IDLE_TRACK);
6193
6564
  store.setLocalTrack("camera", IDLE_TRACK);
6565
+ media.start();
6194
6566
  const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
6195
6567
  store.patch({ effects });
6196
6568
  if (!effects.noiseSuppression || effects.background !== "none") {
@@ -6215,6 +6587,7 @@ function createRoomEngine(options) {
6215
6587
  if (request.microphone !== false) {
6216
6588
  await enableTrack(created, "microphone", true);
6217
6589
  }
6590
+ media.onConnected();
6218
6591
  }
6219
6592
  async function enableTrack(target, kind, enabled) {
6220
6593
  try {
@@ -6224,10 +6597,10 @@ function createRoomEngine(options) {
6224
6597
  store.setLocalTrack(kind, state);
6225
6598
  return state;
6226
6599
  } catch (error) {
6227
- const normalized = normalizeDeviceError(error, `meet.${kind}`);
6228
- const state = { enabled: false, unavailable: unavailableFrom(normalized) };
6600
+ const refused = await media.unavailable(error, kind, `meet.${kind}`);
6601
+ const state = { enabled: false, unavailable: refused };
6229
6602
  store.setLocalTrack(kind, state);
6230
- report("warn", normalized.message, normalized.remedy);
6603
+ report("warn", refused.message, refused.remedy);
6231
6604
  return state;
6232
6605
  }
6233
6606
  }
@@ -6536,6 +6909,10 @@ function createRoomEngine(options) {
6536
6909
  if (driver === null) return;
6537
6910
  await driver.switchDevice(kind, deviceId);
6538
6911
  },
6912
+ enableAudio: () => media.enableAudio(),
6913
+ acceptDeviceOffer: () => media.acceptOffer(),
6914
+ dismissDeviceOffer: () => media.dismissOffer(),
6915
+ dismissDeviceNotice: () => media.dismissNotice(),
6539
6916
  pin(who) {
6540
6917
  store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
6541
6918
  },
@@ -6550,6 +6927,7 @@ function createRoomEngine(options) {
6550
6927
  async dispose() {
6551
6928
  disposed = true;
6552
6929
  supervisor.dispose();
6930
+ media.stop();
6553
6931
  clearGhosts();
6554
6932
  if (driver !== null) {
6555
6933
  const target = driver;
@@ -7534,22 +7912,10 @@ function observedRole(role, standing) {
7534
7912
  if (role === "host" || role === "cohost") return role;
7535
7913
  return isLinkFollower(standing) ? "guest" : "participant";
7536
7914
  }
7537
- function observedDevice(track) {
7915
+ function observedDevice(track, permission = "unknown") {
7538
7916
  if (track.enabled) return "on";
7539
- const refused = track.unavailable;
7540
- if (refused === null) return "muted_by_me";
7541
- switch (refused.code) {
7542
- case "permission-denied":
7543
- return "unavailable:denied";
7544
- case "device-not-found":
7545
- return "unavailable:none";
7546
- case "device-in-use":
7547
- return "unavailable:busy";
7548
- case "insecure-context":
7549
- return "unavailable:blocked_site";
7550
- default:
7551
- return "unavailable:failed";
7552
- }
7917
+ const reason = deviceUnavailableReason(track, permission);
7918
+ return reason === null ? "muted_by_me" : `unavailable:${reason}`;
7553
7919
  }
7554
7920
  function endedRecord(snapshot, given) {
7555
7921
  const record = snapshot?.meeting ?? given;
@@ -7582,9 +7948,10 @@ function observe(input) {
7582
7948
  hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
7583
7949
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
7584
7950
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
7585
- audioBlocked: null,
7586
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,
7587
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null
7951
+ // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
7952
+ audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
7953
+ camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
7954
+ microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
7588
7955
  };
7589
7956
  }
7590
7957
  function staticObservation(phase, reason = null) {
@@ -7649,10 +8016,10 @@ function participantAttributes(p) {
7649
8016
  if (quality !== null) attrs["data-meet-participant-connection"] = quality;
7650
8017
  return attrs;
7651
8018
  }
7652
- function microphoneNoticeCode(track) {
7653
- const refused = track.unavailable;
7654
- if (refused === null) return null;
7655
- return refused.code === "permission-denied" || refused.code === "insecure-context" ? "mic-blocked" : "device-lost";
8019
+ function microphoneNoticeCode(track, permission = "unknown") {
8020
+ const reason = deviceUnavailableReason(track, permission);
8021
+ if (reason === null) return null;
8022
+ return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
7656
8023
  }
7657
8024
  export {
7658
8025
  ATTENDANCE_CSV_HEADER,
@@ -7672,6 +8039,7 @@ export {
7672
8039
  GUEST_RECORD_ACCESS_METADATA_KEY,
7673
8040
  GUEST_RECORD_NOT_SHARED,
7674
8041
  IDLE_MEDIA,
8042
+ IDLE_MEDIA_REGION,
7675
8043
  IDLE_PHASES,
7676
8044
  IDLE_TRACK,
7677
8045
  JOIN_REFUSAL_REASONS,
@@ -7742,6 +8110,7 @@ export {
7742
8110
  captionSegment,
7743
8111
  captureHeldOf,
7744
8112
  changesPatch,
8113
+ classifyDeviceFailure,
7745
8114
  clockPosition,
7746
8115
  collabFromState,
7747
8116
  computeStage,
@@ -7753,6 +8122,7 @@ export {
7753
8122
  createDeviceManager,
7754
8123
  createInRoomClient,
7755
8124
  createLiveKitDriver,
8125
+ createMediaSupervisor,
7756
8126
  createMeetAi,
7757
8127
  createMeetApi,
7758
8128
  createMeetRepository,
@@ -7772,6 +8142,12 @@ export {
7772
8142
  createWebResumeStorage,
7773
8143
  createWebRoomTokenStorage,
7774
8144
  defaultCaptionLanguage,
8145
+ detectBrowser,
8146
+ detectOs,
8147
+ deviceChipLabel,
8148
+ deviceFixSteps,
8149
+ deviceNoticeCode,
8150
+ deviceUnavailableReason,
7775
8151
  displayNameFor,
7776
8152
  fileDownloadPath,
7777
8153
  focusFromRow,
@@ -7883,6 +8259,7 @@ export {
7883
8259
  untranslatedLines,
7884
8260
  userTopic,
7885
8261
  wasRefused,
8262
+ webMediaPlatform,
7886
8263
  withItem,
7887
8264
  wrapUpStatus
7888
8265
  };