@ai-matrx/meet 0.11.28 → 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/react.js CHANGED
@@ -1,6 +1,352 @@
1
1
  "use client";
2
2
  "use client";
3
3
 
4
+ // src/core/connection-supervisor.ts
5
+ var DEFAULT_CONNECTION_POLICY = {
6
+ bannerDelayMs: 2e3,
7
+ autoRejoin: true,
8
+ autoRejoinMaxAttempts: 5,
9
+ reconnectGraceSeconds: 30
10
+ };
11
+ function clampInt(value, min, max, fallback) {
12
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
13
+ return Math.min(max, Math.max(min, Math.round(value)));
14
+ }
15
+ function connectionPolicyFrom(raw) {
16
+ if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
17
+ const row = raw;
18
+ const d = DEFAULT_CONNECTION_POLICY;
19
+ return {
20
+ bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
21
+ autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
22
+ autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
23
+ reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
24
+ };
25
+ }
26
+ var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
27
+ function createReconnectPolicy(options = {}) {
28
+ const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
29
+ const giveUpMs = options.giveUpMs ?? 45e3;
30
+ const maxAttempts = options.maxAttempts ?? 12;
31
+ const random = options.random ?? Math.random;
32
+ return {
33
+ nextRetryDelayInMs({ retryCount, elapsedMs }) {
34
+ if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
35
+ const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
36
+ const spread = Math.min(2e3, Math.max(500, base / 2));
37
+ return Math.round(base + random() * spread);
38
+ }
39
+ };
40
+ }
41
+ function createWebNetworkPort() {
42
+ const scope = globalThis;
43
+ return {
44
+ online: () => scope.navigator?.onLine !== false,
45
+ subscribe(listener) {
46
+ if (typeof scope.addEventListener !== "function") return () => void 0;
47
+ const on = () => listener(true);
48
+ const off = () => listener(false);
49
+ scope.addEventListener("online", on);
50
+ scope.addEventListener("offline", off);
51
+ return () => {
52
+ scope.removeEventListener?.("online", on);
53
+ scope.removeEventListener?.("offline", off);
54
+ };
55
+ }
56
+ };
57
+ }
58
+ function createReachabilityProbe(urls, timeoutMs = 4e3) {
59
+ return async () => {
60
+ const targets = urls().filter((url) => url.length > 0);
61
+ if (targets.length === 0 || typeof fetch !== "function") return true;
62
+ const one = async (url) => {
63
+ const controller = typeof AbortController === "function" ? new AbortController() : null;
64
+ const timer = setTimeout(() => controller?.abort(), timeoutMs);
65
+ try {
66
+ await fetch(url, {
67
+ method: "GET",
68
+ mode: "no-cors",
69
+ cache: "no-store",
70
+ credentials: "omit",
71
+ ...controller !== null ? { signal: controller.signal } : {}
72
+ });
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ } finally {
77
+ clearTimeout(timer);
78
+ }
79
+ };
80
+ const answers = await Promise.all(targets.map(one));
81
+ return answers.every(Boolean);
82
+ };
83
+ }
84
+ function httpUrlOf(serverUrl) {
85
+ return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
86
+ }
87
+ var RESUME_WINDOW_MS = 15 * 6e4;
88
+ var RESUME_KEY = "mx-meet:resume:";
89
+ function createWebResumeStorage() {
90
+ const storage = globalThis.sessionStorage;
91
+ if (storage === void 0) return void 0;
92
+ return {
93
+ load(roomName) {
94
+ try {
95
+ const raw = storage.getItem(RESUME_KEY + roomName);
96
+ if (raw === null) return null;
97
+ const parsed = JSON.parse(raw);
98
+ return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ },
103
+ save(roomName, marker) {
104
+ try {
105
+ storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
106
+ } catch {
107
+ }
108
+ },
109
+ clear(roomName) {
110
+ try {
111
+ storage.removeItem(RESUME_KEY + roomName);
112
+ } catch {
113
+ }
114
+ }
115
+ };
116
+ }
117
+ var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
118
+ function autoRejoinDelayMs(attempt, random = Math.random) {
119
+ const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
120
+ return Math.round(base * (0.5 + random()));
121
+ }
122
+ function createConnectionSupervisor(options) {
123
+ const { store, network } = options;
124
+ const now = options.now ?? (() => Date.now());
125
+ const random = options.random ?? Math.random;
126
+ const probeEveryMs = options.probeEveryMs ?? 3e3;
127
+ let driverRegion = null;
128
+ let probeTimer = null;
129
+ let probing = false;
130
+ let bannerTimer = null;
131
+ let rejoinTimer = null;
132
+ let rejoinAttempts = 0;
133
+ let lastPhase = store.snapshot().phase;
134
+ let lastSavedAt = 0;
135
+ let disposed = false;
136
+ const inCall = () => store.snapshot().phase === "in_call";
137
+ const dropped = () => store.snapshot().phase === "disconnected";
138
+ function setRegion(next) {
139
+ if (store.snapshot().connection !== next) store.patch({ connection: next });
140
+ }
141
+ function regionFor(reachable) {
142
+ if (!network.online()) return "offline";
143
+ if (reachable === false) return "server_unreachable";
144
+ if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
145
+ return driverRegion ?? "stable";
146
+ }
147
+ function needsWatch() {
148
+ const snap = store.snapshot();
149
+ if (dropped()) return true;
150
+ return inCall() && (driverRegion !== null || snap.connection !== "stable");
151
+ }
152
+ function stopProbe() {
153
+ if (probeTimer !== null) clearTimeout(probeTimer);
154
+ probeTimer = null;
155
+ }
156
+ function scheduleProbe(delayMs) {
157
+ if (disposed || probeTimer !== null) return;
158
+ probeTimer = setTimeout(() => {
159
+ probeTimer = null;
160
+ void runProbe();
161
+ }, delayMs);
162
+ }
163
+ async function runProbe() {
164
+ if (disposed || probing) return;
165
+ if (!needsWatch()) return;
166
+ if (!network.online()) {
167
+ setRegion("offline");
168
+ scheduleProbe(probeEveryMs);
169
+ return;
170
+ }
171
+ probing = true;
172
+ let reachable = false;
173
+ try {
174
+ reachable = await options.probe();
175
+ } catch {
176
+ reachable = false;
177
+ } finally {
178
+ probing = false;
179
+ }
180
+ if (disposed) return;
181
+ if (inCall() || dropped()) setRegion(regionFor(reachable));
182
+ if (dropped() && reachable) maybeRejoin();
183
+ if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
184
+ }
185
+ function syncBanner() {
186
+ const snap = store.snapshot();
187
+ const unstable = snap.phase === "in_call" && snap.connection !== "stable";
188
+ if (!unstable) {
189
+ if (bannerTimer !== null) clearTimeout(bannerTimer);
190
+ bannerTimer = null;
191
+ if (snap.connectionBanner) store.patch({ connectionBanner: false });
192
+ return;
193
+ }
194
+ if (snap.connectionBanner || bannerTimer !== null) return;
195
+ const delay = options.policy().bannerDelayMs;
196
+ if (delay <= 0) {
197
+ store.patch({ connectionBanner: true });
198
+ return;
199
+ }
200
+ bannerTimer = setTimeout(() => {
201
+ bannerTimer = null;
202
+ const latest = store.snapshot();
203
+ if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
204
+ store.patch({ connectionBanner: true });
205
+ }
206
+ }, delay);
207
+ }
208
+ function stopRejoin() {
209
+ if (rejoinTimer !== null) clearTimeout(rejoinTimer);
210
+ rejoinTimer = null;
211
+ }
212
+ function maybeRejoin() {
213
+ if (disposed || rejoinTimer !== null || !dropped()) return;
214
+ const policy = options.policy();
215
+ if (!policy.autoRejoin) return;
216
+ if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
217
+ if (!network.online()) return;
218
+ const attempt = rejoinAttempts;
219
+ rejoinTimer = setTimeout(() => {
220
+ rejoinTimer = null;
221
+ if (disposed || !dropped() || !network.online()) return;
222
+ rejoinAttempts = attempt + 1;
223
+ options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
224
+ void options.autoRejoin().catch(() => void 0);
225
+ }, autoRejoinDelayMs(attempt, random));
226
+ }
227
+ function saveMarker(force) {
228
+ const resume = options.resume;
229
+ const snap = store.snapshot();
230
+ if (resume === void 0 || snap.roomName === null) return;
231
+ const at = now();
232
+ if (!force && at - lastSavedAt < 1e4) return;
233
+ const request = options.currentRequest();
234
+ if (request === null) return;
235
+ lastSavedAt = at;
236
+ resume.save(snap.roomName, { request, at });
237
+ }
238
+ const unsubscribeStore = store.subscribe(() => {
239
+ if (disposed) return;
240
+ const snap = store.snapshot();
241
+ const phase = snap.phase;
242
+ const changed = phase !== lastPhase;
243
+ lastPhase = phase;
244
+ if (changed) {
245
+ if (phase === "in_call") {
246
+ rejoinAttempts = 0;
247
+ stopRejoin();
248
+ saveMarker(true);
249
+ } else if (phase === "disconnected") {
250
+ driverRegion = null;
251
+ saveMarker(true);
252
+ stopProbe();
253
+ scheduleProbe(0);
254
+ } else {
255
+ stopRejoin();
256
+ if (phase !== "joining") driverRegion = null;
257
+ if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
258
+ }
259
+ } else if (phase === "in_call" || phase === "disconnected") {
260
+ saveMarker(false);
261
+ }
262
+ syncBanner();
263
+ });
264
+ const unsubscribeNetwork = network.subscribe((online) => {
265
+ if (disposed) return;
266
+ if (!online) {
267
+ if (inCall() || dropped()) setRegion("offline");
268
+ stopRejoin();
269
+ stopProbe();
270
+ if (needsWatch()) scheduleProbe(probeEveryMs);
271
+ return;
272
+ }
273
+ stopProbe();
274
+ if (needsWatch()) scheduleProbe(0);
275
+ });
276
+ return {
277
+ driverState(state) {
278
+ if (disposed) return;
279
+ if (state === "connected") {
280
+ driverRegion = null;
281
+ if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
282
+ if (!needsWatch()) stopProbe();
283
+ return;
284
+ }
285
+ if (state === "reconnecting" || state === "signal_reconnecting") {
286
+ driverRegion = state;
287
+ if (inCall()) {
288
+ setRegion(network.online() ? state : "offline");
289
+ stopProbe();
290
+ scheduleProbe(0);
291
+ }
292
+ }
293
+ },
294
+ exitConnection() {
295
+ return network.online() ? "server_unreachable" : "offline";
296
+ },
297
+ resumable(roomName) {
298
+ const resume = options.resume;
299
+ if (resume === void 0) return null;
300
+ const marker = resume.load(roomName);
301
+ if (marker === null) return null;
302
+ if (now() - marker.at > RESUME_WINDOW_MS) {
303
+ resume.clear(roomName);
304
+ return null;
305
+ }
306
+ return marker;
307
+ },
308
+ dispose() {
309
+ disposed = true;
310
+ stopProbe();
311
+ stopRejoin();
312
+ if (bannerTimer !== null) clearTimeout(bannerTimer);
313
+ bannerTimer = null;
314
+ unsubscribeStore();
315
+ unsubscribeNetwork();
316
+ }
317
+ };
318
+ }
319
+ var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
320
+ "CLIENT_INITIATED",
321
+ "PARTICIPANT_REMOVED",
322
+ "ROOM_DELETED",
323
+ "ROOM_CLOSED",
324
+ "DUPLICATE_IDENTITY",
325
+ "USER_REJECTED",
326
+ "USER_UNAVAILABLE",
327
+ "SIP_TRUNK_FAILURE"
328
+ ]);
329
+ function isUnannouncedDeparture(reason) {
330
+ return reason === null || !DELIBERATE_DEPARTURES.has(reason);
331
+ }
332
+ function mergeGhosts(live, ghosts, at) {
333
+ const present = new Set(live.map((p) => p.identity));
334
+ const kept = /* @__PURE__ */ new Map();
335
+ const extra = [];
336
+ for (const [identity, ghost] of ghosts) {
337
+ if (present.has(identity) || ghost.until <= at) continue;
338
+ kept.set(identity, ghost);
339
+ extra.push({
340
+ ...ghost.participant,
341
+ micEnabled: false,
342
+ cameraEnabled: false,
343
+ screenSharing: false,
344
+ reconnecting: true
345
+ });
346
+ }
347
+ return { roster: [...live, ...extra], kept };
348
+ }
349
+
4
350
  // src/react/provider.tsx
5
351
  import { globalSlot as globalSlot2 } from "@ai-matrx/kit/global-slot";
6
352
  import {
@@ -410,7 +756,8 @@ function parseRoomToken(body, roomName) {
410
756
  admissionState: readString(payload, "admission_state") ?? void 0,
411
757
  pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
412
758
  breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
413
- observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
759
+ observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
760
+ connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
414
761
  };
415
762
  }
416
763
  function createTokenClient(options) {
@@ -1115,6 +1462,10 @@ function nextPhase(from, event) {
1115
1462
  return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
1116
1463
  case "join":
1117
1464
  return JOINABLE_FROM.has(from) ? "joining" : null;
1465
+ case "auto_rejoin":
1466
+ return from === "disconnected" ? "joining" : null;
1467
+ case "resumed":
1468
+ return from === "resolving" ? "joining" : null;
1118
1469
  case "back_to_prejoin":
1119
1470
  return from === "left" || from === "disconnected" ? "prejoin" : null;
1120
1471
  case "retry":
@@ -1832,13 +2183,19 @@ function recordingLabel(state) {
1832
2183
  }
1833
2184
 
1834
2185
  // src/core/attendance.ts
2186
+ var IDLE_MEDIA_REGION = {
2187
+ permission: { microphone: "unknown", camera: "unknown" },
2188
+ audioOutput: "ok",
2189
+ notice: null,
2190
+ offer: null
2191
+ };
1835
2192
  var IDLE_TRACK = { enabled: false, unavailable: null };
1836
2193
  var IDLE_MEDIA = {
1837
2194
  microphone: IDLE_TRACK,
1838
2195
  camera: IDLE_TRACK
1839
2196
  };
1840
- function unavailableFrom(error) {
1841
- return { code: error.code, message: error.message, remedy: error.remedy };
2197
+ function unavailableFrom(error, reason) {
2198
+ return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
1842
2199
  }
1843
2200
  var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
1844
2201
  function isListenOnly(media) {
@@ -3254,6 +3611,7 @@ var EMPTY = {
3254
3611
  phaseNotice: null,
3255
3612
  roomName: null,
3256
3613
  connection: "stable",
3614
+ connectionBanner: false,
3257
3615
  share: NO_SHARE,
3258
3616
  call: null,
3259
3617
  waiting: null,
@@ -3285,6 +3643,7 @@ var EMPTY = {
3285
3643
  wrapUp: null,
3286
3644
  noteTakerRefusal: null,
3287
3645
  media: IDLE_MEDIA,
3646
+ mediaRegion: IDLE_MEDIA_REGION,
3288
3647
  failure: null,
3289
3648
  collab: EMPTY_COLLAB,
3290
3649
  breakoutRoomId: null
@@ -3502,7 +3861,7 @@ function createMeetStore(initial = {}) {
3502
3861
  },
3503
3862
  setLocalTrack(kind, next) {
3504
3863
  const current = state.media[kind];
3505
- if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
3864
+ if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
3506
3865
  return;
3507
3866
  }
3508
3867
  commit({ ...state, media: { ...state.media, [kind]: next } });
@@ -4089,6 +4448,342 @@ function createMeetSessionIdentity(options) {
4089
4448
  });
4090
4449
  }
4091
4450
 
4451
+ // src/core/media-supervisor.ts
4452
+ function classifyDeviceFailure(error, permission) {
4453
+ if (permission === "denied") return "denied";
4454
+ switch (error.code) {
4455
+ case "permission-denied":
4456
+ if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
4457
+ return "denied";
4458
+ case "device-not-found":
4459
+ return "none";
4460
+ case "device-in-use":
4461
+ return "busy";
4462
+ case "insecure-context":
4463
+ return "blocked_site";
4464
+ default:
4465
+ return "failed";
4466
+ }
4467
+ }
4468
+ function deviceNoticeCode(kind, reason) {
4469
+ if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
4470
+ return kind === "microphone" ? "mic-blocked" : "camera-blocked";
4471
+ }
4472
+ return "device-lost";
4473
+ }
4474
+ function deviceUnavailableReason(track, permission) {
4475
+ if (track.enabled) return null;
4476
+ if (permission === "denied") return "denied";
4477
+ const refused = track.unavailable;
4478
+ if (refused === null) return null;
4479
+ return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
4480
+ }
4481
+ function deviceChipLabel(kind, reason) {
4482
+ const what = kind === "microphone" ? "Mic" : "Camera";
4483
+ switch (reason) {
4484
+ case "denied":
4485
+ case "blocked_site":
4486
+ case "blocked_os":
4487
+ return `${what} blocked`;
4488
+ case "none":
4489
+ return kind === "microphone" ? "No microphone found" : "No camera found";
4490
+ case "busy":
4491
+ return `${what} in use by another app`;
4492
+ case "revoked":
4493
+ return `${what} access was turned off`;
4494
+ default:
4495
+ return `${what} didn't start`;
4496
+ }
4497
+ }
4498
+ function deviceFixSteps(kind, reason, where) {
4499
+ const device = kind === "microphone" ? "Microphone" : "Camera";
4500
+ const lower = device.toLowerCase();
4501
+ switch (reason) {
4502
+ case "denied":
4503
+ case "revoked":
4504
+ case "blocked_site":
4505
+ switch (where.browser) {
4506
+ case "firefox":
4507
+ return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
4508
+ case "safari":
4509
+ 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"];
4510
+ default:
4511
+ return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
4512
+ }
4513
+ case "blocked_os": {
4514
+ const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
4515
+ if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
4516
+ if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
4517
+ return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
4518
+ }
4519
+ case "busy":
4520
+ return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
4521
+ case "none":
4522
+ return [`Connect a ${lower}`, "Press Try again"];
4523
+ default:
4524
+ return [`Pick another ${lower} in Settings`, "Press Try again"];
4525
+ }
4526
+ }
4527
+ function detectBrowser(userAgent) {
4528
+ if (/Edg\//.test(userAgent)) return "edge";
4529
+ if (/Firefox\//.test(userAgent)) return "firefox";
4530
+ if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
4531
+ if (/Safari\//.test(userAgent)) return "safari";
4532
+ return "other";
4533
+ }
4534
+ function detectOs(userAgent, maxTouchPoints = 0) {
4535
+ if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
4536
+ if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
4537
+ if (/Android/.test(userAgent)) return "android";
4538
+ if (/Windows/.test(userAgent)) return "windows";
4539
+ if (/Linux|CrOS/.test(userAgent)) return "linux";
4540
+ return "other";
4541
+ }
4542
+ function webMediaPlatform() {
4543
+ const scope = globalThis;
4544
+ const nav = scope.navigator;
4545
+ if (nav === void 0) return null;
4546
+ const ua = nav.userAgent ?? "";
4547
+ const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
4548
+ const query = async (kind) => {
4549
+ try {
4550
+ return await nav.permissions?.query({ name: kind }) ?? null;
4551
+ } catch {
4552
+ return null;
4553
+ }
4554
+ };
4555
+ return {
4556
+ browser: detectBrowser(ua),
4557
+ os: detectOs(ua, nav.maxTouchPoints ?? 0),
4558
+ async queryPermission(kind) {
4559
+ return reading((await query(kind))?.state);
4560
+ },
4561
+ watchPermission(kind, onChange) {
4562
+ let status = null;
4563
+ let stopped = false;
4564
+ void query(kind).then((found) => {
4565
+ if (stopped || found === null) return;
4566
+ status = found;
4567
+ found.onchange = () => onChange(reading(found.state));
4568
+ });
4569
+ return () => {
4570
+ stopped = true;
4571
+ if (status !== null) status.onchange = null;
4572
+ };
4573
+ },
4574
+ async listDevices() {
4575
+ try {
4576
+ return await nav.mediaDevices?.enumerateDevices() ?? [];
4577
+ } catch {
4578
+ return [];
4579
+ }
4580
+ },
4581
+ onVisible(callback) {
4582
+ const doc = scope.document;
4583
+ if (doc === void 0) return () => void 0;
4584
+ const handler = () => {
4585
+ if (doc.visibilityState === "visible") callback();
4586
+ };
4587
+ doc.addEventListener("visibilitychange", handler);
4588
+ return () => doc.removeEventListener("visibilitychange", handler);
4589
+ }
4590
+ };
4591
+ }
4592
+ var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
4593
+ function isAlias(deviceId2) {
4594
+ return deviceId2 === "default" || deviceId2 === "communications" || deviceId2 === "";
4595
+ }
4596
+ function createMediaSupervisor(options) {
4597
+ const { store, platform } = options;
4598
+ let unsubscribers = [];
4599
+ let started = false;
4600
+ let noticeId = 0;
4601
+ let known = null;
4602
+ let inUse = { audioinput: null, videoinput: null };
4603
+ function region() {
4604
+ return store.snapshot().mediaRegion;
4605
+ }
4606
+ function setRegion(next) {
4607
+ const current = region();
4608
+ const merged = { ...current, ...next };
4609
+ 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) {
4610
+ return;
4611
+ }
4612
+ store.patch({ mediaRegion: merged });
4613
+ }
4614
+ function setPermission(kind, reading) {
4615
+ const current = region().permission;
4616
+ if (current[kind] === reading) return;
4617
+ setRegion({ permission: { ...current, [kind]: reading } });
4618
+ const track = store.snapshot().media[kind];
4619
+ if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
4620
+ store.setLocalTrack(kind, {
4621
+ enabled: false,
4622
+ unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
4623
+ });
4624
+ }
4625
+ }
4626
+ async function snapshotDevices() {
4627
+ const list = platform === null ? [] : await platform.listDevices();
4628
+ const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
4629
+ for (const device of list) {
4630
+ if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
4631
+ }
4632
+ return out;
4633
+ }
4634
+ function readInUse() {
4635
+ const driver = options.driver();
4636
+ if (driver === null) return;
4637
+ const active = driver.activeDevices();
4638
+ inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
4639
+ }
4640
+ async function handleDevicesChanged() {
4641
+ if (platform === null) return;
4642
+ const before = known;
4643
+ const now = await snapshotDevices();
4644
+ known = now;
4645
+ const driver = options.driver();
4646
+ if (driver === null || before === null) {
4647
+ readInUse();
4648
+ return;
4649
+ }
4650
+ for (const kind of ["microphone", "camera"]) {
4651
+ const deviceKind = INPUT_KIND[kind];
4652
+ const previous = inUse[deviceKind];
4653
+ const track = store.snapshot().media[kind];
4654
+ const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
4655
+ if (gone) {
4656
+ const from = before[deviceKind].get(previous) ?? "";
4657
+ const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
4658
+ if (remaining.length === 0) {
4659
+ store.setLocalTrack(kind, {
4660
+ enabled: false,
4661
+ unavailable: {
4662
+ code: "device-not-found",
4663
+ message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
4664
+ remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
4665
+ reason: "none"
4666
+ }
4667
+ });
4668
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
4669
+ continue;
4670
+ }
4671
+ if (kind === "camera") {
4672
+ const [nextId, nextLabel] = remaining[0];
4673
+ try {
4674
+ await driver.switchDevice("videoinput", nextId);
4675
+ if (track.enabled || track.unavailable !== null) {
4676
+ await driver.setCameraEnabled(true);
4677
+ store.setLocalTrack("camera", { enabled: true, unavailable: null });
4678
+ }
4679
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
4680
+ } catch (error) {
4681
+ const refused = await unavailable(error, "camera", "meet.camera(fallback)");
4682
+ store.setLocalTrack("camera", { enabled: false, unavailable: refused });
4683
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
4684
+ }
4685
+ } else {
4686
+ const active = driver.activeDevices().audioinput;
4687
+ const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
4688
+ setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
4689
+ }
4690
+ continue;
4691
+ }
4692
+ const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
4693
+ if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
4694
+ setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
4695
+ }
4696
+ }
4697
+ readInUse();
4698
+ }
4699
+ async function unavailable(error, kind, operation) {
4700
+ const normalized = normalizeDeviceError(error, operation);
4701
+ const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
4702
+ if (permission !== "unknown") setPermission(kind, permission);
4703
+ return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
4704
+ }
4705
+ return {
4706
+ start() {
4707
+ if (started || platform === null) return;
4708
+ started = true;
4709
+ for (const kind of ["microphone", "camera"]) {
4710
+ void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
4711
+ unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
4712
+ }
4713
+ unsubscribers.push(
4714
+ platform.onVisible(() => {
4715
+ const driver = options.driver();
4716
+ if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
4717
+ driver.startAudio().catch(() => void 0);
4718
+ })
4719
+ );
4720
+ },
4721
+ stop() {
4722
+ for (const off of unsubscribers) off();
4723
+ unsubscribers = [];
4724
+ started = false;
4725
+ },
4726
+ unavailable,
4727
+ onAudioPlayback(canPlay) {
4728
+ setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
4729
+ },
4730
+ onDevicesChanged() {
4731
+ void handleDevicesChanged().catch((error) => {
4732
+ options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
4733
+ });
4734
+ },
4735
+ onDeviceError(error, deviceKind) {
4736
+ if (deviceKind === null) return;
4737
+ const kind = deviceKind === "audioinput" ? "microphone" : "camera";
4738
+ void unavailable(error, kind, `meet.${kind}`).then((refused) => {
4739
+ const track = store.snapshot().media[kind];
4740
+ if (track.enabled) return;
4741
+ store.setLocalTrack(kind, { enabled: false, unavailable: refused });
4742
+ });
4743
+ },
4744
+ onConnected() {
4745
+ void snapshotDevices().then((now) => {
4746
+ known = now;
4747
+ readInUse();
4748
+ });
4749
+ },
4750
+ onDisconnected() {
4751
+ known = null;
4752
+ inUse = { audioinput: null, videoinput: null };
4753
+ setRegion({ audioOutput: "ok", notice: null, offer: null });
4754
+ },
4755
+ async enableAudio() {
4756
+ const driver = options.driver();
4757
+ if (driver?.startAudio === void 0) return;
4758
+ try {
4759
+ await driver.startAudio();
4760
+ setRegion({ audioOutput: "ok" });
4761
+ } catch (error) {
4762
+ options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
4763
+ }
4764
+ },
4765
+ async acceptOffer() {
4766
+ const offer = region().offer;
4767
+ const driver = options.driver();
4768
+ setRegion({ offer: null });
4769
+ if (offer === null || driver === null) return;
4770
+ try {
4771
+ await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
4772
+ readInUse();
4773
+ } catch (error) {
4774
+ const refused = await unavailable(error, offer.kind, "meet.switchDevice");
4775
+ options.report("warn", refused.message, refused.remedy);
4776
+ }
4777
+ },
4778
+ dismissOffer() {
4779
+ setRegion({ offer: null });
4780
+ },
4781
+ dismissNotice() {
4782
+ setRegion({ notice: null });
4783
+ }
4784
+ };
4785
+ }
4786
+
4092
4787
  // src/core/room-events.ts
4093
4788
  var RECORDING_STATES = [
4094
4789
  "idle",
@@ -4535,6 +5230,18 @@ function createRoomEngine(options) {
4535
5230
  let disposed = false;
4536
5231
  let moving = false;
4537
5232
  const confirmingEnds = /* @__PURE__ */ new WeakSet();
5233
+ let lastServerUrl = null;
5234
+ let connectionPolicy = DEFAULT_CONNECTION_POLICY;
5235
+ let ghosts = /* @__PURE__ */ new Map();
5236
+ let ghostTimer = null;
5237
+ let liveRoster = [];
5238
+ const departures = /* @__PURE__ */ new Map();
5239
+ function requestAsNow() {
5240
+ if (lastRequest === null) return null;
5241
+ const media2 = store.snapshot().media;
5242
+ if (store.snapshot().phase !== "in_call") return lastRequest;
5243
+ return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
5244
+ }
4538
5245
  async function dropRoom() {
4539
5246
  const target = driver;
4540
5247
  driver = null;
@@ -4544,7 +5251,9 @@ function createRoomEngine(options) {
4544
5251
  }
4545
5252
  store.setLocalTrack("microphone", IDLE_TRACK);
4546
5253
  store.setLocalTrack("camera", IDLE_TRACK);
5254
+ media.onDisconnected();
4547
5255
  store.patch({ share: NO_SHARE, connection: "stable" });
5256
+ clearGhosts();
4548
5257
  await target?.disconnect();
4549
5258
  }
4550
5259
  async function leaveRoom() {
@@ -4608,11 +5317,26 @@ function createRoomEngine(options) {
4608
5317
  report("error", normalized.message, normalized.remedy);
4609
5318
  return normalized;
4610
5319
  }
5320
+ const media = createMediaSupervisor({
5321
+ store,
5322
+ platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
5323
+ driver: () => driver,
5324
+ report
5325
+ });
4611
5326
  const handlers = {
5327
+ audioPlayback(canPlay) {
5328
+ media.onAudioPlayback(canPlay);
5329
+ },
5330
+ devicesChanged() {
5331
+ media.onDevicesChanged();
5332
+ },
5333
+ mediaDeviceError(error, kind) {
5334
+ media.onDeviceError(error, kind);
5335
+ },
4612
5336
  participants(list) {
4613
5337
  const joinedAt = new Date(now()).toISOString();
4614
5338
  const before = new Set(store.snapshot().participants.map((p) => p.identity));
4615
- store.setParticipants(list.map((entry) => projectParticipant(entry, joinedAt)));
5339
+ projectRoster(list, joinedAt);
4616
5340
  const snapshot = store.snapshot();
4617
5341
  const newcomer = snapshot.participants.some(
4618
5342
  (participant) => !participant.isLocal && !before.has(participant.identity)
@@ -4624,6 +5348,9 @@ function createRoomEngine(options) {
4624
5348
  );
4625
5349
  }
4626
5350
  },
5351
+ participantLeft(who, reason) {
5352
+ departures.set(who, reason);
5353
+ },
4627
5354
  roomMetadata(metadata) {
4628
5355
  let spotlight = null;
4629
5356
  if (metadata.length > 0) {
@@ -4650,12 +5377,8 @@ function createRoomEngine(options) {
4650
5377
  store.setQuality(asParticipantIdentity(who), value);
4651
5378
  },
4652
5379
  connectionState(state) {
4653
- if (state === "connected") {
4654
- if (store.snapshot().connection !== "stable") store.patch({ connection: "stable" });
4655
- store.dispatch({ type: "connected" });
4656
- } else if (state === "reconnecting") {
4657
- store.patch({ connection: "reconnecting" });
4658
- }
5380
+ supervisor.driverState(state);
5381
+ if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
4659
5382
  },
4660
5383
  transcription(event) {
4661
5384
  const snapshot = store.snapshot();
@@ -4788,6 +5511,7 @@ function createRoomEngine(options) {
4788
5511
  };
4789
5512
  async function resolveExitAndApply(hint) {
4790
5513
  const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
5514
+ lastRequest = requestAsNow();
4791
5515
  await dropRoom();
4792
5516
  let server;
4793
5517
  if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
@@ -4801,7 +5525,7 @@ function createRoomEngine(options) {
4801
5525
  }
4802
5526
  if (disposed) return;
4803
5527
  const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
4804
- store.patch({ connection: resolved.connection });
5528
+ store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
4805
5529
  if (resolved.to === "waiting:knocking") {
4806
5530
  store.patch({
4807
5531
  waiting: {
@@ -4833,6 +5557,8 @@ function createRoomEngine(options) {
4833
5557
  ...request.observe === true ? { observe: true } : {}
4834
5558
  });
4835
5559
  current = token;
5560
+ lastServerUrl = token.serverUrl;
5561
+ connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
4836
5562
  store.patch({
4837
5563
  localIdentity: token.identity,
4838
5564
  breakoutRoomId: token.breakoutRoomId ?? null,
@@ -4872,6 +5598,7 @@ function createRoomEngine(options) {
4872
5598
  store.dispatch({ type: "connected" });
4873
5599
  store.setLocalTrack("microphone", IDLE_TRACK);
4874
5600
  store.setLocalTrack("camera", IDLE_TRACK);
5601
+ media.start();
4875
5602
  const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
4876
5603
  store.patch({ effects });
4877
5604
  if (!effects.noiseSuppression || effects.background !== "none") {
@@ -4896,6 +5623,7 @@ function createRoomEngine(options) {
4896
5623
  if (request.microphone !== false) {
4897
5624
  await enableTrack(created, "microphone", true);
4898
5625
  }
5626
+ media.onConnected();
4899
5627
  }
4900
5628
  async function enableTrack(target, kind, enabled) {
4901
5629
  try {
@@ -4905,10 +5633,10 @@ function createRoomEngine(options) {
4905
5633
  store.setLocalTrack(kind, state);
4906
5634
  return state;
4907
5635
  } catch (error) {
4908
- const normalized = normalizeDeviceError(error, `meet.${kind}`);
4909
- const state = { enabled: false, unavailable: unavailableFrom(normalized) };
5636
+ const refused = await media.unavailable(error, kind, `meet.${kind}`);
5637
+ const state = { enabled: false, unavailable: refused };
4910
5638
  store.setLocalTrack(kind, state);
4911
- report("warn", normalized.message, normalized.remedy);
5639
+ report("warn", refused.message, refused.remedy);
4912
5640
  return state;
4913
5641
  }
4914
5642
  }
@@ -4937,7 +5665,59 @@ function createRoomEngine(options) {
4937
5665
  throw normalized;
4938
5666
  }
4939
5667
  }
4940
- return {
5668
+ function projectRoster(list, joinedAt) {
5669
+ const at = now();
5670
+ const previous = new Map(liveRoster.map((p) => [p.identity, p]));
5671
+ const present = new Set(list.map((p) => p.identity));
5672
+ const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
5673
+ for (const [who, reason] of departures) {
5674
+ const last = previous.get(who);
5675
+ if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
5676
+ ghosts.set(who, { participant: last, until: at + graceMs });
5677
+ }
5678
+ }
5679
+ departures.clear();
5680
+ liveRoster = list;
5681
+ const merged = mergeGhosts(list, ghosts, at);
5682
+ ghosts = merged.kept;
5683
+ store.setParticipants(
5684
+ merged.roster.map((entry) => {
5685
+ const projected = projectParticipant(entry, joinedAt);
5686
+ return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
5687
+ })
5688
+ );
5689
+ scheduleGhostExpiry();
5690
+ }
5691
+ function scheduleGhostExpiry() {
5692
+ if (ghostTimer !== null) clearTimeout(ghostTimer);
5693
+ ghostTimer = null;
5694
+ if (ghosts.size === 0 || disposed) return;
5695
+ const next = Math.min(...[...ghosts.values()].map((g) => g.until));
5696
+ ghostTimer = setTimeout(() => {
5697
+ ghostTimer = null;
5698
+ if (disposed || driver === null) return;
5699
+ projectRoster(liveRoster, new Date(now()).toISOString());
5700
+ }, Math.max(0, next - now()) + 50);
5701
+ }
5702
+ function clearGhosts() {
5703
+ ghosts = /* @__PURE__ */ new Map();
5704
+ departures.clear();
5705
+ liveRoster = [];
5706
+ if (ghostTimer !== null) clearTimeout(ghostTimer);
5707
+ ghostTimer = null;
5708
+ }
5709
+ const supervisor = createConnectionSupervisor({
5710
+ store,
5711
+ network: options.connection?.network ?? createWebNetworkPort(),
5712
+ probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
5713
+ policy: () => connectionPolicy,
5714
+ autoRejoin: () => engine.autoRejoin(),
5715
+ currentRequest: requestAsNow,
5716
+ resume: options.connection?.resume,
5717
+ now,
5718
+ report: (level, message) => report(level, message)
5719
+ });
5720
+ const engine = {
4941
5721
  join(request) {
4942
5722
  if (joining !== null) return joining;
4943
5723
  if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
@@ -4982,6 +5762,35 @@ function createRoomEngine(options) {
4982
5762
  });
4983
5763
  return joining;
4984
5764
  },
5765
+ async autoRejoin() {
5766
+ const request = lastRequest;
5767
+ if (request === null || joining !== null) return joining ?? void 0;
5768
+ if (!store.dispatch({ type: "auto_rejoin" })) return;
5769
+ tokens.invalidate(request.roomName);
5770
+ current = null;
5771
+ joining = connect(request).catch((error) => {
5772
+ throw failJoin(error, "meet.autoRejoin");
5773
+ }).finally(() => {
5774
+ joining = null;
5775
+ });
5776
+ return joining;
5777
+ },
5778
+ resume(roomName) {
5779
+ const marker = supervisor.resumable(roomName);
5780
+ if (marker === null || joining !== null) return false;
5781
+ if (!store.dispatch({ type: "resumed" })) return false;
5782
+ lastRequest = { ...marker.request, roomName };
5783
+ report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
5784
+ tokens.invalidate(roomName);
5785
+ current = null;
5786
+ joining = connect(lastRequest).catch((error) => {
5787
+ throw failJoin(error, "meet.resume");
5788
+ }).finally(() => {
5789
+ joining = null;
5790
+ });
5791
+ joining.catch(() => void 0);
5792
+ return true;
5793
+ },
4985
5794
  async moveToBreakout(roomId) {
4986
5795
  const request = lastRequest;
4987
5796
  if (request === null || driver === null) {
@@ -5136,6 +5945,10 @@ function createRoomEngine(options) {
5136
5945
  if (driver === null) return;
5137
5946
  await driver.switchDevice(kind, deviceId2);
5138
5947
  },
5948
+ enableAudio: () => media.enableAudio(),
5949
+ acceptDeviceOffer: () => media.acceptOffer(),
5950
+ dismissDeviceOffer: () => media.dismissOffer(),
5951
+ dismissDeviceNotice: () => media.dismissNotice(),
5139
5952
  pin(who) {
5140
5953
  store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
5141
5954
  },
@@ -5149,6 +5962,9 @@ function createRoomEngine(options) {
5149
5962
  token: () => current,
5150
5963
  async dispose() {
5151
5964
  disposed = true;
5965
+ supervisor.dispose();
5966
+ media.stop();
5967
+ clearGhosts();
5152
5968
  if (driver !== null) {
5153
5969
  const target = driver;
5154
5970
  driver = null;
@@ -5157,6 +5973,7 @@ function createRoomEngine(options) {
5157
5973
  store.reset();
5158
5974
  }
5159
5975
  };
5976
+ return engine;
5160
5977
  }
5161
5978
  function canModerate(participant) {
5162
5979
  return participant !== void 0 && isHostRole(participant.role);
@@ -5467,8 +6284,11 @@ function mapConnectionState(state) {
5467
6284
  case "connecting":
5468
6285
  return "connecting";
5469
6286
  case "reconnecting":
5470
- case "signalReconnecting":
5471
6287
  return "reconnecting";
6288
+ // Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
6289
+ // signal blip from a full media reconnect.
6290
+ case "signalReconnecting":
6291
+ return "signal_reconnecting";
5472
6292
  default:
5473
6293
  return "disconnected";
5474
6294
  }
@@ -5549,8 +6369,12 @@ function createLiveKitDriver(handlers, options = {}) {
5549
6369
  // R5: large meetings degrade gracefully instead of melting a laptop.
5550
6370
  adaptiveStream: true,
5551
6371
  dynacast: true,
6372
+ // S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
6373
+ // first two attempts), give-up limits in one place (`connection-supervisor.ts`).
6374
+ reconnectPolicy: createReconnectPolicy(),
5552
6375
  ...options.roomOptions
5553
6376
  });
6377
+ const reasonName = (reason) => reason === void 0 || reason === null ? null : module.DisconnectReason?.[reason] ?? String(reason);
5554
6378
  const roster = () => {
5555
6379
  const local = snapshotParticipant(created.localParticipant, true);
5556
6380
  const remotes = [...created.remoteParticipants.values()].map(
@@ -5568,8 +6392,17 @@ function createLiveKitDriver(handlers, options = {}) {
5568
6392
  if (track.kind !== Track.Kind.Audio) return;
5569
6393
  for (const element of track.detach()) element.remove();
5570
6394
  });
5571
- created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, roster).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) => {
6395
+ created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
6396
+ handlers.participantLeft?.(participant.identity, reasonName(reason));
6397
+ roster();
6398
+ }).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) => {
5572
6399
  handlers.roomMetadata?.(metadata ?? "");
6400
+ }).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
6401
+ handlers.audioPlayback?.(canPlay);
6402
+ }).on(RoomEvent.MediaDevicesChanged, () => {
6403
+ handlers.devicesChanged?.();
6404
+ }).on(RoomEvent.MediaDevicesError, (error, kind) => {
6405
+ handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
5573
6406
  }).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
5574
6407
  handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
5575
6408
  }).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
@@ -5585,8 +6418,7 @@ function createLiveKitDriver(handlers, options = {}) {
5585
6418
  }).on(RoomEvent.Disconnected, (reason) => {
5586
6419
  audioContainer?.remove();
5587
6420
  audioContainer = null;
5588
- const named = reason === void 0 ? null : module?.DisconnectReason?.[reason] ?? String(reason);
5589
- handlers.disconnected(named);
6421
+ handlers.disconnected(reasonName(reason));
5590
6422
  });
5591
6423
  try {
5592
6424
  created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
@@ -5667,6 +6499,7 @@ function createLiveKitDriver(handlers, options = {}) {
5667
6499
  }
5668
6500
  projectRoster?.();
5669
6501
  handlers.roomMetadata?.(target.metadata ?? "");
6502
+ handlers.audioPlayback?.(target.canPlaybackAudio);
5670
6503
  },
5671
6504
  async disconnect() {
5672
6505
  if (room === null) return;
@@ -5793,6 +6626,11 @@ function createLiveKitDriver(handlers, options = {}) {
5793
6626
  } catch (error) {
5794
6627
  throw normalizeDeviceError(error, "meet.switchDevice");
5795
6628
  }
6629
+ },
6630
+ async startAudio() {
6631
+ const target = room;
6632
+ if (target === null) return;
6633
+ await target.startAudio();
5796
6634
  }
5797
6635
  };
5798
6636
  }
@@ -5976,7 +6814,14 @@ function MeetRuntime(props) {
5976
6814
  effectsStorage,
5977
6815
  resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,
5978
6816
  // §3.4: on every LiveKit disconnect, ask the server what happened to this connection.
5979
- readExit: (connection) => meetings.readExit(connection)
6817
+ readExit: (connection) => meetings.readExit(connection),
6818
+ // S6b: the connection supervisor's ports — the device's network, whether OUR servers (the
6819
+ // API and the media server) answer, and the per-tab reload-after-offline marker.
6820
+ connection: {
6821
+ network: createWebNetworkPort(),
6822
+ resume: createWebResumeStorage(),
6823
+ probe: (serverUrl) => createReachabilityProbe(() => [baseUrl, ...serverUrl !== null ? [httpUrlOf(serverUrl)] : []])()
6824
+ }
5980
6825
  });
5981
6826
  const meetings = createMeetingSession({
5982
6827
  manager,
@@ -6193,22 +7038,10 @@ function observedRole(role, standing) {
6193
7038
  if (role === "host" || role === "cohost") return role;
6194
7039
  return isLinkFollower(standing) ? "guest" : "participant";
6195
7040
  }
6196
- function observedDevice(track) {
7041
+ function observedDevice(track, permission = "unknown") {
6197
7042
  if (track.enabled) return "on";
6198
- const refused = track.unavailable;
6199
- if (refused === null) return "muted_by_me";
6200
- switch (refused.code) {
6201
- case "permission-denied":
6202
- return "unavailable:denied";
6203
- case "device-not-found":
6204
- return "unavailable:none";
6205
- case "device-in-use":
6206
- return "unavailable:busy";
6207
- case "insecure-context":
6208
- return "unavailable:blocked_site";
6209
- default:
6210
- return "unavailable:failed";
6211
- }
7043
+ const reason = deviceUnavailableReason(track, permission);
7044
+ return reason === null ? "muted_by_me" : `unavailable:${reason}`;
6212
7045
  }
6213
7046
  function endedRecord(snapshot, given) {
6214
7047
  const record = snapshot?.meeting ?? given;
@@ -6241,9 +7074,10 @@ function observe(input) {
6241
7074
  hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
6242
7075
  lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
6243
7076
  recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
6244
- audioBlocked: null,
6245
- camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera) : null,
6246
- microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone) : null
7077
+ // S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
7078
+ audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
7079
+ camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
7080
+ microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
6247
7081
  };
6248
7082
  }
6249
7083
  function staticObservation(phase, reason = null) {
@@ -6304,14 +7138,14 @@ function participantAttributes(p) {
6304
7138
  "data-meet-participant-role": observedRole(p.role, p.standing),
6305
7139
  "data-meet-participant-self": String(p.isLocal)
6306
7140
  };
6307
- const quality = observedQuality(p.quality);
7141
+ const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
6308
7142
  if (quality !== null) attrs["data-meet-participant-connection"] = quality;
6309
7143
  return attrs;
6310
7144
  }
6311
- function microphoneNoticeCode(track) {
6312
- const refused = track.unavailable;
6313
- if (refused === null) return null;
6314
- return refused.code === "permission-denied" || refused.code === "insecure-context" ? "mic-blocked" : "device-lost";
7145
+ function microphoneNoticeCode(track, permission = "unknown") {
7146
+ const reason = deviceUnavailableReason(track, permission);
7147
+ if (reason === null) return null;
7148
+ return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
6315
7149
  }
6316
7150
 
6317
7151
  // src/react/root.tsx
@@ -7412,6 +8246,26 @@ function useConsentNotice() {
7412
8246
  if (agent === null) return null;
7413
8247
  return `${agent.displayName} is in this meeting: it is transcribing what is said and writing notes everyone here can read.`;
7414
8248
  }
8249
+ function useMediaSupervisor() {
8250
+ const host = useMeetHost();
8251
+ const snapshot = useMeetSnapshot();
8252
+ const engine = host?.engine ?? null;
8253
+ return {
8254
+ region: snapshot?.mediaRegion ?? IDLE_MEDIA_REGION,
8255
+ where: useMemo3(() => {
8256
+ const platform = webMediaPlatform();
8257
+ return { browser: platform?.browser ?? "other", os: platform?.os ?? "other" };
8258
+ }, []),
8259
+ enableAudio: useCallback2(async () => engine?.enableAudio(), [engine]),
8260
+ acceptOffer: useCallback2(async () => engine?.acceptDeviceOffer(), [engine]),
8261
+ dismissOffer: useCallback2(() => engine?.dismissDeviceOffer(), [engine]),
8262
+ dismissNotice: useCallback2(() => engine?.dismissDeviceNotice(), [engine]),
8263
+ retry: useCallback2(
8264
+ async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
8265
+ [engine]
8266
+ )
8267
+ };
8268
+ }
7415
8269
  function useLocalMedia() {
7416
8270
  const snapshot = useMeetSnapshot();
7417
8271
  return snapshot?.media ?? IDLE_MEDIA;
@@ -8523,6 +9377,7 @@ function useMeetCore() {
8523
9377
  reason: snapshot?.reason ?? null,
8524
9378
  phaseNotice: snapshot?.phaseNotice ?? null,
8525
9379
  connection: snapshot?.connection ?? "stable",
9380
+ connectionBanner: snapshot?.connectionBanner ?? false,
8526
9381
  share: snapshot?.share ?? NO_SHARE,
8527
9382
  call: snapshot?.call ?? null,
8528
9383
  actions
@@ -9789,7 +10644,8 @@ function ParticipantTile({
9789
10644
  participant.displayName,
9790
10645
  participant.isLocal ? " (you)" : "",
9791
10646
  participant.isAgent ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__badge", children: "AI" }) : null,
9792
- /* @__PURE__ */ jsx6(RoleBadge, { role: participant.role, standing: participant.standing })
10647
+ /* @__PURE__ */ jsx6(RoleBadge, { role: participant.role, standing: participant.standing }),
10648
+ participant.reconnecting === true ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__badge", children: "Reconnecting\u2026" }) : null
9793
10649
  ] }),
9794
10650
  !participant.micEnabled ? /* @__PURE__ */ jsx6(MicOffIcon, { width: 14, height: 14 }) : null,
9795
10651
  /* @__PURE__ */ jsx6(QualityBars, { quality: participant.quality })
@@ -9832,6 +10688,12 @@ function QualityBars({ quality }) {
9832
10688
  const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
9833
10689
  return /* @__PURE__ */ jsx6("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ jsx6(SignalIcon, { bars, width: 14, height: 14 }) });
9834
10690
  }
10691
+ var CONNECTION_BANNER = {
10692
+ signal_reconnecting: "Reconnecting\u2026",
10693
+ reconnecting: "Reconnecting\u2026",
10694
+ offline: "You're offline. Reconnecting when your internet is back\u2026",
10695
+ server_unreachable: "Can't reach the meeting server. Retrying\u2026"
10696
+ };
9835
10697
  function Stage({ pageSize }) {
9836
10698
  const stage = useStage(pageSize);
9837
10699
  const columns = gridColumns(stage.tiles.length);
@@ -10252,12 +11114,83 @@ function Toast({ text: text3, failed, onDone }) {
10252
11114
  /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
10253
11115
  ] });
10254
11116
  }
11117
+ function MediaNotices() {
11118
+ const media = useLocalMedia();
11119
+ const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
11120
+ const [fixing, setFixing] = useState4(null);
11121
+ const chips = ["microphone", "camera"].flatMap((kind) => {
11122
+ const reason = deviceUnavailableReason(media[kind], region.permission[kind]);
11123
+ return reason === null ? [] : [{ kind, reason }];
11124
+ });
11125
+ const fixReason = fixing === null ? null : chips.find((chip) => chip.kind === fixing)?.reason ?? null;
11126
+ const notice = region.notice;
11127
+ const offer = region.offer;
11128
+ const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
11129
+ if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
11130
+ return /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-notices", children: [
11131
+ region.audioOutput === "blocked" ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "alert", "data-meet-notice": "audio-blocked", children: [
11132
+ /* @__PURE__ */ jsx6("span", { children: "Sound is paused in this tab" }),
11133
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
11134
+ ] }) : null,
11135
+ chips.map(({ kind, reason }) => /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
11136
+ /* @__PURE__ */ jsx6("span", { children: deviceChipLabel(kind, reason) }),
11137
+ /* @__PURE__ */ jsx6(
11138
+ "button",
11139
+ {
11140
+ type: "button",
11141
+ className: "mx-meet__primary mx-meet__primary--small",
11142
+ "aria-label": kind === "microphone" ? "Fix microphone" : "Fix camera",
11143
+ "aria-expanded": fixing === kind,
11144
+ onClick: () => setFixing(fixing === kind ? null : kind),
11145
+ children: "Fix"
11146
+ }
11147
+ )
11148
+ ] }, kind)),
11149
+ fixing !== null && fixReason !== null ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__popover mx-meet__fix-sheet", role: "dialog", "aria-label": `Fix ${name(fixing).toLowerCase()}`, children: [
11150
+ /* @__PURE__ */ jsx6("ol", { className: "mx-meet__fix-steps", children: deviceFixSteps(fixing, fixReason, where).map((step) => /* @__PURE__ */ jsx6("li", { children: step }, step)) }),
11151
+ /* @__PURE__ */ jsxs3("div", { className: "mx-meet__fix-actions", children: [
11152
+ /* @__PURE__ */ jsx6(
11153
+ "button",
11154
+ {
11155
+ type: "button",
11156
+ className: "mx-meet__primary mx-meet__primary--small",
11157
+ onClick: () => {
11158
+ const kind = fixing;
11159
+ setFixing(null);
11160
+ void retry(kind);
11161
+ },
11162
+ children: "Try again"
11163
+ }
11164
+ ),
11165
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Close", onClick: () => setFixing(null), children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
11166
+ ] })
11167
+ ] }) : null,
11168
+ notice !== null ? /* @__PURE__ */ jsxs3(
11169
+ "div",
11170
+ {
11171
+ className: "mx-meet__media-pill",
11172
+ role: "status",
11173
+ ...notice.outcome === "lost" ? { "data-meet-notice": "device-lost" } : {},
11174
+ children: [
11175
+ /* @__PURE__ */ jsx6("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 now using ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
11176
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissNotice, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
11177
+ ]
11178
+ }
11179
+ ) : null,
11180
+ offer !== null ? /* @__PURE__ */ jsxs3("div", { className: "mx-meet__media-pill", role: "status", children: [
11181
+ /* @__PURE__ */ jsx6("span", { children: `New ${offer.kind}: ${offer.label || name(offer.kind)}` }),
11182
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void acceptOffer(), children: "Use it" }),
11183
+ /* @__PURE__ */ jsx6("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissOffer, children: /* @__PURE__ */ jsx6(CloseIcon, { width: 14, height: 14 }) })
11184
+ ] }) : null
11185
+ ] });
11186
+ }
10255
11187
  function MeetingOverlays() {
10256
11188
  const reactions = useReactions();
10257
11189
  const { notice, dismiss } = useModerationNotice();
10258
11190
  const room = useRoom();
10259
11191
  return /* @__PURE__ */ jsxs3(Fragment, { children: [
10260
11192
  /* @__PURE__ */ jsx6(ReactionOverlay, { reactions }),
11193
+ /* @__PURE__ */ jsx6(MediaNotices, {}),
10261
11194
  notice !== null && notice.kind !== "removed" ? (
10262
11195
  // A host's request ("the host is asking you to unmute"), not an error:
10263
11196
  // an assertive status, so it is announced at once without being filed
@@ -11539,7 +12472,7 @@ function MeetSkinRoom({
11539
12472
  headerControls !== void 0 && headerControls !== null ? /* @__PURE__ */ jsx6("span", { className: "mx-meet__header-controls", "data-meet-header-controls": "", children: headerControls }) : null,
11540
12473
  isHost ? /* @__PURE__ */ jsx6(HostMenu, {}) : null
11541
12474
  ] }),
11542
- core.connection !== "stable" ? /* @__PURE__ */ jsx6("p", { className: "mx-meet__banner", role: "status", children: "Reconnecting\u2026" }) : null,
12475
+ core.connectionBanner && core.connection !== "stable" ? /* @__PURE__ */ jsx6("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": `connection-${core.connection}`, children: CONNECTION_BANNER[core.connection] }) : null,
11543
12476
  /* @__PURE__ */ jsx6(ShareNotice, {}),
11544
12477
  /* @__PURE__ */ jsx6(Stage, {}),
11545
12478
  /* @__PURE__ */ jsx6(Captions, {}),
@@ -11573,7 +12506,7 @@ var REFUSED_TITLES = {
11573
12506
  registration_required: "Register to join this meeting"
11574
12507
  };
11575
12508
  function ExitScreen({ onLeave }) {
11576
- const { phase, reason, host, snapshot, actions, call } = useMeetCore();
12509
+ const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
11577
12510
  const [busy, setBusy] = useState4(false);
11578
12511
  const failure = snapshot?.failure ?? null;
11579
12512
  const rejoin = /* @__PURE__ */ jsx6(
@@ -11629,7 +12562,7 @@ function ExitScreen({ onLeave }) {
11629
12562
  break;
11630
12563
  case "disconnected":
11631
12564
  title = "You lost your connection to the meeting";
11632
- line = failure?.remedy ?? "Check your internet connection, then rejoin.";
12565
+ line = connection === "offline" ? "You're offline. You'll rejoin automatically when your internet is back." : connection === "server_unreachable" ? "Can't reach the meeting server. You'll rejoin automatically when it answers." : failure?.remedy ?? "Check your internet connection, then rejoin.";
11633
12566
  notice = "disconnected";
11634
12567
  actionsNode = /* @__PURE__ */ jsxs3(Fragment, { children: [
11635
12568
  rejoin,
@@ -12238,6 +13171,7 @@ function MeetingSkinRootBody({
12238
13171
  host.store.dispatch({ type: "reset" });
12239
13172
  }
12240
13173
  if (host.store.snapshot().phase !== "resolving") return;
13174
+ if (given?.linkRun?.reason == null && host.engine.resume(roomName)) return;
12241
13175
  const reason = given?.linkRun?.reason ?? null;
12242
13176
  if (reason !== null && given?.linkRun !== void 0) {
12243
13177
  const words = linkRunWords(given.linkRun);
@@ -12523,6 +13457,7 @@ export {
12523
13457
  CAPTION_LANGUAGES,
12524
13458
  CONSENT_NOTICE_ATTRIBUTE,
12525
13459
  DATA_TOPICS,
13460
+ DEFAULT_CONNECTION_POLICY,
12526
13461
  DEFAULT_EFFECTS,
12527
13462
  DEFAULT_PAGE_SIZE,
12528
13463
  EMPTY_COLLAB,
@@ -12534,12 +13469,14 @@ export {
12534
13469
  GUEST_RECORD_NOT_SHARED,
12535
13470
  GuestRecordAccessControl,
12536
13471
  IDLE_MEDIA,
13472
+ IDLE_MEDIA_REGION,
12537
13473
  IDLE_PHASES,
12538
13474
  IDLE_TRACK,
12539
13475
  IncomingCallCard,
12540
13476
  IncomingCallHost,
12541
13477
  JOIN_REFUSAL_REASONS,
12542
13478
  LISTEN_ONLY_NOTICE,
13479
+ LIVEKIT_BASE_DELAYS_MS,
12543
13480
  LIVE_PHASES,
12544
13481
  MEETING_INVITE_KIND,
12545
13482
  MEETING_INVITE_VERSION,
@@ -12569,6 +13506,7 @@ export {
12569
13506
  PARTICIPANT_STANDING_ATTRIBUTE,
12570
13507
  REACTION_TTL_MS,
12571
13508
  REFUSED_ABSENCE,
13509
+ RESUME_WINDOW_MS,
12572
13510
  ROLE_RANK,
12573
13511
  ROOM_EVENTS,
12574
13512
  ROOM_EVENT_PREFIX,
@@ -12599,6 +13537,7 @@ export {
12599
13537
  attendanceTotals,
12600
13538
  attendeeKey,
12601
13539
  autoAssign,
13540
+ autoRejoinDelayMs,
12602
13541
  availableActivities,
12603
13542
  breakoutRemainingMs,
12604
13543
  callInviteAction,
@@ -12610,15 +13549,19 @@ export {
12610
13549
  captionSegment,
12611
13550
  captureHeldOf,
12612
13551
  changesPatch,
13552
+ classifyDeviceFailure,
12613
13553
  clockPosition,
12614
13554
  collabFromState,
12615
13555
  computeStage,
13556
+ connectionPolicyFrom,
12616
13557
  countLabel,
12617
13558
  createCallCenter,
12618
13559
  createCallInviteHandler,
13560
+ createConnectionSupervisor,
12619
13561
  createDeviceManager,
12620
13562
  createInRoomClient,
12621
13563
  createLiveKitDriver,
13564
+ createMediaSupervisor,
12622
13565
  createMeetAi,
12623
13566
  createMeetApi,
12624
13567
  createMeetRepository,
@@ -12627,13 +13570,23 @@ export {
12627
13570
  createMeetingRecordCache,
12628
13571
  createMeetingSession,
12629
13572
  createMemoryDeviceStorage,
13573
+ createReachabilityProbe,
13574
+ createReconnectPolicy,
12630
13575
  createRecordingController,
12631
13576
  createRoomEngine,
12632
13577
  createTokenClient,
12633
13578
  createWebDeviceStorage,
12634
13579
  createWebEffectsStorage,
13580
+ createWebNetworkPort,
13581
+ createWebResumeStorage,
12635
13582
  createWebRoomTokenStorage,
12636
13583
  defaultCaptionLanguage,
13584
+ detectBrowser,
13585
+ detectOs,
13586
+ deviceChipLabel,
13587
+ deviceFixSteps,
13588
+ deviceNoticeCode,
13589
+ deviceUnavailableReason,
12637
13590
  displayNameFor,
12638
13591
  fileDownloadPath,
12639
13592
  focusFromRow,
@@ -12644,6 +13597,7 @@ export {
12644
13597
  guestRecordAccessSetting,
12645
13598
  hasEnded,
12646
13599
  hostUserIds,
13600
+ httpUrlOf,
12647
13601
  invalidResponse,
12648
13602
  isGuest,
12649
13603
  isGuestIdentity,
@@ -12657,6 +13611,7 @@ export {
12657
13611
  isRealName,
12658
13612
  isRefusedPhase,
12659
13613
  isRoomEventTopic,
13614
+ isUnannouncedDeparture,
12660
13615
  isWaitingPhase,
12661
13616
  learnName,
12662
13617
  learnNames,
@@ -12679,6 +13634,7 @@ export {
12679
13634
  meetingTopic,
12680
13635
  meetingWhen,
12681
13636
  mergeCaption,
13637
+ mergeGhosts,
12682
13638
  mergeRecord,
12683
13639
  microphoneNoticeCode,
12684
13640
  moveToRoom,
@@ -12787,6 +13743,7 @@ export {
12787
13743
  useTrack,
12788
13744
  userTopic,
12789
13745
  wasRefused,
13746
+ webMediaPlatform,
12790
13747
  withItem,
12791
13748
  wrapUpStatus
12792
13749
  };