@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/CHANGELOG.md +6 -0
- package/dist/{collab-CUxjUtrb.d.cts → collab-XY6Xj1dG.d.cts} +5 -0
- package/dist/{collab-CUxjUtrb.d.ts → collab-XY6Xj1dG.d.ts} +5 -0
- package/dist/{components-DPdU3POT.d.cts → components-4f9Mf8fz.d.cts} +1 -1
- package/dist/{components-CUPzD0y3.d.ts → components-Cwep2NYU.d.ts} +1 -1
- package/dist/core.cjs +893 -43
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +549 -169
- package/dist/core.d.ts +549 -169
- package/dist/core.js +893 -43
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +893 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +893 -43
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1005 -48
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +6 -12
- package/dist/react.d.ts +6 -12
- package/dist/react.js +1005 -48
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +263 -27
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +263 -27
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +46 -0
- package/package.json +4 -4
package/dist/react.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(react_exports, {
|
|
|
38
38
|
CAPTION_LANGUAGES: () => CAPTION_LANGUAGES,
|
|
39
39
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
40
40
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
41
|
+
DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
|
|
41
42
|
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
42
43
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
43
44
|
EMPTY_COLLAB: () => EMPTY_COLLAB,
|
|
@@ -49,12 +50,14 @@ __export(react_exports, {
|
|
|
49
50
|
GUEST_RECORD_NOT_SHARED: () => GUEST_RECORD_NOT_SHARED,
|
|
50
51
|
GuestRecordAccessControl: () => GuestRecordAccessControl,
|
|
51
52
|
IDLE_MEDIA: () => IDLE_MEDIA,
|
|
53
|
+
IDLE_MEDIA_REGION: () => IDLE_MEDIA_REGION,
|
|
52
54
|
IDLE_PHASES: () => IDLE_PHASES,
|
|
53
55
|
IDLE_TRACK: () => IDLE_TRACK,
|
|
54
56
|
IncomingCallCard: () => IncomingCallCard,
|
|
55
57
|
IncomingCallHost: () => IncomingCallHost,
|
|
56
58
|
JOIN_REFUSAL_REASONS: () => JOIN_REFUSAL_REASONS,
|
|
57
59
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
60
|
+
LIVEKIT_BASE_DELAYS_MS: () => LIVEKIT_BASE_DELAYS_MS,
|
|
58
61
|
LIVE_PHASES: () => LIVE_PHASES,
|
|
59
62
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
60
63
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
@@ -84,6 +87,7 @@ __export(react_exports, {
|
|
|
84
87
|
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
85
88
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
86
89
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
90
|
+
RESUME_WINDOW_MS: () => RESUME_WINDOW_MS,
|
|
87
91
|
ROLE_RANK: () => ROLE_RANK,
|
|
88
92
|
ROOM_EVENTS: () => ROOM_EVENTS,
|
|
89
93
|
ROOM_EVENT_PREFIX: () => ROOM_EVENT_PREFIX,
|
|
@@ -114,6 +118,7 @@ __export(react_exports, {
|
|
|
114
118
|
attendanceTotals: () => attendanceTotals,
|
|
115
119
|
attendeeKey: () => attendeeKey,
|
|
116
120
|
autoAssign: () => autoAssign,
|
|
121
|
+
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
117
122
|
availableActivities: () => availableActivities,
|
|
118
123
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
119
124
|
callInviteAction: () => callInviteAction,
|
|
@@ -125,15 +130,19 @@ __export(react_exports, {
|
|
|
125
130
|
captionSegment: () => captionSegment,
|
|
126
131
|
captureHeldOf: () => captureHeldOf,
|
|
127
132
|
changesPatch: () => changesPatch,
|
|
133
|
+
classifyDeviceFailure: () => classifyDeviceFailure,
|
|
128
134
|
clockPosition: () => clockPosition,
|
|
129
135
|
collabFromState: () => collabFromState,
|
|
130
136
|
computeStage: () => computeStage,
|
|
137
|
+
connectionPolicyFrom: () => connectionPolicyFrom,
|
|
131
138
|
countLabel: () => countLabel,
|
|
132
139
|
createCallCenter: () => createCallCenter,
|
|
133
140
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
141
|
+
createConnectionSupervisor: () => createConnectionSupervisor,
|
|
134
142
|
createDeviceManager: () => createDeviceManager,
|
|
135
143
|
createInRoomClient: () => createInRoomClient,
|
|
136
144
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
145
|
+
createMediaSupervisor: () => createMediaSupervisor,
|
|
137
146
|
createMeetAi: () => createMeetAi,
|
|
138
147
|
createMeetApi: () => createMeetApi,
|
|
139
148
|
createMeetRepository: () => createMeetRepository,
|
|
@@ -142,13 +151,23 @@ __export(react_exports, {
|
|
|
142
151
|
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
143
152
|
createMeetingSession: () => createMeetingSession,
|
|
144
153
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
154
|
+
createReachabilityProbe: () => createReachabilityProbe,
|
|
155
|
+
createReconnectPolicy: () => createReconnectPolicy,
|
|
145
156
|
createRecordingController: () => createRecordingController,
|
|
146
157
|
createRoomEngine: () => createRoomEngine,
|
|
147
158
|
createTokenClient: () => createTokenClient,
|
|
148
159
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
149
160
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
161
|
+
createWebNetworkPort: () => createWebNetworkPort,
|
|
162
|
+
createWebResumeStorage: () => createWebResumeStorage,
|
|
150
163
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
151
164
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
165
|
+
detectBrowser: () => detectBrowser,
|
|
166
|
+
detectOs: () => detectOs,
|
|
167
|
+
deviceChipLabel: () => deviceChipLabel,
|
|
168
|
+
deviceFixSteps: () => deviceFixSteps,
|
|
169
|
+
deviceNoticeCode: () => deviceNoticeCode,
|
|
170
|
+
deviceUnavailableReason: () => deviceUnavailableReason,
|
|
152
171
|
displayNameFor: () => displayNameFor,
|
|
153
172
|
fileDownloadPath: () => fileDownloadPath,
|
|
154
173
|
focusFromRow: () => focusFromRow,
|
|
@@ -159,6 +178,7 @@ __export(react_exports, {
|
|
|
159
178
|
guestRecordAccessSetting: () => guestRecordAccessSetting,
|
|
160
179
|
hasEnded: () => hasEnded,
|
|
161
180
|
hostUserIds: () => hostUserIds,
|
|
181
|
+
httpUrlOf: () => httpUrlOf,
|
|
162
182
|
invalidResponse: () => invalidResponse,
|
|
163
183
|
isGuest: () => isGuest,
|
|
164
184
|
isGuestIdentity: () => isGuestIdentity,
|
|
@@ -172,6 +192,7 @@ __export(react_exports, {
|
|
|
172
192
|
isRealName: () => isRealName,
|
|
173
193
|
isRefusedPhase: () => isRefusedPhase,
|
|
174
194
|
isRoomEventTopic: () => isRoomEventTopic,
|
|
195
|
+
isUnannouncedDeparture: () => isUnannouncedDeparture,
|
|
175
196
|
isWaitingPhase: () => isWaitingPhase,
|
|
176
197
|
learnName: () => learnName,
|
|
177
198
|
learnNames: () => learnNames,
|
|
@@ -194,6 +215,7 @@ __export(react_exports, {
|
|
|
194
215
|
meetingTopic: () => meetingTopic,
|
|
195
216
|
meetingWhen: () => meetingWhen,
|
|
196
217
|
mergeCaption: () => mergeCaption,
|
|
218
|
+
mergeGhosts: () => mergeGhosts,
|
|
197
219
|
mergeRecord: () => mergeRecord,
|
|
198
220
|
microphoneNoticeCode: () => microphoneNoticeCode,
|
|
199
221
|
moveToRoom: () => moveToRoom,
|
|
@@ -302,11 +324,358 @@ __export(react_exports, {
|
|
|
302
324
|
useTrack: () => useTrack,
|
|
303
325
|
userTopic: () => userTopic,
|
|
304
326
|
wasRefused: () => wasRefused,
|
|
327
|
+
webMediaPlatform: () => webMediaPlatform,
|
|
305
328
|
withItem: () => withItem,
|
|
306
329
|
wrapUpStatus: () => wrapUpStatus
|
|
307
330
|
});
|
|
308
331
|
module.exports = __toCommonJS(react_exports);
|
|
309
332
|
|
|
333
|
+
// src/core/connection-supervisor.ts
|
|
334
|
+
var DEFAULT_CONNECTION_POLICY = {
|
|
335
|
+
bannerDelayMs: 2e3,
|
|
336
|
+
autoRejoin: true,
|
|
337
|
+
autoRejoinMaxAttempts: 5,
|
|
338
|
+
reconnectGraceSeconds: 30
|
|
339
|
+
};
|
|
340
|
+
function clampInt(value, min, max, fallback) {
|
|
341
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
342
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
343
|
+
}
|
|
344
|
+
function connectionPolicyFrom(raw) {
|
|
345
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
|
|
346
|
+
const row = raw;
|
|
347
|
+
const d = DEFAULT_CONNECTION_POLICY;
|
|
348
|
+
return {
|
|
349
|
+
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
350
|
+
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
351
|
+
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
352
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
356
|
+
function createReconnectPolicy(options = {}) {
|
|
357
|
+
const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
|
|
358
|
+
const giveUpMs = options.giveUpMs ?? 45e3;
|
|
359
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
360
|
+
const random = options.random ?? Math.random;
|
|
361
|
+
return {
|
|
362
|
+
nextRetryDelayInMs({ retryCount, elapsedMs }) {
|
|
363
|
+
if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
|
|
364
|
+
const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
|
|
365
|
+
const spread = Math.min(2e3, Math.max(500, base / 2));
|
|
366
|
+
return Math.round(base + random() * spread);
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function createWebNetworkPort() {
|
|
371
|
+
const scope = globalThis;
|
|
372
|
+
return {
|
|
373
|
+
online: () => scope.navigator?.onLine !== false,
|
|
374
|
+
subscribe(listener) {
|
|
375
|
+
if (typeof scope.addEventListener !== "function") return () => void 0;
|
|
376
|
+
const on = () => listener(true);
|
|
377
|
+
const off = () => listener(false);
|
|
378
|
+
scope.addEventListener("online", on);
|
|
379
|
+
scope.addEventListener("offline", off);
|
|
380
|
+
return () => {
|
|
381
|
+
scope.removeEventListener?.("online", on);
|
|
382
|
+
scope.removeEventListener?.("offline", off);
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
function createReachabilityProbe(urls, timeoutMs = 4e3) {
|
|
388
|
+
return async () => {
|
|
389
|
+
const targets = urls().filter((url) => url.length > 0);
|
|
390
|
+
if (targets.length === 0 || typeof fetch !== "function") return true;
|
|
391
|
+
const one = async (url) => {
|
|
392
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
393
|
+
const timer = setTimeout(() => controller?.abort(), timeoutMs);
|
|
394
|
+
try {
|
|
395
|
+
await fetch(url, {
|
|
396
|
+
method: "GET",
|
|
397
|
+
mode: "no-cors",
|
|
398
|
+
cache: "no-store",
|
|
399
|
+
credentials: "omit",
|
|
400
|
+
...controller !== null ? { signal: controller.signal } : {}
|
|
401
|
+
});
|
|
402
|
+
return true;
|
|
403
|
+
} catch {
|
|
404
|
+
return false;
|
|
405
|
+
} finally {
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
const answers = await Promise.all(targets.map(one));
|
|
410
|
+
return answers.every(Boolean);
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function httpUrlOf(serverUrl) {
|
|
414
|
+
return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
|
|
415
|
+
}
|
|
416
|
+
var RESUME_WINDOW_MS = 15 * 6e4;
|
|
417
|
+
var RESUME_KEY = "mx-meet:resume:";
|
|
418
|
+
function createWebResumeStorage() {
|
|
419
|
+
const storage = globalThis.sessionStorage;
|
|
420
|
+
if (storage === void 0) return void 0;
|
|
421
|
+
return {
|
|
422
|
+
load(roomName) {
|
|
423
|
+
try {
|
|
424
|
+
const raw = storage.getItem(RESUME_KEY + roomName);
|
|
425
|
+
if (raw === null) return null;
|
|
426
|
+
const parsed = JSON.parse(raw);
|
|
427
|
+
return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
|
|
428
|
+
} catch {
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
save(roomName, marker) {
|
|
433
|
+
try {
|
|
434
|
+
storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
|
|
435
|
+
} catch {
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
clear(roomName) {
|
|
439
|
+
try {
|
|
440
|
+
storage.removeItem(RESUME_KEY + roomName);
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
|
|
447
|
+
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
448
|
+
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
449
|
+
return Math.round(base * (0.5 + random()));
|
|
450
|
+
}
|
|
451
|
+
function createConnectionSupervisor(options) {
|
|
452
|
+
const { store, network } = options;
|
|
453
|
+
const now = options.now ?? (() => Date.now());
|
|
454
|
+
const random = options.random ?? Math.random;
|
|
455
|
+
const probeEveryMs = options.probeEveryMs ?? 3e3;
|
|
456
|
+
let driverRegion = null;
|
|
457
|
+
let probeTimer = null;
|
|
458
|
+
let probing = false;
|
|
459
|
+
let bannerTimer = null;
|
|
460
|
+
let rejoinTimer = null;
|
|
461
|
+
let rejoinAttempts = 0;
|
|
462
|
+
let lastPhase = store.snapshot().phase;
|
|
463
|
+
let lastSavedAt = 0;
|
|
464
|
+
let disposed = false;
|
|
465
|
+
const inCall = () => store.snapshot().phase === "in_call";
|
|
466
|
+
const dropped = () => store.snapshot().phase === "disconnected";
|
|
467
|
+
function setRegion(next) {
|
|
468
|
+
if (store.snapshot().connection !== next) store.patch({ connection: next });
|
|
469
|
+
}
|
|
470
|
+
function regionFor(reachable) {
|
|
471
|
+
if (!network.online()) return "offline";
|
|
472
|
+
if (reachable === false) return "server_unreachable";
|
|
473
|
+
if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
|
|
474
|
+
return driverRegion ?? "stable";
|
|
475
|
+
}
|
|
476
|
+
function needsWatch() {
|
|
477
|
+
const snap = store.snapshot();
|
|
478
|
+
if (dropped()) return true;
|
|
479
|
+
return inCall() && (driverRegion !== null || snap.connection !== "stable");
|
|
480
|
+
}
|
|
481
|
+
function stopProbe() {
|
|
482
|
+
if (probeTimer !== null) clearTimeout(probeTimer);
|
|
483
|
+
probeTimer = null;
|
|
484
|
+
}
|
|
485
|
+
function scheduleProbe(delayMs) {
|
|
486
|
+
if (disposed || probeTimer !== null) return;
|
|
487
|
+
probeTimer = setTimeout(() => {
|
|
488
|
+
probeTimer = null;
|
|
489
|
+
void runProbe();
|
|
490
|
+
}, delayMs);
|
|
491
|
+
}
|
|
492
|
+
async function runProbe() {
|
|
493
|
+
if (disposed || probing) return;
|
|
494
|
+
if (!needsWatch()) return;
|
|
495
|
+
if (!network.online()) {
|
|
496
|
+
setRegion("offline");
|
|
497
|
+
scheduleProbe(probeEveryMs);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
probing = true;
|
|
501
|
+
let reachable = false;
|
|
502
|
+
try {
|
|
503
|
+
reachable = await options.probe();
|
|
504
|
+
} catch {
|
|
505
|
+
reachable = false;
|
|
506
|
+
} finally {
|
|
507
|
+
probing = false;
|
|
508
|
+
}
|
|
509
|
+
if (disposed) return;
|
|
510
|
+
if (inCall() || dropped()) setRegion(regionFor(reachable));
|
|
511
|
+
if (dropped() && reachable) maybeRejoin();
|
|
512
|
+
if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
|
|
513
|
+
}
|
|
514
|
+
function syncBanner() {
|
|
515
|
+
const snap = store.snapshot();
|
|
516
|
+
const unstable = snap.phase === "in_call" && snap.connection !== "stable";
|
|
517
|
+
if (!unstable) {
|
|
518
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
519
|
+
bannerTimer = null;
|
|
520
|
+
if (snap.connectionBanner) store.patch({ connectionBanner: false });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (snap.connectionBanner || bannerTimer !== null) return;
|
|
524
|
+
const delay = options.policy().bannerDelayMs;
|
|
525
|
+
if (delay <= 0) {
|
|
526
|
+
store.patch({ connectionBanner: true });
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
bannerTimer = setTimeout(() => {
|
|
530
|
+
bannerTimer = null;
|
|
531
|
+
const latest = store.snapshot();
|
|
532
|
+
if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
|
|
533
|
+
store.patch({ connectionBanner: true });
|
|
534
|
+
}
|
|
535
|
+
}, delay);
|
|
536
|
+
}
|
|
537
|
+
function stopRejoin() {
|
|
538
|
+
if (rejoinTimer !== null) clearTimeout(rejoinTimer);
|
|
539
|
+
rejoinTimer = null;
|
|
540
|
+
}
|
|
541
|
+
function maybeRejoin() {
|
|
542
|
+
if (disposed || rejoinTimer !== null || !dropped()) return;
|
|
543
|
+
const policy = options.policy();
|
|
544
|
+
if (!policy.autoRejoin) return;
|
|
545
|
+
if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
|
|
546
|
+
if (!network.online()) return;
|
|
547
|
+
const attempt = rejoinAttempts;
|
|
548
|
+
rejoinTimer = setTimeout(() => {
|
|
549
|
+
rejoinTimer = null;
|
|
550
|
+
if (disposed || !dropped() || !network.online()) return;
|
|
551
|
+
rejoinAttempts = attempt + 1;
|
|
552
|
+
options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
|
|
553
|
+
void options.autoRejoin().catch(() => void 0);
|
|
554
|
+
}, autoRejoinDelayMs(attempt, random));
|
|
555
|
+
}
|
|
556
|
+
function saveMarker(force) {
|
|
557
|
+
const resume = options.resume;
|
|
558
|
+
const snap = store.snapshot();
|
|
559
|
+
if (resume === void 0 || snap.roomName === null) return;
|
|
560
|
+
const at = now();
|
|
561
|
+
if (!force && at - lastSavedAt < 1e4) return;
|
|
562
|
+
const request = options.currentRequest();
|
|
563
|
+
if (request === null) return;
|
|
564
|
+
lastSavedAt = at;
|
|
565
|
+
resume.save(snap.roomName, { request, at });
|
|
566
|
+
}
|
|
567
|
+
const unsubscribeStore = store.subscribe(() => {
|
|
568
|
+
if (disposed) return;
|
|
569
|
+
const snap = store.snapshot();
|
|
570
|
+
const phase = snap.phase;
|
|
571
|
+
const changed = phase !== lastPhase;
|
|
572
|
+
lastPhase = phase;
|
|
573
|
+
if (changed) {
|
|
574
|
+
if (phase === "in_call") {
|
|
575
|
+
rejoinAttempts = 0;
|
|
576
|
+
stopRejoin();
|
|
577
|
+
saveMarker(true);
|
|
578
|
+
} else if (phase === "disconnected") {
|
|
579
|
+
driverRegion = null;
|
|
580
|
+
saveMarker(true);
|
|
581
|
+
stopProbe();
|
|
582
|
+
scheduleProbe(0);
|
|
583
|
+
} else {
|
|
584
|
+
stopRejoin();
|
|
585
|
+
if (phase !== "joining") driverRegion = null;
|
|
586
|
+
if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
|
|
587
|
+
}
|
|
588
|
+
} else if (phase === "in_call" || phase === "disconnected") {
|
|
589
|
+
saveMarker(false);
|
|
590
|
+
}
|
|
591
|
+
syncBanner();
|
|
592
|
+
});
|
|
593
|
+
const unsubscribeNetwork = network.subscribe((online) => {
|
|
594
|
+
if (disposed) return;
|
|
595
|
+
if (!online) {
|
|
596
|
+
if (inCall() || dropped()) setRegion("offline");
|
|
597
|
+
stopRejoin();
|
|
598
|
+
stopProbe();
|
|
599
|
+
if (needsWatch()) scheduleProbe(probeEveryMs);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
stopProbe();
|
|
603
|
+
if (needsWatch()) scheduleProbe(0);
|
|
604
|
+
});
|
|
605
|
+
return {
|
|
606
|
+
driverState(state) {
|
|
607
|
+
if (disposed) return;
|
|
608
|
+
if (state === "connected") {
|
|
609
|
+
driverRegion = null;
|
|
610
|
+
if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
|
|
611
|
+
if (!needsWatch()) stopProbe();
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (state === "reconnecting" || state === "signal_reconnecting") {
|
|
615
|
+
driverRegion = state;
|
|
616
|
+
if (inCall()) {
|
|
617
|
+
setRegion(network.online() ? state : "offline");
|
|
618
|
+
stopProbe();
|
|
619
|
+
scheduleProbe(0);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
exitConnection() {
|
|
624
|
+
return network.online() ? "server_unreachable" : "offline";
|
|
625
|
+
},
|
|
626
|
+
resumable(roomName) {
|
|
627
|
+
const resume = options.resume;
|
|
628
|
+
if (resume === void 0) return null;
|
|
629
|
+
const marker = resume.load(roomName);
|
|
630
|
+
if (marker === null) return null;
|
|
631
|
+
if (now() - marker.at > RESUME_WINDOW_MS) {
|
|
632
|
+
resume.clear(roomName);
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
return marker;
|
|
636
|
+
},
|
|
637
|
+
dispose() {
|
|
638
|
+
disposed = true;
|
|
639
|
+
stopProbe();
|
|
640
|
+
stopRejoin();
|
|
641
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
642
|
+
bannerTimer = null;
|
|
643
|
+
unsubscribeStore();
|
|
644
|
+
unsubscribeNetwork();
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
|
|
649
|
+
"CLIENT_INITIATED",
|
|
650
|
+
"PARTICIPANT_REMOVED",
|
|
651
|
+
"ROOM_DELETED",
|
|
652
|
+
"ROOM_CLOSED",
|
|
653
|
+
"DUPLICATE_IDENTITY",
|
|
654
|
+
"USER_REJECTED",
|
|
655
|
+
"USER_UNAVAILABLE",
|
|
656
|
+
"SIP_TRUNK_FAILURE"
|
|
657
|
+
]);
|
|
658
|
+
function isUnannouncedDeparture(reason) {
|
|
659
|
+
return reason === null || !DELIBERATE_DEPARTURES.has(reason);
|
|
660
|
+
}
|
|
661
|
+
function mergeGhosts(live, ghosts, at) {
|
|
662
|
+
const present = new Set(live.map((p) => p.identity));
|
|
663
|
+
const kept = /* @__PURE__ */ new Map();
|
|
664
|
+
const extra = [];
|
|
665
|
+
for (const [identity, ghost] of ghosts) {
|
|
666
|
+
if (present.has(identity) || ghost.until <= at) continue;
|
|
667
|
+
kept.set(identity, ghost);
|
|
668
|
+
extra.push({
|
|
669
|
+
...ghost.participant,
|
|
670
|
+
micEnabled: false,
|
|
671
|
+
cameraEnabled: false,
|
|
672
|
+
screenSharing: false,
|
|
673
|
+
reconnecting: true
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
return { roster: [...live, ...extra], kept };
|
|
677
|
+
}
|
|
678
|
+
|
|
310
679
|
// src/react/provider.tsx
|
|
311
680
|
var import_global_slot2 = require("@ai-matrx/kit/global-slot");
|
|
312
681
|
var import_react = require("react");
|
|
@@ -703,7 +1072,8 @@ function parseRoomToken(body, roomName) {
|
|
|
703
1072
|
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
704
1073
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
705
1074
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
706
|
-
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
1075
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1076
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
707
1077
|
};
|
|
708
1078
|
}
|
|
709
1079
|
function createTokenClient(options) {
|
|
@@ -1408,6 +1778,10 @@ function nextPhase(from, event) {
|
|
|
1408
1778
|
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1409
1779
|
case "join":
|
|
1410
1780
|
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1781
|
+
case "auto_rejoin":
|
|
1782
|
+
return from === "disconnected" ? "joining" : null;
|
|
1783
|
+
case "resumed":
|
|
1784
|
+
return from === "resolving" ? "joining" : null;
|
|
1411
1785
|
case "back_to_prejoin":
|
|
1412
1786
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1413
1787
|
case "retry":
|
|
@@ -2125,13 +2499,19 @@ function recordingLabel(state) {
|
|
|
2125
2499
|
}
|
|
2126
2500
|
|
|
2127
2501
|
// src/core/attendance.ts
|
|
2502
|
+
var IDLE_MEDIA_REGION = {
|
|
2503
|
+
permission: { microphone: "unknown", camera: "unknown" },
|
|
2504
|
+
audioOutput: "ok",
|
|
2505
|
+
notice: null,
|
|
2506
|
+
offer: null
|
|
2507
|
+
};
|
|
2128
2508
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
2129
2509
|
var IDLE_MEDIA = {
|
|
2130
2510
|
microphone: IDLE_TRACK,
|
|
2131
2511
|
camera: IDLE_TRACK
|
|
2132
2512
|
};
|
|
2133
|
-
function unavailableFrom(error) {
|
|
2134
|
-
return { code: error.code, message: error.message, remedy: error.remedy };
|
|
2513
|
+
function unavailableFrom(error, reason) {
|
|
2514
|
+
return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
|
|
2135
2515
|
}
|
|
2136
2516
|
var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
|
|
2137
2517
|
function isListenOnly(media) {
|
|
@@ -3547,6 +3927,7 @@ var EMPTY = {
|
|
|
3547
3927
|
phaseNotice: null,
|
|
3548
3928
|
roomName: null,
|
|
3549
3929
|
connection: "stable",
|
|
3930
|
+
connectionBanner: false,
|
|
3550
3931
|
share: NO_SHARE,
|
|
3551
3932
|
call: null,
|
|
3552
3933
|
waiting: null,
|
|
@@ -3578,6 +3959,7 @@ var EMPTY = {
|
|
|
3578
3959
|
wrapUp: null,
|
|
3579
3960
|
noteTakerRefusal: null,
|
|
3580
3961
|
media: IDLE_MEDIA,
|
|
3962
|
+
mediaRegion: IDLE_MEDIA_REGION,
|
|
3581
3963
|
failure: null,
|
|
3582
3964
|
collab: EMPTY_COLLAB,
|
|
3583
3965
|
breakoutRoomId: null
|
|
@@ -3795,7 +4177,7 @@ function createMeetStore(initial = {}) {
|
|
|
3795
4177
|
},
|
|
3796
4178
|
setLocalTrack(kind, next) {
|
|
3797
4179
|
const current = state.media[kind];
|
|
3798
|
-
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
|
|
4180
|
+
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
3799
4181
|
return;
|
|
3800
4182
|
}
|
|
3801
4183
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
@@ -4380,6 +4762,342 @@ function createMeetSessionIdentity(options) {
|
|
|
4380
4762
|
});
|
|
4381
4763
|
}
|
|
4382
4764
|
|
|
4765
|
+
// src/core/media-supervisor.ts
|
|
4766
|
+
function classifyDeviceFailure(error, permission) {
|
|
4767
|
+
if (permission === "denied") return "denied";
|
|
4768
|
+
switch (error.code) {
|
|
4769
|
+
case "permission-denied":
|
|
4770
|
+
if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
|
|
4771
|
+
return "denied";
|
|
4772
|
+
case "device-not-found":
|
|
4773
|
+
return "none";
|
|
4774
|
+
case "device-in-use":
|
|
4775
|
+
return "busy";
|
|
4776
|
+
case "insecure-context":
|
|
4777
|
+
return "blocked_site";
|
|
4778
|
+
default:
|
|
4779
|
+
return "failed";
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
function deviceNoticeCode(kind, reason) {
|
|
4783
|
+
if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
|
|
4784
|
+
return kind === "microphone" ? "mic-blocked" : "camera-blocked";
|
|
4785
|
+
}
|
|
4786
|
+
return "device-lost";
|
|
4787
|
+
}
|
|
4788
|
+
function deviceUnavailableReason(track, permission) {
|
|
4789
|
+
if (track.enabled) return null;
|
|
4790
|
+
if (permission === "denied") return "denied";
|
|
4791
|
+
const refused = track.unavailable;
|
|
4792
|
+
if (refused === null) return null;
|
|
4793
|
+
return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
|
|
4794
|
+
}
|
|
4795
|
+
function deviceChipLabel(kind, reason) {
|
|
4796
|
+
const what = kind === "microphone" ? "Mic" : "Camera";
|
|
4797
|
+
switch (reason) {
|
|
4798
|
+
case "denied":
|
|
4799
|
+
case "blocked_site":
|
|
4800
|
+
case "blocked_os":
|
|
4801
|
+
return `${what} blocked`;
|
|
4802
|
+
case "none":
|
|
4803
|
+
return kind === "microphone" ? "No microphone found" : "No camera found";
|
|
4804
|
+
case "busy":
|
|
4805
|
+
return `${what} in use by another app`;
|
|
4806
|
+
case "revoked":
|
|
4807
|
+
return `${what} access was turned off`;
|
|
4808
|
+
default:
|
|
4809
|
+
return `${what} didn't start`;
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
function deviceFixSteps(kind, reason, where) {
|
|
4813
|
+
const device = kind === "microphone" ? "Microphone" : "Camera";
|
|
4814
|
+
const lower = device.toLowerCase();
|
|
4815
|
+
switch (reason) {
|
|
4816
|
+
case "denied":
|
|
4817
|
+
case "revoked":
|
|
4818
|
+
case "blocked_site":
|
|
4819
|
+
switch (where.browser) {
|
|
4820
|
+
case "firefox":
|
|
4821
|
+
return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
|
|
4822
|
+
case "safari":
|
|
4823
|
+
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"];
|
|
4824
|
+
default:
|
|
4825
|
+
return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
|
|
4826
|
+
}
|
|
4827
|
+
case "blocked_os": {
|
|
4828
|
+
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
4829
|
+
if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
|
|
4830
|
+
if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
|
|
4831
|
+
return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
|
|
4832
|
+
}
|
|
4833
|
+
case "busy":
|
|
4834
|
+
return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
|
|
4835
|
+
case "none":
|
|
4836
|
+
return [`Connect a ${lower}`, "Press Try again"];
|
|
4837
|
+
default:
|
|
4838
|
+
return [`Pick another ${lower} in Settings`, "Press Try again"];
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
function detectBrowser(userAgent) {
|
|
4842
|
+
if (/Edg\//.test(userAgent)) return "edge";
|
|
4843
|
+
if (/Firefox\//.test(userAgent)) return "firefox";
|
|
4844
|
+
if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
|
|
4845
|
+
if (/Safari\//.test(userAgent)) return "safari";
|
|
4846
|
+
return "other";
|
|
4847
|
+
}
|
|
4848
|
+
function detectOs(userAgent, maxTouchPoints = 0) {
|
|
4849
|
+
if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
|
|
4850
|
+
if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
|
|
4851
|
+
if (/Android/.test(userAgent)) return "android";
|
|
4852
|
+
if (/Windows/.test(userAgent)) return "windows";
|
|
4853
|
+
if (/Linux|CrOS/.test(userAgent)) return "linux";
|
|
4854
|
+
return "other";
|
|
4855
|
+
}
|
|
4856
|
+
function webMediaPlatform() {
|
|
4857
|
+
const scope = globalThis;
|
|
4858
|
+
const nav = scope.navigator;
|
|
4859
|
+
if (nav === void 0) return null;
|
|
4860
|
+
const ua = nav.userAgent ?? "";
|
|
4861
|
+
const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
|
|
4862
|
+
const query = async (kind) => {
|
|
4863
|
+
try {
|
|
4864
|
+
return await nav.permissions?.query({ name: kind }) ?? null;
|
|
4865
|
+
} catch {
|
|
4866
|
+
return null;
|
|
4867
|
+
}
|
|
4868
|
+
};
|
|
4869
|
+
return {
|
|
4870
|
+
browser: detectBrowser(ua),
|
|
4871
|
+
os: detectOs(ua, nav.maxTouchPoints ?? 0),
|
|
4872
|
+
async queryPermission(kind) {
|
|
4873
|
+
return reading((await query(kind))?.state);
|
|
4874
|
+
},
|
|
4875
|
+
watchPermission(kind, onChange) {
|
|
4876
|
+
let status = null;
|
|
4877
|
+
let stopped = false;
|
|
4878
|
+
void query(kind).then((found) => {
|
|
4879
|
+
if (stopped || found === null) return;
|
|
4880
|
+
status = found;
|
|
4881
|
+
found.onchange = () => onChange(reading(found.state));
|
|
4882
|
+
});
|
|
4883
|
+
return () => {
|
|
4884
|
+
stopped = true;
|
|
4885
|
+
if (status !== null) status.onchange = null;
|
|
4886
|
+
};
|
|
4887
|
+
},
|
|
4888
|
+
async listDevices() {
|
|
4889
|
+
try {
|
|
4890
|
+
return await nav.mediaDevices?.enumerateDevices() ?? [];
|
|
4891
|
+
} catch {
|
|
4892
|
+
return [];
|
|
4893
|
+
}
|
|
4894
|
+
},
|
|
4895
|
+
onVisible(callback) {
|
|
4896
|
+
const doc = scope.document;
|
|
4897
|
+
if (doc === void 0) return () => void 0;
|
|
4898
|
+
const handler = () => {
|
|
4899
|
+
if (doc.visibilityState === "visible") callback();
|
|
4900
|
+
};
|
|
4901
|
+
doc.addEventListener("visibilitychange", handler);
|
|
4902
|
+
return () => doc.removeEventListener("visibilitychange", handler);
|
|
4903
|
+
}
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
|
|
4907
|
+
function isAlias(deviceId2) {
|
|
4908
|
+
return deviceId2 === "default" || deviceId2 === "communications" || deviceId2 === "";
|
|
4909
|
+
}
|
|
4910
|
+
function createMediaSupervisor(options) {
|
|
4911
|
+
const { store, platform } = options;
|
|
4912
|
+
let unsubscribers = [];
|
|
4913
|
+
let started = false;
|
|
4914
|
+
let noticeId = 0;
|
|
4915
|
+
let known = null;
|
|
4916
|
+
let inUse = { audioinput: null, videoinput: null };
|
|
4917
|
+
function region() {
|
|
4918
|
+
return store.snapshot().mediaRegion;
|
|
4919
|
+
}
|
|
4920
|
+
function setRegion(next) {
|
|
4921
|
+
const current = region();
|
|
4922
|
+
const merged = { ...current, ...next };
|
|
4923
|
+
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) {
|
|
4924
|
+
return;
|
|
4925
|
+
}
|
|
4926
|
+
store.patch({ mediaRegion: merged });
|
|
4927
|
+
}
|
|
4928
|
+
function setPermission(kind, reading) {
|
|
4929
|
+
const current = region().permission;
|
|
4930
|
+
if (current[kind] === reading) return;
|
|
4931
|
+
setRegion({ permission: { ...current, [kind]: reading } });
|
|
4932
|
+
const track = store.snapshot().media[kind];
|
|
4933
|
+
if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
|
|
4934
|
+
store.setLocalTrack(kind, {
|
|
4935
|
+
enabled: false,
|
|
4936
|
+
unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
|
|
4937
|
+
});
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
async function snapshotDevices() {
|
|
4941
|
+
const list = platform === null ? [] : await platform.listDevices();
|
|
4942
|
+
const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
|
|
4943
|
+
for (const device of list) {
|
|
4944
|
+
if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
|
|
4945
|
+
}
|
|
4946
|
+
return out;
|
|
4947
|
+
}
|
|
4948
|
+
function readInUse() {
|
|
4949
|
+
const driver = options.driver();
|
|
4950
|
+
if (driver === null) return;
|
|
4951
|
+
const active = driver.activeDevices();
|
|
4952
|
+
inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
|
|
4953
|
+
}
|
|
4954
|
+
async function handleDevicesChanged() {
|
|
4955
|
+
if (platform === null) return;
|
|
4956
|
+
const before = known;
|
|
4957
|
+
const now = await snapshotDevices();
|
|
4958
|
+
known = now;
|
|
4959
|
+
const driver = options.driver();
|
|
4960
|
+
if (driver === null || before === null) {
|
|
4961
|
+
readInUse();
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
for (const kind of ["microphone", "camera"]) {
|
|
4965
|
+
const deviceKind = INPUT_KIND[kind];
|
|
4966
|
+
const previous = inUse[deviceKind];
|
|
4967
|
+
const track = store.snapshot().media[kind];
|
|
4968
|
+
const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
|
|
4969
|
+
if (gone) {
|
|
4970
|
+
const from = before[deviceKind].get(previous) ?? "";
|
|
4971
|
+
const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
|
|
4972
|
+
if (remaining.length === 0) {
|
|
4973
|
+
store.setLocalTrack(kind, {
|
|
4974
|
+
enabled: false,
|
|
4975
|
+
unavailable: {
|
|
4976
|
+
code: "device-not-found",
|
|
4977
|
+
message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
|
|
4978
|
+
remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
|
|
4979
|
+
reason: "none"
|
|
4980
|
+
}
|
|
4981
|
+
});
|
|
4982
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
4983
|
+
continue;
|
|
4984
|
+
}
|
|
4985
|
+
if (kind === "camera") {
|
|
4986
|
+
const [nextId, nextLabel] = remaining[0];
|
|
4987
|
+
try {
|
|
4988
|
+
await driver.switchDevice("videoinput", nextId);
|
|
4989
|
+
if (track.enabled || track.unavailable !== null) {
|
|
4990
|
+
await driver.setCameraEnabled(true);
|
|
4991
|
+
store.setLocalTrack("camera", { enabled: true, unavailable: null });
|
|
4992
|
+
}
|
|
4993
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
|
|
4994
|
+
} catch (error) {
|
|
4995
|
+
const refused = await unavailable(error, "camera", "meet.camera(fallback)");
|
|
4996
|
+
store.setLocalTrack("camera", { enabled: false, unavailable: refused });
|
|
4997
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
4998
|
+
}
|
|
4999
|
+
} else {
|
|
5000
|
+
const active = driver.activeDevices().audioinput;
|
|
5001
|
+
const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
|
|
5002
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
|
|
5003
|
+
}
|
|
5004
|
+
continue;
|
|
5005
|
+
}
|
|
5006
|
+
const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
|
|
5007
|
+
if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
|
|
5008
|
+
setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
|
|
5009
|
+
}
|
|
5010
|
+
}
|
|
5011
|
+
readInUse();
|
|
5012
|
+
}
|
|
5013
|
+
async function unavailable(error, kind, operation) {
|
|
5014
|
+
const normalized = normalizeDeviceError(error, operation);
|
|
5015
|
+
const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
|
|
5016
|
+
if (permission !== "unknown") setPermission(kind, permission);
|
|
5017
|
+
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
5018
|
+
}
|
|
5019
|
+
return {
|
|
5020
|
+
start() {
|
|
5021
|
+
if (started || platform === null) return;
|
|
5022
|
+
started = true;
|
|
5023
|
+
for (const kind of ["microphone", "camera"]) {
|
|
5024
|
+
void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
|
|
5025
|
+
unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
|
|
5026
|
+
}
|
|
5027
|
+
unsubscribers.push(
|
|
5028
|
+
platform.onVisible(() => {
|
|
5029
|
+
const driver = options.driver();
|
|
5030
|
+
if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
|
|
5031
|
+
driver.startAudio().catch(() => void 0);
|
|
5032
|
+
})
|
|
5033
|
+
);
|
|
5034
|
+
},
|
|
5035
|
+
stop() {
|
|
5036
|
+
for (const off of unsubscribers) off();
|
|
5037
|
+
unsubscribers = [];
|
|
5038
|
+
started = false;
|
|
5039
|
+
},
|
|
5040
|
+
unavailable,
|
|
5041
|
+
onAudioPlayback(canPlay) {
|
|
5042
|
+
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
5043
|
+
},
|
|
5044
|
+
onDevicesChanged() {
|
|
5045
|
+
void handleDevicesChanged().catch((error) => {
|
|
5046
|
+
options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
|
|
5047
|
+
});
|
|
5048
|
+
},
|
|
5049
|
+
onDeviceError(error, deviceKind) {
|
|
5050
|
+
if (deviceKind === null) return;
|
|
5051
|
+
const kind = deviceKind === "audioinput" ? "microphone" : "camera";
|
|
5052
|
+
void unavailable(error, kind, `meet.${kind}`).then((refused) => {
|
|
5053
|
+
const track = store.snapshot().media[kind];
|
|
5054
|
+
if (track.enabled) return;
|
|
5055
|
+
store.setLocalTrack(kind, { enabled: false, unavailable: refused });
|
|
5056
|
+
});
|
|
5057
|
+
},
|
|
5058
|
+
onConnected() {
|
|
5059
|
+
void snapshotDevices().then((now) => {
|
|
5060
|
+
known = now;
|
|
5061
|
+
readInUse();
|
|
5062
|
+
});
|
|
5063
|
+
},
|
|
5064
|
+
onDisconnected() {
|
|
5065
|
+
known = null;
|
|
5066
|
+
inUse = { audioinput: null, videoinput: null };
|
|
5067
|
+
setRegion({ audioOutput: "ok", notice: null, offer: null });
|
|
5068
|
+
},
|
|
5069
|
+
async enableAudio() {
|
|
5070
|
+
const driver = options.driver();
|
|
5071
|
+
if (driver?.startAudio === void 0) return;
|
|
5072
|
+
try {
|
|
5073
|
+
await driver.startAudio();
|
|
5074
|
+
setRegion({ audioOutput: "ok" });
|
|
5075
|
+
} catch (error) {
|
|
5076
|
+
options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
|
|
5077
|
+
}
|
|
5078
|
+
},
|
|
5079
|
+
async acceptOffer() {
|
|
5080
|
+
const offer = region().offer;
|
|
5081
|
+
const driver = options.driver();
|
|
5082
|
+
setRegion({ offer: null });
|
|
5083
|
+
if (offer === null || driver === null) return;
|
|
5084
|
+
try {
|
|
5085
|
+
await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
|
|
5086
|
+
readInUse();
|
|
5087
|
+
} catch (error) {
|
|
5088
|
+
const refused = await unavailable(error, offer.kind, "meet.switchDevice");
|
|
5089
|
+
options.report("warn", refused.message, refused.remedy);
|
|
5090
|
+
}
|
|
5091
|
+
},
|
|
5092
|
+
dismissOffer() {
|
|
5093
|
+
setRegion({ offer: null });
|
|
5094
|
+
},
|
|
5095
|
+
dismissNotice() {
|
|
5096
|
+
setRegion({ notice: null });
|
|
5097
|
+
}
|
|
5098
|
+
};
|
|
5099
|
+
}
|
|
5100
|
+
|
|
4383
5101
|
// src/core/room-events.ts
|
|
4384
5102
|
var RECORDING_STATES = [
|
|
4385
5103
|
"idle",
|
|
@@ -4826,6 +5544,18 @@ function createRoomEngine(options) {
|
|
|
4826
5544
|
let disposed = false;
|
|
4827
5545
|
let moving = false;
|
|
4828
5546
|
const confirmingEnds = /* @__PURE__ */ new WeakSet();
|
|
5547
|
+
let lastServerUrl = null;
|
|
5548
|
+
let connectionPolicy = DEFAULT_CONNECTION_POLICY;
|
|
5549
|
+
let ghosts = /* @__PURE__ */ new Map();
|
|
5550
|
+
let ghostTimer = null;
|
|
5551
|
+
let liveRoster = [];
|
|
5552
|
+
const departures = /* @__PURE__ */ new Map();
|
|
5553
|
+
function requestAsNow() {
|
|
5554
|
+
if (lastRequest === null) return null;
|
|
5555
|
+
const media2 = store.snapshot().media;
|
|
5556
|
+
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
5557
|
+
return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
|
|
5558
|
+
}
|
|
4829
5559
|
async function dropRoom() {
|
|
4830
5560
|
const target = driver;
|
|
4831
5561
|
driver = null;
|
|
@@ -4835,7 +5565,9 @@ function createRoomEngine(options) {
|
|
|
4835
5565
|
}
|
|
4836
5566
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
4837
5567
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5568
|
+
media.onDisconnected();
|
|
4838
5569
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
5570
|
+
clearGhosts();
|
|
4839
5571
|
await target?.disconnect();
|
|
4840
5572
|
}
|
|
4841
5573
|
async function leaveRoom() {
|
|
@@ -4899,11 +5631,26 @@ function createRoomEngine(options) {
|
|
|
4899
5631
|
report("error", normalized.message, normalized.remedy);
|
|
4900
5632
|
return normalized;
|
|
4901
5633
|
}
|
|
5634
|
+
const media = createMediaSupervisor({
|
|
5635
|
+
store,
|
|
5636
|
+
platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
|
|
5637
|
+
driver: () => driver,
|
|
5638
|
+
report
|
|
5639
|
+
});
|
|
4902
5640
|
const handlers = {
|
|
5641
|
+
audioPlayback(canPlay) {
|
|
5642
|
+
media.onAudioPlayback(canPlay);
|
|
5643
|
+
},
|
|
5644
|
+
devicesChanged() {
|
|
5645
|
+
media.onDevicesChanged();
|
|
5646
|
+
},
|
|
5647
|
+
mediaDeviceError(error, kind) {
|
|
5648
|
+
media.onDeviceError(error, kind);
|
|
5649
|
+
},
|
|
4903
5650
|
participants(list) {
|
|
4904
5651
|
const joinedAt = new Date(now()).toISOString();
|
|
4905
5652
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
4906
|
-
|
|
5653
|
+
projectRoster(list, joinedAt);
|
|
4907
5654
|
const snapshot = store.snapshot();
|
|
4908
5655
|
const newcomer = snapshot.participants.some(
|
|
4909
5656
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -4915,6 +5662,9 @@ function createRoomEngine(options) {
|
|
|
4915
5662
|
);
|
|
4916
5663
|
}
|
|
4917
5664
|
},
|
|
5665
|
+
participantLeft(who, reason) {
|
|
5666
|
+
departures.set(who, reason);
|
|
5667
|
+
},
|
|
4918
5668
|
roomMetadata(metadata) {
|
|
4919
5669
|
let spotlight = null;
|
|
4920
5670
|
if (metadata.length > 0) {
|
|
@@ -4941,12 +5691,8 @@ function createRoomEngine(options) {
|
|
|
4941
5691
|
store.setQuality(asParticipantIdentity(who), value);
|
|
4942
5692
|
},
|
|
4943
5693
|
connectionState(state) {
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
store.dispatch({ type: "connected" });
|
|
4947
|
-
} else if (state === "reconnecting") {
|
|
4948
|
-
store.patch({ connection: "reconnecting" });
|
|
4949
|
-
}
|
|
5694
|
+
supervisor.driverState(state);
|
|
5695
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
4950
5696
|
},
|
|
4951
5697
|
transcription(event) {
|
|
4952
5698
|
const snapshot = store.snapshot();
|
|
@@ -5079,6 +5825,7 @@ function createRoomEngine(options) {
|
|
|
5079
5825
|
};
|
|
5080
5826
|
async function resolveExitAndApply(hint) {
|
|
5081
5827
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
5828
|
+
lastRequest = requestAsNow();
|
|
5082
5829
|
await dropRoom();
|
|
5083
5830
|
let server;
|
|
5084
5831
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5092,7 +5839,7 @@ function createRoomEngine(options) {
|
|
|
5092
5839
|
}
|
|
5093
5840
|
if (disposed) return;
|
|
5094
5841
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5095
|
-
store.patch({ connection: resolved.connection });
|
|
5842
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
5096
5843
|
if (resolved.to === "waiting:knocking") {
|
|
5097
5844
|
store.patch({
|
|
5098
5845
|
waiting: {
|
|
@@ -5124,6 +5871,8 @@ function createRoomEngine(options) {
|
|
|
5124
5871
|
...request.observe === true ? { observe: true } : {}
|
|
5125
5872
|
});
|
|
5126
5873
|
current = token;
|
|
5874
|
+
lastServerUrl = token.serverUrl;
|
|
5875
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
5127
5876
|
store.patch({
|
|
5128
5877
|
localIdentity: token.identity,
|
|
5129
5878
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -5163,6 +5912,7 @@ function createRoomEngine(options) {
|
|
|
5163
5912
|
store.dispatch({ type: "connected" });
|
|
5164
5913
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5165
5914
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
5915
|
+
media.start();
|
|
5166
5916
|
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
5167
5917
|
store.patch({ effects });
|
|
5168
5918
|
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
@@ -5187,6 +5937,7 @@ function createRoomEngine(options) {
|
|
|
5187
5937
|
if (request.microphone !== false) {
|
|
5188
5938
|
await enableTrack(created, "microphone", true);
|
|
5189
5939
|
}
|
|
5940
|
+
media.onConnected();
|
|
5190
5941
|
}
|
|
5191
5942
|
async function enableTrack(target, kind, enabled) {
|
|
5192
5943
|
try {
|
|
@@ -5196,10 +5947,10 @@ function createRoomEngine(options) {
|
|
|
5196
5947
|
store.setLocalTrack(kind, state);
|
|
5197
5948
|
return state;
|
|
5198
5949
|
} catch (error) {
|
|
5199
|
-
const
|
|
5200
|
-
const state = { enabled: false, unavailable:
|
|
5950
|
+
const refused = await media.unavailable(error, kind, `meet.${kind}`);
|
|
5951
|
+
const state = { enabled: false, unavailable: refused };
|
|
5201
5952
|
store.setLocalTrack(kind, state);
|
|
5202
|
-
report("warn",
|
|
5953
|
+
report("warn", refused.message, refused.remedy);
|
|
5203
5954
|
return state;
|
|
5204
5955
|
}
|
|
5205
5956
|
}
|
|
@@ -5228,7 +5979,59 @@ function createRoomEngine(options) {
|
|
|
5228
5979
|
throw normalized;
|
|
5229
5980
|
}
|
|
5230
5981
|
}
|
|
5231
|
-
|
|
5982
|
+
function projectRoster(list, joinedAt) {
|
|
5983
|
+
const at = now();
|
|
5984
|
+
const previous = new Map(liveRoster.map((p) => [p.identity, p]));
|
|
5985
|
+
const present = new Set(list.map((p) => p.identity));
|
|
5986
|
+
const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
|
|
5987
|
+
for (const [who, reason] of departures) {
|
|
5988
|
+
const last = previous.get(who);
|
|
5989
|
+
if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
|
|
5990
|
+
ghosts.set(who, { participant: last, until: at + graceMs });
|
|
5991
|
+
}
|
|
5992
|
+
}
|
|
5993
|
+
departures.clear();
|
|
5994
|
+
liveRoster = list;
|
|
5995
|
+
const merged = mergeGhosts(list, ghosts, at);
|
|
5996
|
+
ghosts = merged.kept;
|
|
5997
|
+
store.setParticipants(
|
|
5998
|
+
merged.roster.map((entry) => {
|
|
5999
|
+
const projected = projectParticipant(entry, joinedAt);
|
|
6000
|
+
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6001
|
+
})
|
|
6002
|
+
);
|
|
6003
|
+
scheduleGhostExpiry();
|
|
6004
|
+
}
|
|
6005
|
+
function scheduleGhostExpiry() {
|
|
6006
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6007
|
+
ghostTimer = null;
|
|
6008
|
+
if (ghosts.size === 0 || disposed) return;
|
|
6009
|
+
const next = Math.min(...[...ghosts.values()].map((g) => g.until));
|
|
6010
|
+
ghostTimer = setTimeout(() => {
|
|
6011
|
+
ghostTimer = null;
|
|
6012
|
+
if (disposed || driver === null) return;
|
|
6013
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
6014
|
+
}, Math.max(0, next - now()) + 50);
|
|
6015
|
+
}
|
|
6016
|
+
function clearGhosts() {
|
|
6017
|
+
ghosts = /* @__PURE__ */ new Map();
|
|
6018
|
+
departures.clear();
|
|
6019
|
+
liveRoster = [];
|
|
6020
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6021
|
+
ghostTimer = null;
|
|
6022
|
+
}
|
|
6023
|
+
const supervisor = createConnectionSupervisor({
|
|
6024
|
+
store,
|
|
6025
|
+
network: options.connection?.network ?? createWebNetworkPort(),
|
|
6026
|
+
probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
|
|
6027
|
+
policy: () => connectionPolicy,
|
|
6028
|
+
autoRejoin: () => engine.autoRejoin(),
|
|
6029
|
+
currentRequest: requestAsNow,
|
|
6030
|
+
resume: options.connection?.resume,
|
|
6031
|
+
now,
|
|
6032
|
+
report: (level, message) => report(level, message)
|
|
6033
|
+
});
|
|
6034
|
+
const engine = {
|
|
5232
6035
|
join(request) {
|
|
5233
6036
|
if (joining !== null) return joining;
|
|
5234
6037
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -5273,6 +6076,35 @@ function createRoomEngine(options) {
|
|
|
5273
6076
|
});
|
|
5274
6077
|
return joining;
|
|
5275
6078
|
},
|
|
6079
|
+
async autoRejoin() {
|
|
6080
|
+
const request = lastRequest;
|
|
6081
|
+
if (request === null || joining !== null) return joining ?? void 0;
|
|
6082
|
+
if (!store.dispatch({ type: "auto_rejoin" })) return;
|
|
6083
|
+
tokens.invalidate(request.roomName);
|
|
6084
|
+
current = null;
|
|
6085
|
+
joining = connect(request).catch((error) => {
|
|
6086
|
+
throw failJoin(error, "meet.autoRejoin");
|
|
6087
|
+
}).finally(() => {
|
|
6088
|
+
joining = null;
|
|
6089
|
+
});
|
|
6090
|
+
return joining;
|
|
6091
|
+
},
|
|
6092
|
+
resume(roomName) {
|
|
6093
|
+
const marker = supervisor.resumable(roomName);
|
|
6094
|
+
if (marker === null || joining !== null) return false;
|
|
6095
|
+
if (!store.dispatch({ type: "resumed" })) return false;
|
|
6096
|
+
lastRequest = { ...marker.request, roomName };
|
|
6097
|
+
report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
|
|
6098
|
+
tokens.invalidate(roomName);
|
|
6099
|
+
current = null;
|
|
6100
|
+
joining = connect(lastRequest).catch((error) => {
|
|
6101
|
+
throw failJoin(error, "meet.resume");
|
|
6102
|
+
}).finally(() => {
|
|
6103
|
+
joining = null;
|
|
6104
|
+
});
|
|
6105
|
+
joining.catch(() => void 0);
|
|
6106
|
+
return true;
|
|
6107
|
+
},
|
|
5276
6108
|
async moveToBreakout(roomId) {
|
|
5277
6109
|
const request = lastRequest;
|
|
5278
6110
|
if (request === null || driver === null) {
|
|
@@ -5427,6 +6259,10 @@ function createRoomEngine(options) {
|
|
|
5427
6259
|
if (driver === null) return;
|
|
5428
6260
|
await driver.switchDevice(kind, deviceId2);
|
|
5429
6261
|
},
|
|
6262
|
+
enableAudio: () => media.enableAudio(),
|
|
6263
|
+
acceptDeviceOffer: () => media.acceptOffer(),
|
|
6264
|
+
dismissDeviceOffer: () => media.dismissOffer(),
|
|
6265
|
+
dismissDeviceNotice: () => media.dismissNotice(),
|
|
5430
6266
|
pin(who) {
|
|
5431
6267
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
5432
6268
|
},
|
|
@@ -5440,6 +6276,9 @@ function createRoomEngine(options) {
|
|
|
5440
6276
|
token: () => current,
|
|
5441
6277
|
async dispose() {
|
|
5442
6278
|
disposed = true;
|
|
6279
|
+
supervisor.dispose();
|
|
6280
|
+
media.stop();
|
|
6281
|
+
clearGhosts();
|
|
5443
6282
|
if (driver !== null) {
|
|
5444
6283
|
const target = driver;
|
|
5445
6284
|
driver = null;
|
|
@@ -5448,6 +6287,7 @@ function createRoomEngine(options) {
|
|
|
5448
6287
|
store.reset();
|
|
5449
6288
|
}
|
|
5450
6289
|
};
|
|
6290
|
+
return engine;
|
|
5451
6291
|
}
|
|
5452
6292
|
function canModerate(participant) {
|
|
5453
6293
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -5758,8 +6598,11 @@ function mapConnectionState(state) {
|
|
|
5758
6598
|
case "connecting":
|
|
5759
6599
|
return "connecting";
|
|
5760
6600
|
case "reconnecting":
|
|
5761
|
-
case "signalReconnecting":
|
|
5762
6601
|
return "reconnecting";
|
|
6602
|
+
// Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
|
|
6603
|
+
// signal blip from a full media reconnect.
|
|
6604
|
+
case "signalReconnecting":
|
|
6605
|
+
return "signal_reconnecting";
|
|
5763
6606
|
default:
|
|
5764
6607
|
return "disconnected";
|
|
5765
6608
|
}
|
|
@@ -5840,8 +6683,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5840
6683
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
5841
6684
|
adaptiveStream: true,
|
|
5842
6685
|
dynacast: true,
|
|
6686
|
+
// S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
|
|
6687
|
+
// first two attempts), give-up limits in one place (`connection-supervisor.ts`).
|
|
6688
|
+
reconnectPolicy: createReconnectPolicy(),
|
|
5843
6689
|
...options.roomOptions
|
|
5844
6690
|
});
|
|
6691
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module2.DisconnectReason?.[reason] ?? String(reason);
|
|
5845
6692
|
const roster = () => {
|
|
5846
6693
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
5847
6694
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -5859,8 +6706,17 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5859
6706
|
if (track.kind !== Track.Kind.Audio) return;
|
|
5860
6707
|
for (const element of track.detach()) element.remove();
|
|
5861
6708
|
});
|
|
5862
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected,
|
|
6709
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
6710
|
+
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
6711
|
+
roster();
|
|
6712
|
+
}).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) => {
|
|
5863
6713
|
handlers.roomMetadata?.(metadata ?? "");
|
|
6714
|
+
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
6715
|
+
handlers.audioPlayback?.(canPlay);
|
|
6716
|
+
}).on(RoomEvent.MediaDevicesChanged, () => {
|
|
6717
|
+
handlers.devicesChanged?.();
|
|
6718
|
+
}).on(RoomEvent.MediaDevicesError, (error, kind) => {
|
|
6719
|
+
handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
|
|
5864
6720
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
5865
6721
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
5866
6722
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
@@ -5876,8 +6732,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5876
6732
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
5877
6733
|
audioContainer?.remove();
|
|
5878
6734
|
audioContainer = null;
|
|
5879
|
-
|
|
5880
|
-
handlers.disconnected(named);
|
|
6735
|
+
handlers.disconnected(reasonName(reason));
|
|
5881
6736
|
});
|
|
5882
6737
|
try {
|
|
5883
6738
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -5958,6 +6813,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5958
6813
|
}
|
|
5959
6814
|
projectRoster?.();
|
|
5960
6815
|
handlers.roomMetadata?.(target.metadata ?? "");
|
|
6816
|
+
handlers.audioPlayback?.(target.canPlaybackAudio);
|
|
5961
6817
|
},
|
|
5962
6818
|
async disconnect() {
|
|
5963
6819
|
if (room === null) return;
|
|
@@ -6084,6 +6940,11 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
6084
6940
|
} catch (error) {
|
|
6085
6941
|
throw normalizeDeviceError(error, "meet.switchDevice");
|
|
6086
6942
|
}
|
|
6943
|
+
},
|
|
6944
|
+
async startAudio() {
|
|
6945
|
+
const target = room;
|
|
6946
|
+
if (target === null) return;
|
|
6947
|
+
await target.startAudio();
|
|
6087
6948
|
}
|
|
6088
6949
|
};
|
|
6089
6950
|
}
|
|
@@ -6267,7 +7128,14 @@ function MeetRuntime(props) {
|
|
|
6267
7128
|
effectsStorage,
|
|
6268
7129
|
resolveDevice: (kind) => devicesHolder.current?.resolve(kind) ?? null,
|
|
6269
7130
|
// §3.4: on every LiveKit disconnect, ask the server what happened to this connection.
|
|
6270
|
-
readExit: (connection) => meetings.readExit(connection)
|
|
7131
|
+
readExit: (connection) => meetings.readExit(connection),
|
|
7132
|
+
// S6b: the connection supervisor's ports — the device's network, whether OUR servers (the
|
|
7133
|
+
// API and the media server) answer, and the per-tab reload-after-offline marker.
|
|
7134
|
+
connection: {
|
|
7135
|
+
network: createWebNetworkPort(),
|
|
7136
|
+
resume: createWebResumeStorage(),
|
|
7137
|
+
probe: (serverUrl) => createReachabilityProbe(() => [baseUrl, ...serverUrl !== null ? [httpUrlOf(serverUrl)] : []])()
|
|
7138
|
+
}
|
|
6271
7139
|
});
|
|
6272
7140
|
const meetings = createMeetingSession({
|
|
6273
7141
|
manager,
|
|
@@ -6484,22 +7352,10 @@ function observedRole(role, standing) {
|
|
|
6484
7352
|
if (role === "host" || role === "cohost") return role;
|
|
6485
7353
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
6486
7354
|
}
|
|
6487
|
-
function observedDevice(track) {
|
|
7355
|
+
function observedDevice(track, permission = "unknown") {
|
|
6488
7356
|
if (track.enabled) return "on";
|
|
6489
|
-
const
|
|
6490
|
-
|
|
6491
|
-
switch (refused.code) {
|
|
6492
|
-
case "permission-denied":
|
|
6493
|
-
return "unavailable:denied";
|
|
6494
|
-
case "device-not-found":
|
|
6495
|
-
return "unavailable:none";
|
|
6496
|
-
case "device-in-use":
|
|
6497
|
-
return "unavailable:busy";
|
|
6498
|
-
case "insecure-context":
|
|
6499
|
-
return "unavailable:blocked_site";
|
|
6500
|
-
default:
|
|
6501
|
-
return "unavailable:failed";
|
|
6502
|
-
}
|
|
7357
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
7358
|
+
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
6503
7359
|
}
|
|
6504
7360
|
function endedRecord(snapshot, given) {
|
|
6505
7361
|
const record = snapshot?.meeting ?? given;
|
|
@@ -6532,9 +7388,10 @@ function observe(input) {
|
|
|
6532
7388
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
6533
7389
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
6534
7390
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
7391
|
+
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
7392
|
+
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
7393
|
+
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
7394
|
+
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
6538
7395
|
};
|
|
6539
7396
|
}
|
|
6540
7397
|
function staticObservation(phase, reason = null) {
|
|
@@ -6595,14 +7452,14 @@ function participantAttributes(p) {
|
|
|
6595
7452
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
6596
7453
|
"data-meet-participant-self": String(p.isLocal)
|
|
6597
7454
|
};
|
|
6598
|
-
const quality = observedQuality(p.quality);
|
|
7455
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
6599
7456
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
6600
7457
|
return attrs;
|
|
6601
7458
|
}
|
|
6602
|
-
function microphoneNoticeCode(track) {
|
|
6603
|
-
const
|
|
6604
|
-
if (
|
|
6605
|
-
return
|
|
7459
|
+
function microphoneNoticeCode(track, permission = "unknown") {
|
|
7460
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
7461
|
+
if (reason === null) return null;
|
|
7462
|
+
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
6606
7463
|
}
|
|
6607
7464
|
|
|
6608
7465
|
// src/react/root.tsx
|
|
@@ -7703,6 +8560,26 @@ function useConsentNotice() {
|
|
|
7703
8560
|
if (agent === null) return null;
|
|
7704
8561
|
return `${agent.displayName} is in this meeting: it is transcribing what is said and writing notes everyone here can read.`;
|
|
7705
8562
|
}
|
|
8563
|
+
function useMediaSupervisor() {
|
|
8564
|
+
const host = useMeetHost();
|
|
8565
|
+
const snapshot = useMeetSnapshot();
|
|
8566
|
+
const engine = host?.engine ?? null;
|
|
8567
|
+
return {
|
|
8568
|
+
region: snapshot?.mediaRegion ?? IDLE_MEDIA_REGION,
|
|
8569
|
+
where: (0, import_react4.useMemo)(() => {
|
|
8570
|
+
const platform = webMediaPlatform();
|
|
8571
|
+
return { browser: platform?.browser ?? "other", os: platform?.os ?? "other" };
|
|
8572
|
+
}, []),
|
|
8573
|
+
enableAudio: (0, import_react4.useCallback)(async () => engine?.enableAudio(), [engine]),
|
|
8574
|
+
acceptOffer: (0, import_react4.useCallback)(async () => engine?.acceptDeviceOffer(), [engine]),
|
|
8575
|
+
dismissOffer: (0, import_react4.useCallback)(() => engine?.dismissDeviceOffer(), [engine]),
|
|
8576
|
+
dismissNotice: (0, import_react4.useCallback)(() => engine?.dismissDeviceNotice(), [engine]),
|
|
8577
|
+
retry: (0, import_react4.useCallback)(
|
|
8578
|
+
async (kind) => kind === "microphone" ? engine?.setMicrophoneEnabled(true) : engine?.setCameraEnabled(true),
|
|
8579
|
+
[engine]
|
|
8580
|
+
)
|
|
8581
|
+
};
|
|
8582
|
+
}
|
|
7706
8583
|
function useLocalMedia() {
|
|
7707
8584
|
const snapshot = useMeetSnapshot();
|
|
7708
8585
|
return snapshot?.media ?? IDLE_MEDIA;
|
|
@@ -8814,6 +9691,7 @@ function useMeetCore() {
|
|
|
8814
9691
|
reason: snapshot?.reason ?? null,
|
|
8815
9692
|
phaseNotice: snapshot?.phaseNotice ?? null,
|
|
8816
9693
|
connection: snapshot?.connection ?? "stable",
|
|
9694
|
+
connectionBanner: snapshot?.connectionBanner ?? false,
|
|
8817
9695
|
share: snapshot?.share ?? NO_SHARE,
|
|
8818
9696
|
call: snapshot?.call ?? null,
|
|
8819
9697
|
actions
|
|
@@ -10080,7 +10958,8 @@ function ParticipantTile({
|
|
|
10080
10958
|
participant.displayName,
|
|
10081
10959
|
participant.isLocal ? " (you)" : "",
|
|
10082
10960
|
participant.isAgent ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "AI" }) : null,
|
|
10083
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RoleBadge, { role: participant.role, standing: participant.standing })
|
|
10961
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(RoleBadge, { role: participant.role, standing: participant.standing }),
|
|
10962
|
+
participant.reconnecting === true ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__badge", children: "Reconnecting\u2026" }) : null
|
|
10084
10963
|
] }),
|
|
10085
10964
|
!participant.micEnabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MicOffIcon, { width: 14, height: 14 }) : null,
|
|
10086
10965
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(QualityBars, { quality: participant.quality })
|
|
@@ -10123,6 +11002,12 @@ function QualityBars({ quality }) {
|
|
|
10123
11002
|
const label = quality === "lost" ? "Connection lost" : quality === "poor" ? "Poor connection" : `Connection ${quality}`;
|
|
10124
11003
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `mx-meet__signal mx-meet__signal--${quality}`, title: label, "aria-label": label, role: "img", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SignalIcon, { bars, width: 14, height: 14 }) });
|
|
10125
11004
|
}
|
|
11005
|
+
var CONNECTION_BANNER = {
|
|
11006
|
+
signal_reconnecting: "Reconnecting\u2026",
|
|
11007
|
+
reconnecting: "Reconnecting\u2026",
|
|
11008
|
+
offline: "You're offline. Reconnecting when your internet is back\u2026",
|
|
11009
|
+
server_unreachable: "Can't reach the meeting server. Retrying\u2026"
|
|
11010
|
+
};
|
|
10126
11011
|
function Stage({ pageSize }) {
|
|
10127
11012
|
const stage = useStage(pageSize);
|
|
10128
11013
|
const columns = gridColumns(stage.tiles.length);
|
|
@@ -10543,12 +11428,83 @@ function Toast({ text: text3, failed, onDone }) {
|
|
|
10543
11428
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: onDone, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
10544
11429
|
] });
|
|
10545
11430
|
}
|
|
11431
|
+
function MediaNotices() {
|
|
11432
|
+
const media = useLocalMedia();
|
|
11433
|
+
const { region, where, enableAudio, acceptOffer, dismissOffer, dismissNotice, retry } = useMediaSupervisor();
|
|
11434
|
+
const [fixing, setFixing] = (0, import_react10.useState)(null);
|
|
11435
|
+
const chips = ["microphone", "camera"].flatMap((kind) => {
|
|
11436
|
+
const reason = deviceUnavailableReason(media[kind], region.permission[kind]);
|
|
11437
|
+
return reason === null ? [] : [{ kind, reason }];
|
|
11438
|
+
});
|
|
11439
|
+
const fixReason = fixing === null ? null : chips.find((chip) => chip.kind === fixing)?.reason ?? null;
|
|
11440
|
+
const notice = region.notice;
|
|
11441
|
+
const offer = region.offer;
|
|
11442
|
+
const name = (kind) => kind === "microphone" ? "Microphone" : "Camera";
|
|
11443
|
+
if (chips.length === 0 && region.audioOutput !== "blocked" && notice === null && offer === null) return null;
|
|
11444
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-notices", children: [
|
|
11445
|
+
region.audioOutput === "blocked" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-pill", role: "alert", "data-meet-notice": "audio-blocked", children: [
|
|
11446
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Sound is paused in this tab" }),
|
|
11447
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void enableAudio(), children: "Enable sound" })
|
|
11448
|
+
] }) : null,
|
|
11449
|
+
chips.map(({ kind, reason }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-pill", role: "status", "data-meet-notice": deviceNoticeCode(kind, reason), children: [
|
|
11450
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: deviceChipLabel(kind, reason) }),
|
|
11451
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11452
|
+
"button",
|
|
11453
|
+
{
|
|
11454
|
+
type: "button",
|
|
11455
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
11456
|
+
"aria-label": kind === "microphone" ? "Fix microphone" : "Fix camera",
|
|
11457
|
+
"aria-expanded": fixing === kind,
|
|
11458
|
+
onClick: () => setFixing(fixing === kind ? null : kind),
|
|
11459
|
+
children: "Fix"
|
|
11460
|
+
}
|
|
11461
|
+
)
|
|
11462
|
+
] }, kind)),
|
|
11463
|
+
fixing !== null && fixReason !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__popover mx-meet__fix-sheet", role: "dialog", "aria-label": `Fix ${name(fixing).toLowerCase()}`, children: [
|
|
11464
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { className: "mx-meet__fix-steps", children: deviceFixSteps(fixing, fixReason, where).map((step) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: step }, step)) }),
|
|
11465
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__fix-actions", children: [
|
|
11466
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
11467
|
+
"button",
|
|
11468
|
+
{
|
|
11469
|
+
type: "button",
|
|
11470
|
+
className: "mx-meet__primary mx-meet__primary--small",
|
|
11471
|
+
onClick: () => {
|
|
11472
|
+
const kind = fixing;
|
|
11473
|
+
setFixing(null);
|
|
11474
|
+
void retry(kind);
|
|
11475
|
+
},
|
|
11476
|
+
children: "Try again"
|
|
11477
|
+
}
|
|
11478
|
+
),
|
|
11479
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Close", onClick: () => setFixing(null), children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
11480
|
+
] })
|
|
11481
|
+
] }) : null,
|
|
11482
|
+
notice !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
11483
|
+
"div",
|
|
11484
|
+
{
|
|
11485
|
+
className: "mx-meet__media-pill",
|
|
11486
|
+
role: "status",
|
|
11487
|
+
...notice.outcome === "lost" ? { "data-meet-notice": "device-lost" } : {},
|
|
11488
|
+
children: [
|
|
11489
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: notice.outcome === "switched" ? `${notice.from || name(notice.kind)} disconnected \xB7 now using ${notice.to || "the default"}` : `${notice.from || name(notice.kind)} disconnected` }),
|
|
11490
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissNotice, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
11491
|
+
]
|
|
11492
|
+
}
|
|
11493
|
+
) : null,
|
|
11494
|
+
offer !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mx-meet__media-pill", role: "status", children: [
|
|
11495
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: `New ${offer.kind}: ${offer.label || name(offer.kind)}` }),
|
|
11496
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__primary mx-meet__primary--small", onClick: () => void acceptOffer(), children: "Use it" }),
|
|
11497
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "mx-meet__toast-close", "aria-label": "Dismiss", onClick: dismissOffer, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, { width: 14, height: 14 }) })
|
|
11498
|
+
] }) : null
|
|
11499
|
+
] });
|
|
11500
|
+
}
|
|
10546
11501
|
function MeetingOverlays() {
|
|
10547
11502
|
const reactions = useReactions();
|
|
10548
11503
|
const { notice, dismiss } = useModerationNotice();
|
|
10549
11504
|
const room = useRoom();
|
|
10550
11505
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
10551
11506
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ReactionOverlay, { reactions }),
|
|
11507
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MediaNotices, {}),
|
|
10552
11508
|
notice !== null && notice.kind !== "removed" ? (
|
|
10553
11509
|
// A host's request ("the host is asking you to unmute"), not an error:
|
|
10554
11510
|
// an assertive status, so it is announced at once without being filed
|
|
@@ -11830,7 +12786,7 @@ function MeetSkinRoom({
|
|
|
11830
12786
|
headerControls !== void 0 && headerControls !== null ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "mx-meet__header-controls", "data-meet-header-controls": "", children: headerControls }) : null,
|
|
11831
12787
|
isHost ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HostMenu, {}) : null
|
|
11832
12788
|
] }),
|
|
11833
|
-
core.connection !== "stable" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", children:
|
|
12789
|
+
core.connectionBanner && core.connection !== "stable" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mx-meet__banner", role: "status", "data-meet-notice": `connection-${core.connection}`, children: CONNECTION_BANNER[core.connection] }) : null,
|
|
11834
12790
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ShareNotice, {}),
|
|
11835
12791
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Stage, {}),
|
|
11836
12792
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Captions, {}),
|
|
@@ -11864,7 +12820,7 @@ var REFUSED_TITLES = {
|
|
|
11864
12820
|
registration_required: "Register to join this meeting"
|
|
11865
12821
|
};
|
|
11866
12822
|
function ExitScreen({ onLeave }) {
|
|
11867
|
-
const { phase, reason, host, snapshot, actions, call } = useMeetCore();
|
|
12823
|
+
const { phase, reason, host, snapshot, actions, call, connection } = useMeetCore();
|
|
11868
12824
|
const [busy, setBusy] = (0, import_react10.useState)(false);
|
|
11869
12825
|
const failure = snapshot?.failure ?? null;
|
|
11870
12826
|
const rejoin = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
@@ -11920,7 +12876,7 @@ function ExitScreen({ onLeave }) {
|
|
|
11920
12876
|
break;
|
|
11921
12877
|
case "disconnected":
|
|
11922
12878
|
title = "You lost your connection to the meeting";
|
|
11923
|
-
line = failure?.remedy ?? "Check your internet connection, then rejoin.";
|
|
12879
|
+
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.";
|
|
11924
12880
|
notice = "disconnected";
|
|
11925
12881
|
actionsNode = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
11926
12882
|
rejoin,
|
|
@@ -12529,6 +13485,7 @@ function MeetingSkinRootBody({
|
|
|
12529
13485
|
host.store.dispatch({ type: "reset" });
|
|
12530
13486
|
}
|
|
12531
13487
|
if (host.store.snapshot().phase !== "resolving") return;
|
|
13488
|
+
if (given?.linkRun?.reason == null && host.engine.resume(roomName)) return;
|
|
12532
13489
|
const reason = given?.linkRun?.reason ?? null;
|
|
12533
13490
|
if (reason !== null && given?.linkRun !== void 0) {
|
|
12534
13491
|
const words = linkRunWords(given.linkRun);
|