@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/core.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(core_exports, {
|
|
|
36
36
|
CAPTION_LANGUAGES: () => CAPTION_LANGUAGES,
|
|
37
37
|
CONSENT_NOTICE_ATTRIBUTE: () => CONSENT_NOTICE_ATTRIBUTE,
|
|
38
38
|
DATA_TOPICS: () => DATA_TOPICS,
|
|
39
|
+
DEFAULT_CONNECTION_POLICY: () => DEFAULT_CONNECTION_POLICY,
|
|
39
40
|
DEFAULT_EFFECTS: () => DEFAULT_EFFECTS,
|
|
40
41
|
DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
|
|
41
42
|
EMPTY_COLLAB: () => EMPTY_COLLAB,
|
|
@@ -46,10 +47,12 @@ __export(core_exports, {
|
|
|
46
47
|
GUEST_RECORD_ACCESS_METADATA_KEY: () => GUEST_RECORD_ACCESS_METADATA_KEY,
|
|
47
48
|
GUEST_RECORD_NOT_SHARED: () => GUEST_RECORD_NOT_SHARED,
|
|
48
49
|
IDLE_MEDIA: () => IDLE_MEDIA,
|
|
50
|
+
IDLE_MEDIA_REGION: () => IDLE_MEDIA_REGION,
|
|
49
51
|
IDLE_PHASES: () => IDLE_PHASES,
|
|
50
52
|
IDLE_TRACK: () => IDLE_TRACK,
|
|
51
53
|
JOIN_REFUSAL_REASONS: () => JOIN_REFUSAL_REASONS,
|
|
52
54
|
LISTEN_ONLY_NOTICE: () => LISTEN_ONLY_NOTICE,
|
|
55
|
+
LIVEKIT_BASE_DELAYS_MS: () => LIVEKIT_BASE_DELAYS_MS,
|
|
53
56
|
LIVE_PHASES: () => LIVE_PHASES,
|
|
54
57
|
MEETING_INVITE_KIND: () => MEETING_INVITE_KIND,
|
|
55
58
|
MEETING_INVITE_VERSION: () => MEETING_INVITE_VERSION,
|
|
@@ -73,6 +76,7 @@ __export(core_exports, {
|
|
|
73
76
|
PARTICIPANT_STANDING_ATTRIBUTE: () => PARTICIPANT_STANDING_ATTRIBUTE,
|
|
74
77
|
REACTION_TTL_MS: () => REACTION_TTL_MS,
|
|
75
78
|
REFUSED_ABSENCE: () => REFUSED_ABSENCE,
|
|
79
|
+
RESUME_WINDOW_MS: () => RESUME_WINDOW_MS,
|
|
76
80
|
ROLE_RANK: () => ROLE_RANK,
|
|
77
81
|
ROOM_EVENTS: () => ROOM_EVENTS,
|
|
78
82
|
ROOM_EVENT_PREFIX: () => ROOM_EVENT_PREFIX,
|
|
@@ -102,6 +106,7 @@ __export(core_exports, {
|
|
|
102
106
|
attendanceTotals: () => attendanceTotals,
|
|
103
107
|
attendeeKey: () => attendeeKey,
|
|
104
108
|
autoAssign: () => autoAssign,
|
|
109
|
+
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
105
110
|
availableActivities: () => availableActivities,
|
|
106
111
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
107
112
|
callInviteAction: () => callInviteAction,
|
|
@@ -113,15 +118,19 @@ __export(core_exports, {
|
|
|
113
118
|
captionSegment: () => captionSegment,
|
|
114
119
|
captureHeldOf: () => captureHeldOf,
|
|
115
120
|
changesPatch: () => changesPatch,
|
|
121
|
+
classifyDeviceFailure: () => classifyDeviceFailure,
|
|
116
122
|
clockPosition: () => clockPosition,
|
|
117
123
|
collabFromState: () => collabFromState,
|
|
118
124
|
computeStage: () => computeStage,
|
|
125
|
+
connectionPolicyFrom: () => connectionPolicyFrom,
|
|
119
126
|
countLabel: () => countLabel,
|
|
120
127
|
createCallCenter: () => createCallCenter,
|
|
121
128
|
createCallInviteHandler: () => createCallInviteHandler,
|
|
129
|
+
createConnectionSupervisor: () => createConnectionSupervisor,
|
|
122
130
|
createDeviceManager: () => createDeviceManager,
|
|
123
131
|
createInRoomClient: () => createInRoomClient,
|
|
124
132
|
createLiveKitDriver: () => createLiveKitDriver,
|
|
133
|
+
createMediaSupervisor: () => createMediaSupervisor,
|
|
125
134
|
createMeetAi: () => createMeetAi,
|
|
126
135
|
createMeetApi: () => createMeetApi,
|
|
127
136
|
createMeetRepository: () => createMeetRepository,
|
|
@@ -130,13 +139,23 @@ __export(core_exports, {
|
|
|
130
139
|
createMeetingRecordCache: () => createMeetingRecordCache,
|
|
131
140
|
createMeetingSession: () => createMeetingSession,
|
|
132
141
|
createMemoryDeviceStorage: () => createMemoryDeviceStorage,
|
|
142
|
+
createReachabilityProbe: () => createReachabilityProbe,
|
|
143
|
+
createReconnectPolicy: () => createReconnectPolicy,
|
|
133
144
|
createRecordingController: () => createRecordingController,
|
|
134
145
|
createRoomEngine: () => createRoomEngine,
|
|
135
146
|
createTokenClient: () => createTokenClient,
|
|
136
147
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
137
148
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
149
|
+
createWebNetworkPort: () => createWebNetworkPort,
|
|
150
|
+
createWebResumeStorage: () => createWebResumeStorage,
|
|
138
151
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
139
152
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
153
|
+
detectBrowser: () => detectBrowser,
|
|
154
|
+
detectOs: () => detectOs,
|
|
155
|
+
deviceChipLabel: () => deviceChipLabel,
|
|
156
|
+
deviceFixSteps: () => deviceFixSteps,
|
|
157
|
+
deviceNoticeCode: () => deviceNoticeCode,
|
|
158
|
+
deviceUnavailableReason: () => deviceUnavailableReason,
|
|
140
159
|
displayNameFor: () => displayNameFor,
|
|
141
160
|
fileDownloadPath: () => fileDownloadPath,
|
|
142
161
|
focusFromRow: () => focusFromRow,
|
|
@@ -147,6 +166,7 @@ __export(core_exports, {
|
|
|
147
166
|
guestRecordAccessSetting: () => guestRecordAccessSetting,
|
|
148
167
|
hasEnded: () => hasEnded,
|
|
149
168
|
hostUserIds: () => hostUserIds,
|
|
169
|
+
httpUrlOf: () => httpUrlOf,
|
|
150
170
|
invalidResponse: () => invalidResponse,
|
|
151
171
|
isGuest: () => isGuest,
|
|
152
172
|
isGuestIdentity: () => isGuestIdentity,
|
|
@@ -160,6 +180,7 @@ __export(core_exports, {
|
|
|
160
180
|
isRealName: () => isRealName,
|
|
161
181
|
isRefusedPhase: () => isRefusedPhase,
|
|
162
182
|
isRoomEventTopic: () => isRoomEventTopic,
|
|
183
|
+
isUnannouncedDeparture: () => isUnannouncedDeparture,
|
|
163
184
|
isWaitingPhase: () => isWaitingPhase,
|
|
164
185
|
learnName: () => learnName,
|
|
165
186
|
learnNames: () => learnNames,
|
|
@@ -181,6 +202,7 @@ __export(core_exports, {
|
|
|
181
202
|
meetingTopic: () => meetingTopic,
|
|
182
203
|
meetingWhen: () => meetingWhen,
|
|
183
204
|
mergeCaption: () => mergeCaption,
|
|
205
|
+
mergeGhosts: () => mergeGhosts,
|
|
184
206
|
mergeRecord: () => mergeRecord,
|
|
185
207
|
microphoneNoticeCode: () => microphoneNoticeCode,
|
|
186
208
|
moveToRoom: () => moveToRoom,
|
|
@@ -245,6 +267,7 @@ __export(core_exports, {
|
|
|
245
267
|
untranslatedLines: () => untranslatedLines,
|
|
246
268
|
userTopic: () => userTopic,
|
|
247
269
|
wasRefused: () => wasRefused,
|
|
270
|
+
webMediaPlatform: () => webMediaPlatform,
|
|
248
271
|
withItem: () => withItem,
|
|
249
272
|
wrapUpStatus: () => wrapUpStatus
|
|
250
273
|
});
|
|
@@ -456,6 +479,352 @@ function invalidResponse(operation, detail) {
|
|
|
456
479
|
);
|
|
457
480
|
}
|
|
458
481
|
|
|
482
|
+
// src/core/connection-supervisor.ts
|
|
483
|
+
var DEFAULT_CONNECTION_POLICY = {
|
|
484
|
+
bannerDelayMs: 2e3,
|
|
485
|
+
autoRejoin: true,
|
|
486
|
+
autoRejoinMaxAttempts: 5,
|
|
487
|
+
reconnectGraceSeconds: 30
|
|
488
|
+
};
|
|
489
|
+
function clampInt(value, min, max, fallback) {
|
|
490
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
491
|
+
return Math.min(max, Math.max(min, Math.round(value)));
|
|
492
|
+
}
|
|
493
|
+
function connectionPolicyFrom(raw) {
|
|
494
|
+
if (typeof raw !== "object" || raw === null) return DEFAULT_CONNECTION_POLICY;
|
|
495
|
+
const row = raw;
|
|
496
|
+
const d = DEFAULT_CONNECTION_POLICY;
|
|
497
|
+
return {
|
|
498
|
+
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
499
|
+
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
500
|
+
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
501
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
505
|
+
function createReconnectPolicy(options = {}) {
|
|
506
|
+
const delays = options.delaysMs !== void 0 && options.delaysMs.length > 0 ? options.delaysMs : LIVEKIT_BASE_DELAYS_MS;
|
|
507
|
+
const giveUpMs = options.giveUpMs ?? 45e3;
|
|
508
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
509
|
+
const random = options.random ?? Math.random;
|
|
510
|
+
return {
|
|
511
|
+
nextRetryDelayInMs({ retryCount, elapsedMs }) {
|
|
512
|
+
if (retryCount >= maxAttempts || elapsedMs >= giveUpMs) return null;
|
|
513
|
+
const base = delays[Math.min(retryCount, delays.length - 1)] ?? 0;
|
|
514
|
+
const spread = Math.min(2e3, Math.max(500, base / 2));
|
|
515
|
+
return Math.round(base + random() * spread);
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function createWebNetworkPort() {
|
|
520
|
+
const scope = globalThis;
|
|
521
|
+
return {
|
|
522
|
+
online: () => scope.navigator?.onLine !== false,
|
|
523
|
+
subscribe(listener) {
|
|
524
|
+
if (typeof scope.addEventListener !== "function") return () => void 0;
|
|
525
|
+
const on = () => listener(true);
|
|
526
|
+
const off = () => listener(false);
|
|
527
|
+
scope.addEventListener("online", on);
|
|
528
|
+
scope.addEventListener("offline", off);
|
|
529
|
+
return () => {
|
|
530
|
+
scope.removeEventListener?.("online", on);
|
|
531
|
+
scope.removeEventListener?.("offline", off);
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
function createReachabilityProbe(urls, timeoutMs = 4e3) {
|
|
537
|
+
return async () => {
|
|
538
|
+
const targets = urls().filter((url) => url.length > 0);
|
|
539
|
+
if (targets.length === 0 || typeof fetch !== "function") return true;
|
|
540
|
+
const one = async (url) => {
|
|
541
|
+
const controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
542
|
+
const timer = setTimeout(() => controller?.abort(), timeoutMs);
|
|
543
|
+
try {
|
|
544
|
+
await fetch(url, {
|
|
545
|
+
method: "GET",
|
|
546
|
+
mode: "no-cors",
|
|
547
|
+
cache: "no-store",
|
|
548
|
+
credentials: "omit",
|
|
549
|
+
...controller !== null ? { signal: controller.signal } : {}
|
|
550
|
+
});
|
|
551
|
+
return true;
|
|
552
|
+
} catch {
|
|
553
|
+
return false;
|
|
554
|
+
} finally {
|
|
555
|
+
clearTimeout(timer);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
const answers = await Promise.all(targets.map(one));
|
|
559
|
+
return answers.every(Boolean);
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function httpUrlOf(serverUrl) {
|
|
563
|
+
return serverUrl.replace(/^ws(s?):\/\//i, "http$1://");
|
|
564
|
+
}
|
|
565
|
+
var RESUME_WINDOW_MS = 15 * 6e4;
|
|
566
|
+
var RESUME_KEY = "mx-meet:resume:";
|
|
567
|
+
function createWebResumeStorage() {
|
|
568
|
+
const storage = globalThis.sessionStorage;
|
|
569
|
+
if (storage === void 0) return void 0;
|
|
570
|
+
return {
|
|
571
|
+
load(roomName) {
|
|
572
|
+
try {
|
|
573
|
+
const raw = storage.getItem(RESUME_KEY + roomName);
|
|
574
|
+
if (raw === null) return null;
|
|
575
|
+
const parsed = JSON.parse(raw);
|
|
576
|
+
return typeof parsed?.at === "number" && typeof parsed.request?.roomName === "string" ? parsed : null;
|
|
577
|
+
} catch {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
save(roomName, marker) {
|
|
582
|
+
try {
|
|
583
|
+
storage.setItem(RESUME_KEY + roomName, JSON.stringify(marker));
|
|
584
|
+
} catch {
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
clear(roomName) {
|
|
588
|
+
try {
|
|
589
|
+
storage.removeItem(RESUME_KEY + roomName);
|
|
590
|
+
} catch {
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase.startsWith("refused:") || phase.startsWith("waiting:");
|
|
596
|
+
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
597
|
+
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
598
|
+
return Math.round(base * (0.5 + random()));
|
|
599
|
+
}
|
|
600
|
+
function createConnectionSupervisor(options) {
|
|
601
|
+
const { store, network } = options;
|
|
602
|
+
const now = options.now ?? (() => Date.now());
|
|
603
|
+
const random = options.random ?? Math.random;
|
|
604
|
+
const probeEveryMs = options.probeEveryMs ?? 3e3;
|
|
605
|
+
let driverRegion = null;
|
|
606
|
+
let probeTimer = null;
|
|
607
|
+
let probing = false;
|
|
608
|
+
let bannerTimer = null;
|
|
609
|
+
let rejoinTimer = null;
|
|
610
|
+
let rejoinAttempts = 0;
|
|
611
|
+
let lastPhase = store.snapshot().phase;
|
|
612
|
+
let lastSavedAt = 0;
|
|
613
|
+
let disposed = false;
|
|
614
|
+
const inCall = () => store.snapshot().phase === "in_call";
|
|
615
|
+
const dropped = () => store.snapshot().phase === "disconnected";
|
|
616
|
+
function setRegion(next) {
|
|
617
|
+
if (store.snapshot().connection !== next) store.patch({ connection: next });
|
|
618
|
+
}
|
|
619
|
+
function regionFor(reachable) {
|
|
620
|
+
if (!network.online()) return "offline";
|
|
621
|
+
if (reachable === false) return "server_unreachable";
|
|
622
|
+
if (dropped()) return reachable === true ? "stable" : store.snapshot().connection;
|
|
623
|
+
return driverRegion ?? "stable";
|
|
624
|
+
}
|
|
625
|
+
function needsWatch() {
|
|
626
|
+
const snap = store.snapshot();
|
|
627
|
+
if (dropped()) return true;
|
|
628
|
+
return inCall() && (driverRegion !== null || snap.connection !== "stable");
|
|
629
|
+
}
|
|
630
|
+
function stopProbe() {
|
|
631
|
+
if (probeTimer !== null) clearTimeout(probeTimer);
|
|
632
|
+
probeTimer = null;
|
|
633
|
+
}
|
|
634
|
+
function scheduleProbe(delayMs) {
|
|
635
|
+
if (disposed || probeTimer !== null) return;
|
|
636
|
+
probeTimer = setTimeout(() => {
|
|
637
|
+
probeTimer = null;
|
|
638
|
+
void runProbe();
|
|
639
|
+
}, delayMs);
|
|
640
|
+
}
|
|
641
|
+
async function runProbe() {
|
|
642
|
+
if (disposed || probing) return;
|
|
643
|
+
if (!needsWatch()) return;
|
|
644
|
+
if (!network.online()) {
|
|
645
|
+
setRegion("offline");
|
|
646
|
+
scheduleProbe(probeEveryMs);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
probing = true;
|
|
650
|
+
let reachable = false;
|
|
651
|
+
try {
|
|
652
|
+
reachable = await options.probe();
|
|
653
|
+
} catch {
|
|
654
|
+
reachable = false;
|
|
655
|
+
} finally {
|
|
656
|
+
probing = false;
|
|
657
|
+
}
|
|
658
|
+
if (disposed) return;
|
|
659
|
+
if (inCall() || dropped()) setRegion(regionFor(reachable));
|
|
660
|
+
if (dropped() && reachable) maybeRejoin();
|
|
661
|
+
if (needsWatch()) scheduleProbe(Math.round(probeEveryMs * (0.75 + random() * 0.5)));
|
|
662
|
+
}
|
|
663
|
+
function syncBanner() {
|
|
664
|
+
const snap = store.snapshot();
|
|
665
|
+
const unstable = snap.phase === "in_call" && snap.connection !== "stable";
|
|
666
|
+
if (!unstable) {
|
|
667
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
668
|
+
bannerTimer = null;
|
|
669
|
+
if (snap.connectionBanner) store.patch({ connectionBanner: false });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (snap.connectionBanner || bannerTimer !== null) return;
|
|
673
|
+
const delay = options.policy().bannerDelayMs;
|
|
674
|
+
if (delay <= 0) {
|
|
675
|
+
store.patch({ connectionBanner: true });
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
bannerTimer = setTimeout(() => {
|
|
679
|
+
bannerTimer = null;
|
|
680
|
+
const latest = store.snapshot();
|
|
681
|
+
if (latest.phase === "in_call" && latest.connection !== "stable" && !latest.connectionBanner) {
|
|
682
|
+
store.patch({ connectionBanner: true });
|
|
683
|
+
}
|
|
684
|
+
}, delay);
|
|
685
|
+
}
|
|
686
|
+
function stopRejoin() {
|
|
687
|
+
if (rejoinTimer !== null) clearTimeout(rejoinTimer);
|
|
688
|
+
rejoinTimer = null;
|
|
689
|
+
}
|
|
690
|
+
function maybeRejoin() {
|
|
691
|
+
if (disposed || rejoinTimer !== null || !dropped()) return;
|
|
692
|
+
const policy = options.policy();
|
|
693
|
+
if (!policy.autoRejoin) return;
|
|
694
|
+
if (rejoinAttempts >= policy.autoRejoinMaxAttempts) return;
|
|
695
|
+
if (!network.online()) return;
|
|
696
|
+
const attempt = rejoinAttempts;
|
|
697
|
+
rejoinTimer = setTimeout(() => {
|
|
698
|
+
rejoinTimer = null;
|
|
699
|
+
if (disposed || !dropped() || !network.online()) return;
|
|
700
|
+
rejoinAttempts = attempt + 1;
|
|
701
|
+
options.report?.("info", `meet: rejoining by itself (attempt ${rejoinAttempts} of ${policy.autoRejoinMaxAttempts})`);
|
|
702
|
+
void options.autoRejoin().catch(() => void 0);
|
|
703
|
+
}, autoRejoinDelayMs(attempt, random));
|
|
704
|
+
}
|
|
705
|
+
function saveMarker(force) {
|
|
706
|
+
const resume = options.resume;
|
|
707
|
+
const snap = store.snapshot();
|
|
708
|
+
if (resume === void 0 || snap.roomName === null) return;
|
|
709
|
+
const at = now();
|
|
710
|
+
if (!force && at - lastSavedAt < 1e4) return;
|
|
711
|
+
const request = options.currentRequest();
|
|
712
|
+
if (request === null) return;
|
|
713
|
+
lastSavedAt = at;
|
|
714
|
+
resume.save(snap.roomName, { request, at });
|
|
715
|
+
}
|
|
716
|
+
const unsubscribeStore = store.subscribe(() => {
|
|
717
|
+
if (disposed) return;
|
|
718
|
+
const snap = store.snapshot();
|
|
719
|
+
const phase = snap.phase;
|
|
720
|
+
const changed = phase !== lastPhase;
|
|
721
|
+
lastPhase = phase;
|
|
722
|
+
if (changed) {
|
|
723
|
+
if (phase === "in_call") {
|
|
724
|
+
rejoinAttempts = 0;
|
|
725
|
+
stopRejoin();
|
|
726
|
+
saveMarker(true);
|
|
727
|
+
} else if (phase === "disconnected") {
|
|
728
|
+
driverRegion = null;
|
|
729
|
+
saveMarker(true);
|
|
730
|
+
stopProbe();
|
|
731
|
+
scheduleProbe(0);
|
|
732
|
+
} else {
|
|
733
|
+
stopRejoin();
|
|
734
|
+
if (phase !== "joining") driverRegion = null;
|
|
735
|
+
if (CLEARS_RESUME(phase) && snap.roomName !== null) options.resume?.clear(snap.roomName);
|
|
736
|
+
}
|
|
737
|
+
} else if (phase === "in_call" || phase === "disconnected") {
|
|
738
|
+
saveMarker(false);
|
|
739
|
+
}
|
|
740
|
+
syncBanner();
|
|
741
|
+
});
|
|
742
|
+
const unsubscribeNetwork = network.subscribe((online) => {
|
|
743
|
+
if (disposed) return;
|
|
744
|
+
if (!online) {
|
|
745
|
+
if (inCall() || dropped()) setRegion("offline");
|
|
746
|
+
stopRejoin();
|
|
747
|
+
stopProbe();
|
|
748
|
+
if (needsWatch()) scheduleProbe(probeEveryMs);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
stopProbe();
|
|
752
|
+
if (needsWatch()) scheduleProbe(0);
|
|
753
|
+
});
|
|
754
|
+
return {
|
|
755
|
+
driverState(state) {
|
|
756
|
+
if (disposed) return;
|
|
757
|
+
if (state === "connected") {
|
|
758
|
+
driverRegion = null;
|
|
759
|
+
if (inCall() || store.snapshot().phase === "joining") setRegion(network.online() ? "stable" : "offline");
|
|
760
|
+
if (!needsWatch()) stopProbe();
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (state === "reconnecting" || state === "signal_reconnecting") {
|
|
764
|
+
driverRegion = state;
|
|
765
|
+
if (inCall()) {
|
|
766
|
+
setRegion(network.online() ? state : "offline");
|
|
767
|
+
stopProbe();
|
|
768
|
+
scheduleProbe(0);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
},
|
|
772
|
+
exitConnection() {
|
|
773
|
+
return network.online() ? "server_unreachable" : "offline";
|
|
774
|
+
},
|
|
775
|
+
resumable(roomName) {
|
|
776
|
+
const resume = options.resume;
|
|
777
|
+
if (resume === void 0) return null;
|
|
778
|
+
const marker = resume.load(roomName);
|
|
779
|
+
if (marker === null) return null;
|
|
780
|
+
if (now() - marker.at > RESUME_WINDOW_MS) {
|
|
781
|
+
resume.clear(roomName);
|
|
782
|
+
return null;
|
|
783
|
+
}
|
|
784
|
+
return marker;
|
|
785
|
+
},
|
|
786
|
+
dispose() {
|
|
787
|
+
disposed = true;
|
|
788
|
+
stopProbe();
|
|
789
|
+
stopRejoin();
|
|
790
|
+
if (bannerTimer !== null) clearTimeout(bannerTimer);
|
|
791
|
+
bannerTimer = null;
|
|
792
|
+
unsubscribeStore();
|
|
793
|
+
unsubscribeNetwork();
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
var DELIBERATE_DEPARTURES = /* @__PURE__ */ new Set([
|
|
798
|
+
"CLIENT_INITIATED",
|
|
799
|
+
"PARTICIPANT_REMOVED",
|
|
800
|
+
"ROOM_DELETED",
|
|
801
|
+
"ROOM_CLOSED",
|
|
802
|
+
"DUPLICATE_IDENTITY",
|
|
803
|
+
"USER_REJECTED",
|
|
804
|
+
"USER_UNAVAILABLE",
|
|
805
|
+
"SIP_TRUNK_FAILURE"
|
|
806
|
+
]);
|
|
807
|
+
function isUnannouncedDeparture(reason) {
|
|
808
|
+
return reason === null || !DELIBERATE_DEPARTURES.has(reason);
|
|
809
|
+
}
|
|
810
|
+
function mergeGhosts(live, ghosts, at) {
|
|
811
|
+
const present = new Set(live.map((p) => p.identity));
|
|
812
|
+
const kept = /* @__PURE__ */ new Map();
|
|
813
|
+
const extra = [];
|
|
814
|
+
for (const [identity, ghost] of ghosts) {
|
|
815
|
+
if (present.has(identity) || ghost.until <= at) continue;
|
|
816
|
+
kept.set(identity, ghost);
|
|
817
|
+
extra.push({
|
|
818
|
+
...ghost.participant,
|
|
819
|
+
micEnabled: false,
|
|
820
|
+
cameraEnabled: false,
|
|
821
|
+
screenSharing: false,
|
|
822
|
+
reconnecting: true
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
return { roster: [...live, ...extra], kept };
|
|
826
|
+
}
|
|
827
|
+
|
|
459
828
|
// src/core/tokens.ts
|
|
460
829
|
function createWebRoomTokenStorage(onDiagnostic) {
|
|
461
830
|
const store = globalThis.localStorage;
|
|
@@ -641,7 +1010,8 @@ function parseRoomToken(body, roomName) {
|
|
|
641
1010
|
admissionState: readString(payload, "admission_state") ?? void 0,
|
|
642
1011
|
pollSeconds: typeof payload["poll_seconds"] === "number" ? payload["poll_seconds"] : void 0,
|
|
643
1012
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
644
|
-
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone"
|
|
1013
|
+
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1014
|
+
connectionPolicy: connectionPolicyFrom(payload["connection_policy"])
|
|
645
1015
|
};
|
|
646
1016
|
}
|
|
647
1017
|
function createTokenClient(options) {
|
|
@@ -1371,6 +1741,10 @@ function nextPhase(from, event) {
|
|
|
1371
1741
|
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1372
1742
|
case "join":
|
|
1373
1743
|
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1744
|
+
case "auto_rejoin":
|
|
1745
|
+
return from === "disconnected" ? "joining" : null;
|
|
1746
|
+
case "resumed":
|
|
1747
|
+
return from === "resolving" ? "joining" : null;
|
|
1374
1748
|
case "back_to_prejoin":
|
|
1375
1749
|
return from === "left" || from === "disconnected" ? "prejoin" : null;
|
|
1376
1750
|
case "retry":
|
|
@@ -3248,13 +3622,19 @@ function recordingLabel(state) {
|
|
|
3248
3622
|
}
|
|
3249
3623
|
|
|
3250
3624
|
// src/core/attendance.ts
|
|
3625
|
+
var IDLE_MEDIA_REGION = {
|
|
3626
|
+
permission: { microphone: "unknown", camera: "unknown" },
|
|
3627
|
+
audioOutput: "ok",
|
|
3628
|
+
notice: null,
|
|
3629
|
+
offer: null
|
|
3630
|
+
};
|
|
3251
3631
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
3252
3632
|
var IDLE_MEDIA = {
|
|
3253
3633
|
microphone: IDLE_TRACK,
|
|
3254
3634
|
camera: IDLE_TRACK
|
|
3255
3635
|
};
|
|
3256
|
-
function unavailableFrom(error) {
|
|
3257
|
-
return { code: error.code, message: error.message, remedy: error.remedy };
|
|
3636
|
+
function unavailableFrom(error, reason) {
|
|
3637
|
+
return reason === void 0 ? { code: error.code, message: error.message, remedy: error.remedy } : { code: error.code, message: error.message, remedy: error.remedy, reason };
|
|
3258
3638
|
}
|
|
3259
3639
|
var LISTEN_ONLY_NOTICE = "You're attending without a microphone \u2014 others can't hear you";
|
|
3260
3640
|
function isListenOnly(media) {
|
|
@@ -3284,6 +3664,7 @@ var EMPTY = {
|
|
|
3284
3664
|
phaseNotice: null,
|
|
3285
3665
|
roomName: null,
|
|
3286
3666
|
connection: "stable",
|
|
3667
|
+
connectionBanner: false,
|
|
3287
3668
|
share: NO_SHARE,
|
|
3288
3669
|
call: null,
|
|
3289
3670
|
waiting: null,
|
|
@@ -3315,6 +3696,7 @@ var EMPTY = {
|
|
|
3315
3696
|
wrapUp: null,
|
|
3316
3697
|
noteTakerRefusal: null,
|
|
3317
3698
|
media: IDLE_MEDIA,
|
|
3699
|
+
mediaRegion: IDLE_MEDIA_REGION,
|
|
3318
3700
|
failure: null,
|
|
3319
3701
|
collab: EMPTY_COLLAB,
|
|
3320
3702
|
breakoutRoomId: null
|
|
@@ -3532,7 +3914,7 @@ function createMeetStore(initial = {}) {
|
|
|
3532
3914
|
},
|
|
3533
3915
|
setLocalTrack(kind, next) {
|
|
3534
3916
|
const current = state.media[kind];
|
|
3535
|
-
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.message === next.unavailable?.message) {
|
|
3917
|
+
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
3536
3918
|
return;
|
|
3537
3919
|
}
|
|
3538
3920
|
commit({ ...state, media: { ...state.media, [kind]: next } });
|
|
@@ -4238,8 +4620,11 @@ function mapConnectionState(state) {
|
|
|
4238
4620
|
case "connecting":
|
|
4239
4621
|
return "connecting";
|
|
4240
4622
|
case "reconnecting":
|
|
4241
|
-
case "signalReconnecting":
|
|
4242
4623
|
return "reconnecting";
|
|
4624
|
+
// Only the signal socket is down (S6b): its own region value, so the supervisor can tell a
|
|
4625
|
+
// signal blip from a full media reconnect.
|
|
4626
|
+
case "signalReconnecting":
|
|
4627
|
+
return "signal_reconnecting";
|
|
4243
4628
|
default:
|
|
4244
4629
|
return "disconnected";
|
|
4245
4630
|
}
|
|
@@ -4320,8 +4705,12 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4320
4705
|
// R5: large meetings degrade gracefully instead of melting a laptop.
|
|
4321
4706
|
adaptiveStream: true,
|
|
4322
4707
|
dynacast: true,
|
|
4708
|
+
// S6b: OUR retry schedule — jittered from attempt 0 (the SDK's default has no jitter on its
|
|
4709
|
+
// first two attempts), give-up limits in one place (`connection-supervisor.ts`).
|
|
4710
|
+
reconnectPolicy: createReconnectPolicy(),
|
|
4323
4711
|
...options.roomOptions
|
|
4324
4712
|
});
|
|
4713
|
+
const reasonName = (reason) => reason === void 0 || reason === null ? null : module2.DisconnectReason?.[reason] ?? String(reason);
|
|
4325
4714
|
const roster = () => {
|
|
4326
4715
|
const local = snapshotParticipant(created.localParticipant, true);
|
|
4327
4716
|
const remotes = [...created.remoteParticipants.values()].map(
|
|
@@ -4339,8 +4728,17 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4339
4728
|
if (track.kind !== Track.Kind.Audio) return;
|
|
4340
4729
|
for (const element of track.detach()) element.remove();
|
|
4341
4730
|
});
|
|
4342
|
-
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected,
|
|
4731
|
+
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4732
|
+
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4733
|
+
roster();
|
|
4734
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, roster).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4343
4735
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4736
|
+
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
4737
|
+
handlers.audioPlayback?.(canPlay);
|
|
4738
|
+
}).on(RoomEvent.MediaDevicesChanged, () => {
|
|
4739
|
+
handlers.devicesChanged?.();
|
|
4740
|
+
}).on(RoomEvent.MediaDevicesError, (error, kind) => {
|
|
4741
|
+
handlers.mediaDeviceError?.(error, kind === "audioinput" || kind === "videoinput" ? kind : null);
|
|
4344
4742
|
}).on(RoomEvent.ActiveSpeakersChanged, (speakers) => {
|
|
4345
4743
|
handlers.activeSpeakers(speakers.map((speaker) => speaker.identity));
|
|
4346
4744
|
}).on(RoomEvent.ConnectionQualityChanged, (quality, participant) => {
|
|
@@ -4356,8 +4754,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4356
4754
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
4357
4755
|
audioContainer?.remove();
|
|
4358
4756
|
audioContainer = null;
|
|
4359
|
-
|
|
4360
|
-
handlers.disconnected(named);
|
|
4757
|
+
handlers.disconnected(reasonName(reason));
|
|
4361
4758
|
});
|
|
4362
4759
|
try {
|
|
4363
4760
|
created.registerTextStreamHandler(TRANSCRIPTION_TOPIC, (reader, participantInfo) => {
|
|
@@ -4438,6 +4835,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4438
4835
|
}
|
|
4439
4836
|
projectRoster?.();
|
|
4440
4837
|
handlers.roomMetadata?.(target.metadata ?? "");
|
|
4838
|
+
handlers.audioPlayback?.(target.canPlaybackAudio);
|
|
4441
4839
|
},
|
|
4442
4840
|
async disconnect() {
|
|
4443
4841
|
if (room === null) return;
|
|
@@ -4564,6 +4962,11 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4564
4962
|
} catch (error) {
|
|
4565
4963
|
throw normalizeDeviceError(error, "meet.switchDevice");
|
|
4566
4964
|
}
|
|
4965
|
+
},
|
|
4966
|
+
async startAudio() {
|
|
4967
|
+
const target = room;
|
|
4968
|
+
if (target === null) return;
|
|
4969
|
+
await target.startAudio();
|
|
4567
4970
|
}
|
|
4568
4971
|
};
|
|
4569
4972
|
}
|
|
@@ -5659,6 +6062,342 @@ async function pushMeetingToGoogleCalendar(api, meetingId, target) {
|
|
|
5659
6062
|
return await response.json();
|
|
5660
6063
|
}
|
|
5661
6064
|
|
|
6065
|
+
// src/core/media-supervisor.ts
|
|
6066
|
+
function classifyDeviceFailure(error, permission) {
|
|
6067
|
+
if (permission === "denied") return "denied";
|
|
6068
|
+
switch (error.code) {
|
|
6069
|
+
case "permission-denied":
|
|
6070
|
+
if (/by system/i.test(error.message) || permission === "granted") return "blocked_os";
|
|
6071
|
+
return "denied";
|
|
6072
|
+
case "device-not-found":
|
|
6073
|
+
return "none";
|
|
6074
|
+
case "device-in-use":
|
|
6075
|
+
return "busy";
|
|
6076
|
+
case "insecure-context":
|
|
6077
|
+
return "blocked_site";
|
|
6078
|
+
default:
|
|
6079
|
+
return "failed";
|
|
6080
|
+
}
|
|
6081
|
+
}
|
|
6082
|
+
function deviceNoticeCode(kind, reason) {
|
|
6083
|
+
if (reason === "denied" || reason === "blocked_site" || reason === "blocked_os") {
|
|
6084
|
+
return kind === "microphone" ? "mic-blocked" : "camera-blocked";
|
|
6085
|
+
}
|
|
6086
|
+
return "device-lost";
|
|
6087
|
+
}
|
|
6088
|
+
function deviceUnavailableReason(track, permission) {
|
|
6089
|
+
if (track.enabled) return null;
|
|
6090
|
+
if (permission === "denied") return "denied";
|
|
6091
|
+
const refused = track.unavailable;
|
|
6092
|
+
if (refused === null) return null;
|
|
6093
|
+
return refused.reason ?? classifyDeviceFailure({ code: refused.code, message: refused.message }, permission);
|
|
6094
|
+
}
|
|
6095
|
+
function deviceChipLabel(kind, reason) {
|
|
6096
|
+
const what = kind === "microphone" ? "Mic" : "Camera";
|
|
6097
|
+
switch (reason) {
|
|
6098
|
+
case "denied":
|
|
6099
|
+
case "blocked_site":
|
|
6100
|
+
case "blocked_os":
|
|
6101
|
+
return `${what} blocked`;
|
|
6102
|
+
case "none":
|
|
6103
|
+
return kind === "microphone" ? "No microphone found" : "No camera found";
|
|
6104
|
+
case "busy":
|
|
6105
|
+
return `${what} in use by another app`;
|
|
6106
|
+
case "revoked":
|
|
6107
|
+
return `${what} access was turned off`;
|
|
6108
|
+
default:
|
|
6109
|
+
return `${what} didn't start`;
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
function deviceFixSteps(kind, reason, where) {
|
|
6113
|
+
const device = kind === "microphone" ? "Microphone" : "Camera";
|
|
6114
|
+
const lower = device.toLowerCase();
|
|
6115
|
+
switch (reason) {
|
|
6116
|
+
case "denied":
|
|
6117
|
+
case "revoked":
|
|
6118
|
+
case "blocked_site":
|
|
6119
|
+
switch (where.browser) {
|
|
6120
|
+
case "firefox":
|
|
6121
|
+
return [`Click the crossed-out ${lower} icon in the address bar`, "Clear the Blocked setting", "Press Try again and choose Allow"];
|
|
6122
|
+
case "safari":
|
|
6123
|
+
return where.os === "ios" ? [`Settings \u203A Apps \u203A Safari \u203A ${device}`, "Choose Allow", "Return here and press Try again"] : ["Safari \u203A Settings for This Website", `Set ${device} to Allow`, "Press Try again"];
|
|
6124
|
+
default:
|
|
6125
|
+
return ["Click the icon left of the web address", `Set ${device} to Allow`, "Press Try again"];
|
|
6126
|
+
}
|
|
6127
|
+
case "blocked_os": {
|
|
6128
|
+
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
6129
|
+
if (where.os === "mac") return [`System Settings \u203A Privacy & Security \u203A ${device}`, `Turn on ${app}`, "Reopen the browser if asked, then Try again"];
|
|
6130
|
+
if (where.os === "windows") return [`Settings \u203A Privacy & security \u203A ${device}`, `Turn on ${device} access for apps`, "Press Try again"];
|
|
6131
|
+
return [`Allow ${lower} access for ${app} in your system settings`, "Press Try again"];
|
|
6132
|
+
}
|
|
6133
|
+
case "busy":
|
|
6134
|
+
return [`Close other apps using the ${lower} (Zoom, Teams, another tab)`, "Press Try again"];
|
|
6135
|
+
case "none":
|
|
6136
|
+
return [`Connect a ${lower}`, "Press Try again"];
|
|
6137
|
+
default:
|
|
6138
|
+
return [`Pick another ${lower} in Settings`, "Press Try again"];
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
function detectBrowser(userAgent) {
|
|
6142
|
+
if (/Edg\//.test(userAgent)) return "edge";
|
|
6143
|
+
if (/Firefox\//.test(userAgent)) return "firefox";
|
|
6144
|
+
if (/Chrome\/|Chromium\/|CriOS\//.test(userAgent)) return "chrome";
|
|
6145
|
+
if (/Safari\//.test(userAgent)) return "safari";
|
|
6146
|
+
return "other";
|
|
6147
|
+
}
|
|
6148
|
+
function detectOs(userAgent, maxTouchPoints = 0) {
|
|
6149
|
+
if (/iPhone|iPad|iPod/.test(userAgent)) return "ios";
|
|
6150
|
+
if (/Macintosh/.test(userAgent)) return maxTouchPoints > 1 ? "ios" : "mac";
|
|
6151
|
+
if (/Android/.test(userAgent)) return "android";
|
|
6152
|
+
if (/Windows/.test(userAgent)) return "windows";
|
|
6153
|
+
if (/Linux|CrOS/.test(userAgent)) return "linux";
|
|
6154
|
+
return "other";
|
|
6155
|
+
}
|
|
6156
|
+
function webMediaPlatform() {
|
|
6157
|
+
const scope = globalThis;
|
|
6158
|
+
const nav = scope.navigator;
|
|
6159
|
+
if (nav === void 0) return null;
|
|
6160
|
+
const ua = nav.userAgent ?? "";
|
|
6161
|
+
const reading = (state) => state === "granted" || state === "denied" || state === "prompt" ? state : "unknown";
|
|
6162
|
+
const query = async (kind) => {
|
|
6163
|
+
try {
|
|
6164
|
+
return await nav.permissions?.query({ name: kind }) ?? null;
|
|
6165
|
+
} catch {
|
|
6166
|
+
return null;
|
|
6167
|
+
}
|
|
6168
|
+
};
|
|
6169
|
+
return {
|
|
6170
|
+
browser: detectBrowser(ua),
|
|
6171
|
+
os: detectOs(ua, nav.maxTouchPoints ?? 0),
|
|
6172
|
+
async queryPermission(kind) {
|
|
6173
|
+
return reading((await query(kind))?.state);
|
|
6174
|
+
},
|
|
6175
|
+
watchPermission(kind, onChange) {
|
|
6176
|
+
let status = null;
|
|
6177
|
+
let stopped = false;
|
|
6178
|
+
void query(kind).then((found) => {
|
|
6179
|
+
if (stopped || found === null) return;
|
|
6180
|
+
status = found;
|
|
6181
|
+
found.onchange = () => onChange(reading(found.state));
|
|
6182
|
+
});
|
|
6183
|
+
return () => {
|
|
6184
|
+
stopped = true;
|
|
6185
|
+
if (status !== null) status.onchange = null;
|
|
6186
|
+
};
|
|
6187
|
+
},
|
|
6188
|
+
async listDevices() {
|
|
6189
|
+
try {
|
|
6190
|
+
return await nav.mediaDevices?.enumerateDevices() ?? [];
|
|
6191
|
+
} catch {
|
|
6192
|
+
return [];
|
|
6193
|
+
}
|
|
6194
|
+
},
|
|
6195
|
+
onVisible(callback) {
|
|
6196
|
+
const doc = scope.document;
|
|
6197
|
+
if (doc === void 0) return () => void 0;
|
|
6198
|
+
const handler = () => {
|
|
6199
|
+
if (doc.visibilityState === "visible") callback();
|
|
6200
|
+
};
|
|
6201
|
+
doc.addEventListener("visibilitychange", handler);
|
|
6202
|
+
return () => doc.removeEventListener("visibilitychange", handler);
|
|
6203
|
+
}
|
|
6204
|
+
};
|
|
6205
|
+
}
|
|
6206
|
+
var INPUT_KIND = { microphone: "audioinput", camera: "videoinput" };
|
|
6207
|
+
function isAlias(deviceId) {
|
|
6208
|
+
return deviceId === "default" || deviceId === "communications" || deviceId === "";
|
|
6209
|
+
}
|
|
6210
|
+
function createMediaSupervisor(options) {
|
|
6211
|
+
const { store, platform } = options;
|
|
6212
|
+
let unsubscribers = [];
|
|
6213
|
+
let started = false;
|
|
6214
|
+
let noticeId = 0;
|
|
6215
|
+
let known = null;
|
|
6216
|
+
let inUse = { audioinput: null, videoinput: null };
|
|
6217
|
+
function region() {
|
|
6218
|
+
return store.snapshot().mediaRegion;
|
|
6219
|
+
}
|
|
6220
|
+
function setRegion(next) {
|
|
6221
|
+
const current = region();
|
|
6222
|
+
const merged = { ...current, ...next };
|
|
6223
|
+
if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
|
|
6224
|
+
return;
|
|
6225
|
+
}
|
|
6226
|
+
store.patch({ mediaRegion: merged });
|
|
6227
|
+
}
|
|
6228
|
+
function setPermission(kind, reading) {
|
|
6229
|
+
const current = region().permission;
|
|
6230
|
+
if (current[kind] === reading) return;
|
|
6231
|
+
setRegion({ permission: { ...current, [kind]: reading } });
|
|
6232
|
+
const track = store.snapshot().media[kind];
|
|
6233
|
+
if (reading === "granted" && !track.enabled && track.unavailable?.reason === "denied") {
|
|
6234
|
+
store.setLocalTrack(kind, {
|
|
6235
|
+
enabled: false,
|
|
6236
|
+
unavailable: { ...track.unavailable, reason: "failed", remedy: "Access is allowed now. Press Try again." }
|
|
6237
|
+
});
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
async function snapshotDevices() {
|
|
6241
|
+
const list = platform === null ? [] : await platform.listDevices();
|
|
6242
|
+
const out = { audioinput: /* @__PURE__ */ new Map(), videoinput: /* @__PURE__ */ new Map() };
|
|
6243
|
+
for (const device of list) {
|
|
6244
|
+
if (device.kind === "audioinput" || device.kind === "videoinput") out[device.kind].set(device.deviceId, device.label);
|
|
6245
|
+
}
|
|
6246
|
+
return out;
|
|
6247
|
+
}
|
|
6248
|
+
function readInUse() {
|
|
6249
|
+
const driver = options.driver();
|
|
6250
|
+
if (driver === null) return;
|
|
6251
|
+
const active = driver.activeDevices();
|
|
6252
|
+
inUse = { audioinput: active.audioinput, videoinput: active.videoinput };
|
|
6253
|
+
}
|
|
6254
|
+
async function handleDevicesChanged() {
|
|
6255
|
+
if (platform === null) return;
|
|
6256
|
+
const before = known;
|
|
6257
|
+
const now = await snapshotDevices();
|
|
6258
|
+
known = now;
|
|
6259
|
+
const driver = options.driver();
|
|
6260
|
+
if (driver === null || before === null) {
|
|
6261
|
+
readInUse();
|
|
6262
|
+
return;
|
|
6263
|
+
}
|
|
6264
|
+
for (const kind of ["microphone", "camera"]) {
|
|
6265
|
+
const deviceKind = INPUT_KIND[kind];
|
|
6266
|
+
const previous = inUse[deviceKind];
|
|
6267
|
+
const track = store.snapshot().media[kind];
|
|
6268
|
+
const gone = previous !== null && !isAlias(previous) && before[deviceKind].has(previous) && !now[deviceKind].has(previous);
|
|
6269
|
+
if (gone) {
|
|
6270
|
+
const from = before[deviceKind].get(previous) ?? "";
|
|
6271
|
+
const remaining = [...now[deviceKind].entries()].filter(([id]) => !isAlias(id));
|
|
6272
|
+
if (remaining.length === 0) {
|
|
6273
|
+
store.setLocalTrack(kind, {
|
|
6274
|
+
enabled: false,
|
|
6275
|
+
unavailable: {
|
|
6276
|
+
code: "device-not-found",
|
|
6277
|
+
message: `meet.${kind}: the ${kind} was disconnected and no other is connected`,
|
|
6278
|
+
remedy: `Connect a ${kind === "microphone" ? "microphone" : "camera"}, then press Try again.`,
|
|
6279
|
+
reason: "none"
|
|
6280
|
+
}
|
|
6281
|
+
});
|
|
6282
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
6283
|
+
continue;
|
|
6284
|
+
}
|
|
6285
|
+
if (kind === "camera") {
|
|
6286
|
+
const [nextId, nextLabel] = remaining[0];
|
|
6287
|
+
try {
|
|
6288
|
+
await driver.switchDevice("videoinput", nextId);
|
|
6289
|
+
if (track.enabled || track.unavailable !== null) {
|
|
6290
|
+
await driver.setCameraEnabled(true);
|
|
6291
|
+
store.setLocalTrack("camera", { enabled: true, unavailable: null });
|
|
6292
|
+
}
|
|
6293
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to: nextLabel } });
|
|
6294
|
+
} catch (error) {
|
|
6295
|
+
const refused = await unavailable(error, "camera", "meet.camera(fallback)");
|
|
6296
|
+
store.setLocalTrack("camera", { enabled: false, unavailable: refused });
|
|
6297
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "lost", from, to: null } });
|
|
6298
|
+
}
|
|
6299
|
+
} else {
|
|
6300
|
+
const active = driver.activeDevices().audioinput;
|
|
6301
|
+
const to = (active !== null ? now.audioinput.get(active) : void 0) ?? now.audioinput.get("default") ?? remaining[0][1];
|
|
6302
|
+
setRegion({ notice: { id: ++noticeId, kind, outcome: "switched", from, to } });
|
|
6303
|
+
}
|
|
6304
|
+
continue;
|
|
6305
|
+
}
|
|
6306
|
+
const added = [...now[deviceKind].entries()].find(([id]) => !isAlias(id) && !before[deviceKind].has(id));
|
|
6307
|
+
if (added !== void 0 && added[0] !== driver.activeDevices()[deviceKind]) {
|
|
6308
|
+
setRegion({ offer: { kind, deviceId: added[0], label: added[1] } });
|
|
6309
|
+
}
|
|
6310
|
+
}
|
|
6311
|
+
readInUse();
|
|
6312
|
+
}
|
|
6313
|
+
async function unavailable(error, kind, operation) {
|
|
6314
|
+
const normalized = normalizeDeviceError(error, operation);
|
|
6315
|
+
const permission = platform === null ? "unknown" : await platform.queryPermission(kind);
|
|
6316
|
+
if (permission !== "unknown") setPermission(kind, permission);
|
|
6317
|
+
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
6318
|
+
}
|
|
6319
|
+
return {
|
|
6320
|
+
start() {
|
|
6321
|
+
if (started || platform === null) return;
|
|
6322
|
+
started = true;
|
|
6323
|
+
for (const kind of ["microphone", "camera"]) {
|
|
6324
|
+
void platform.queryPermission(kind).then((reading) => setPermission(kind, reading));
|
|
6325
|
+
unsubscribers.push(platform.watchPermission(kind, (reading) => setPermission(kind, reading)));
|
|
6326
|
+
}
|
|
6327
|
+
unsubscribers.push(
|
|
6328
|
+
platform.onVisible(() => {
|
|
6329
|
+
const driver = options.driver();
|
|
6330
|
+
if (driver?.startAudio === void 0 || region().audioOutput !== "blocked") return;
|
|
6331
|
+
driver.startAudio().catch(() => void 0);
|
|
6332
|
+
})
|
|
6333
|
+
);
|
|
6334
|
+
},
|
|
6335
|
+
stop() {
|
|
6336
|
+
for (const off of unsubscribers) off();
|
|
6337
|
+
unsubscribers = [];
|
|
6338
|
+
started = false;
|
|
6339
|
+
},
|
|
6340
|
+
unavailable,
|
|
6341
|
+
onAudioPlayback(canPlay) {
|
|
6342
|
+
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
6343
|
+
},
|
|
6344
|
+
onDevicesChanged() {
|
|
6345
|
+
void handleDevicesChanged().catch((error) => {
|
|
6346
|
+
options.report("warn", `meet: device change could not be handled: ${String(error)}`, "Pick the device in Settings.");
|
|
6347
|
+
});
|
|
6348
|
+
},
|
|
6349
|
+
onDeviceError(error, deviceKind) {
|
|
6350
|
+
if (deviceKind === null) return;
|
|
6351
|
+
const kind = deviceKind === "audioinput" ? "microphone" : "camera";
|
|
6352
|
+
void unavailable(error, kind, `meet.${kind}`).then((refused) => {
|
|
6353
|
+
const track = store.snapshot().media[kind];
|
|
6354
|
+
if (track.enabled) return;
|
|
6355
|
+
store.setLocalTrack(kind, { enabled: false, unavailable: refused });
|
|
6356
|
+
});
|
|
6357
|
+
},
|
|
6358
|
+
onConnected() {
|
|
6359
|
+
void snapshotDevices().then((now) => {
|
|
6360
|
+
known = now;
|
|
6361
|
+
readInUse();
|
|
6362
|
+
});
|
|
6363
|
+
},
|
|
6364
|
+
onDisconnected() {
|
|
6365
|
+
known = null;
|
|
6366
|
+
inUse = { audioinput: null, videoinput: null };
|
|
6367
|
+
setRegion({ audioOutput: "ok", notice: null, offer: null });
|
|
6368
|
+
},
|
|
6369
|
+
async enableAudio() {
|
|
6370
|
+
const driver = options.driver();
|
|
6371
|
+
if (driver?.startAudio === void 0) return;
|
|
6372
|
+
try {
|
|
6373
|
+
await driver.startAudio();
|
|
6374
|
+
setRegion({ audioOutput: "ok" });
|
|
6375
|
+
} catch (error) {
|
|
6376
|
+
options.report("warn", `meet: sound could not be enabled: ${String(error)}`, "Click Enable sound again, or check the tab is not muted.");
|
|
6377
|
+
}
|
|
6378
|
+
},
|
|
6379
|
+
async acceptOffer() {
|
|
6380
|
+
const offer = region().offer;
|
|
6381
|
+
const driver = options.driver();
|
|
6382
|
+
setRegion({ offer: null });
|
|
6383
|
+
if (offer === null || driver === null) return;
|
|
6384
|
+
try {
|
|
6385
|
+
await driver.switchDevice(INPUT_KIND[offer.kind], offer.deviceId);
|
|
6386
|
+
readInUse();
|
|
6387
|
+
} catch (error) {
|
|
6388
|
+
const refused = await unavailable(error, offer.kind, "meet.switchDevice");
|
|
6389
|
+
options.report("warn", refused.message, refused.remedy);
|
|
6390
|
+
}
|
|
6391
|
+
},
|
|
6392
|
+
dismissOffer() {
|
|
6393
|
+
setRegion({ offer: null });
|
|
6394
|
+
},
|
|
6395
|
+
dismissNotice() {
|
|
6396
|
+
setRegion({ notice: null });
|
|
6397
|
+
}
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
|
|
5662
6401
|
// src/core/room.ts
|
|
5663
6402
|
function SCREEN_SHARE_BLOCKED_REMEDY_FOR(remedy) {
|
|
5664
6403
|
return /administrator|policy/i.test(remedy) ? remedy : SCREEN_SHARE_BLOCKED_REMEDY;
|
|
@@ -5730,6 +6469,18 @@ function createRoomEngine(options) {
|
|
|
5730
6469
|
let disposed = false;
|
|
5731
6470
|
let moving = false;
|
|
5732
6471
|
const confirmingEnds = /* @__PURE__ */ new WeakSet();
|
|
6472
|
+
let lastServerUrl = null;
|
|
6473
|
+
let connectionPolicy = DEFAULT_CONNECTION_POLICY;
|
|
6474
|
+
let ghosts = /* @__PURE__ */ new Map();
|
|
6475
|
+
let ghostTimer = null;
|
|
6476
|
+
let liveRoster = [];
|
|
6477
|
+
const departures = /* @__PURE__ */ new Map();
|
|
6478
|
+
function requestAsNow() {
|
|
6479
|
+
if (lastRequest === null) return null;
|
|
6480
|
+
const media2 = store.snapshot().media;
|
|
6481
|
+
if (store.snapshot().phase !== "in_call") return lastRequest;
|
|
6482
|
+
return { ...lastRequest, mode: media2.camera.enabled ? "video" : "audio", microphone: media2.microphone.enabled };
|
|
6483
|
+
}
|
|
5733
6484
|
async function dropRoom() {
|
|
5734
6485
|
const target = driver;
|
|
5735
6486
|
driver = null;
|
|
@@ -5739,7 +6490,9 @@ function createRoomEngine(options) {
|
|
|
5739
6490
|
}
|
|
5740
6491
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
5741
6492
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
6493
|
+
media.onDisconnected();
|
|
5742
6494
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
6495
|
+
clearGhosts();
|
|
5743
6496
|
await target?.disconnect();
|
|
5744
6497
|
}
|
|
5745
6498
|
async function leaveRoom() {
|
|
@@ -5803,11 +6556,26 @@ function createRoomEngine(options) {
|
|
|
5803
6556
|
report("error", normalized.message, normalized.remedy);
|
|
5804
6557
|
return normalized;
|
|
5805
6558
|
}
|
|
6559
|
+
const media = createMediaSupervisor({
|
|
6560
|
+
store,
|
|
6561
|
+
platform: options.mediaPlatform === void 0 ? webMediaPlatform() : options.mediaPlatform,
|
|
6562
|
+
driver: () => driver,
|
|
6563
|
+
report
|
|
6564
|
+
});
|
|
5806
6565
|
const handlers = {
|
|
6566
|
+
audioPlayback(canPlay) {
|
|
6567
|
+
media.onAudioPlayback(canPlay);
|
|
6568
|
+
},
|
|
6569
|
+
devicesChanged() {
|
|
6570
|
+
media.onDevicesChanged();
|
|
6571
|
+
},
|
|
6572
|
+
mediaDeviceError(error, kind) {
|
|
6573
|
+
media.onDeviceError(error, kind);
|
|
6574
|
+
},
|
|
5807
6575
|
participants(list) {
|
|
5808
6576
|
const joinedAt = new Date(now()).toISOString();
|
|
5809
6577
|
const before = new Set(store.snapshot().participants.map((p) => p.identity));
|
|
5810
|
-
|
|
6578
|
+
projectRoster(list, joinedAt);
|
|
5811
6579
|
const snapshot = store.snapshot();
|
|
5812
6580
|
const newcomer = snapshot.participants.some(
|
|
5813
6581
|
(participant) => !participant.isLocal && !before.has(participant.identity)
|
|
@@ -5819,6 +6587,9 @@ function createRoomEngine(options) {
|
|
|
5819
6587
|
);
|
|
5820
6588
|
}
|
|
5821
6589
|
},
|
|
6590
|
+
participantLeft(who, reason) {
|
|
6591
|
+
departures.set(who, reason);
|
|
6592
|
+
},
|
|
5822
6593
|
roomMetadata(metadata) {
|
|
5823
6594
|
let spotlight = null;
|
|
5824
6595
|
if (metadata.length > 0) {
|
|
@@ -5845,12 +6616,8 @@ function createRoomEngine(options) {
|
|
|
5845
6616
|
store.setQuality(asParticipantIdentity(who), value);
|
|
5846
6617
|
},
|
|
5847
6618
|
connectionState(state) {
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
store.dispatch({ type: "connected" });
|
|
5851
|
-
} else if (state === "reconnecting") {
|
|
5852
|
-
store.patch({ connection: "reconnecting" });
|
|
5853
|
-
}
|
|
6619
|
+
supervisor.driverState(state);
|
|
6620
|
+
if (state === "connected" && store.snapshot().phase === "joining") store.dispatch({ type: "connected" });
|
|
5854
6621
|
},
|
|
5855
6622
|
transcription(event) {
|
|
5856
6623
|
const snapshot = store.snapshot();
|
|
@@ -5983,6 +6750,7 @@ function createRoomEngine(options) {
|
|
|
5983
6750
|
};
|
|
5984
6751
|
async function resolveExitAndApply(hint) {
|
|
5985
6752
|
const connection = current?.identity ?? store.snapshot().localIdentity ?? null;
|
|
6753
|
+
lastRequest = requestAsNow();
|
|
5986
6754
|
await dropRoom();
|
|
5987
6755
|
let server;
|
|
5988
6756
|
if (options.readExit === void 0 || connection === null || store.snapshot().meeting === null) {
|
|
@@ -5996,7 +6764,7 @@ function createRoomEngine(options) {
|
|
|
5996
6764
|
}
|
|
5997
6765
|
if (disposed) return;
|
|
5998
6766
|
const resolved = resolveExit({ server, hint, frozen: options.wasFrozen?.() ?? false });
|
|
5999
|
-
store.patch({ connection: resolved.connection });
|
|
6767
|
+
store.patch({ connection: server === null ? supervisor.exitConnection() : resolved.connection });
|
|
6000
6768
|
if (resolved.to === "waiting:knocking") {
|
|
6001
6769
|
store.patch({
|
|
6002
6770
|
waiting: {
|
|
@@ -6028,6 +6796,8 @@ function createRoomEngine(options) {
|
|
|
6028
6796
|
...request.observe === true ? { observe: true } : {}
|
|
6029
6797
|
});
|
|
6030
6798
|
current = token;
|
|
6799
|
+
lastServerUrl = token.serverUrl;
|
|
6800
|
+
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6031
6801
|
store.patch({
|
|
6032
6802
|
localIdentity: token.identity,
|
|
6033
6803
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -6067,6 +6837,7 @@ function createRoomEngine(options) {
|
|
|
6067
6837
|
store.dispatch({ type: "connected" });
|
|
6068
6838
|
store.setLocalTrack("microphone", IDLE_TRACK);
|
|
6069
6839
|
store.setLocalTrack("camera", IDLE_TRACK);
|
|
6840
|
+
media.start();
|
|
6070
6841
|
const effects = options.effectsStorage?.load() ?? store.snapshot().effects;
|
|
6071
6842
|
store.patch({ effects });
|
|
6072
6843
|
if (!effects.noiseSuppression || effects.background !== "none") {
|
|
@@ -6091,6 +6862,7 @@ function createRoomEngine(options) {
|
|
|
6091
6862
|
if (request.microphone !== false) {
|
|
6092
6863
|
await enableTrack(created, "microphone", true);
|
|
6093
6864
|
}
|
|
6865
|
+
media.onConnected();
|
|
6094
6866
|
}
|
|
6095
6867
|
async function enableTrack(target, kind, enabled) {
|
|
6096
6868
|
try {
|
|
@@ -6100,10 +6872,10 @@ function createRoomEngine(options) {
|
|
|
6100
6872
|
store.setLocalTrack(kind, state);
|
|
6101
6873
|
return state;
|
|
6102
6874
|
} catch (error) {
|
|
6103
|
-
const
|
|
6104
|
-
const state = { enabled: false, unavailable:
|
|
6875
|
+
const refused = await media.unavailable(error, kind, `meet.${kind}`);
|
|
6876
|
+
const state = { enabled: false, unavailable: refused };
|
|
6105
6877
|
store.setLocalTrack(kind, state);
|
|
6106
|
-
report("warn",
|
|
6878
|
+
report("warn", refused.message, refused.remedy);
|
|
6107
6879
|
return state;
|
|
6108
6880
|
}
|
|
6109
6881
|
}
|
|
@@ -6132,7 +6904,59 @@ function createRoomEngine(options) {
|
|
|
6132
6904
|
throw normalized;
|
|
6133
6905
|
}
|
|
6134
6906
|
}
|
|
6135
|
-
|
|
6907
|
+
function projectRoster(list, joinedAt) {
|
|
6908
|
+
const at = now();
|
|
6909
|
+
const previous = new Map(liveRoster.map((p) => [p.identity, p]));
|
|
6910
|
+
const present = new Set(list.map((p) => p.identity));
|
|
6911
|
+
const graceMs = connectionPolicy.reconnectGraceSeconds * 1e3;
|
|
6912
|
+
for (const [who, reason] of departures) {
|
|
6913
|
+
const last = previous.get(who);
|
|
6914
|
+
if (last !== void 0 && !last.isLocal && !present.has(who) && graceMs > 0 && isUnannouncedDeparture(reason)) {
|
|
6915
|
+
ghosts.set(who, { participant: last, until: at + graceMs });
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
departures.clear();
|
|
6919
|
+
liveRoster = list;
|
|
6920
|
+
const merged = mergeGhosts(list, ghosts, at);
|
|
6921
|
+
ghosts = merged.kept;
|
|
6922
|
+
store.setParticipants(
|
|
6923
|
+
merged.roster.map((entry) => {
|
|
6924
|
+
const projected = projectParticipant(entry, joinedAt);
|
|
6925
|
+
return entry.reconnecting === true ? { ...projected, reconnecting: true, quality: "lost" } : projected;
|
|
6926
|
+
})
|
|
6927
|
+
);
|
|
6928
|
+
scheduleGhostExpiry();
|
|
6929
|
+
}
|
|
6930
|
+
function scheduleGhostExpiry() {
|
|
6931
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6932
|
+
ghostTimer = null;
|
|
6933
|
+
if (ghosts.size === 0 || disposed) return;
|
|
6934
|
+
const next = Math.min(...[...ghosts.values()].map((g) => g.until));
|
|
6935
|
+
ghostTimer = setTimeout(() => {
|
|
6936
|
+
ghostTimer = null;
|
|
6937
|
+
if (disposed || driver === null) return;
|
|
6938
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
6939
|
+
}, Math.max(0, next - now()) + 50);
|
|
6940
|
+
}
|
|
6941
|
+
function clearGhosts() {
|
|
6942
|
+
ghosts = /* @__PURE__ */ new Map();
|
|
6943
|
+
departures.clear();
|
|
6944
|
+
liveRoster = [];
|
|
6945
|
+
if (ghostTimer !== null) clearTimeout(ghostTimer);
|
|
6946
|
+
ghostTimer = null;
|
|
6947
|
+
}
|
|
6948
|
+
const supervisor = createConnectionSupervisor({
|
|
6949
|
+
store,
|
|
6950
|
+
network: options.connection?.network ?? createWebNetworkPort(),
|
|
6951
|
+
probe: () => options.connection?.probe?.(current?.serverUrl ?? lastServerUrl) ?? Promise.resolve(true),
|
|
6952
|
+
policy: () => connectionPolicy,
|
|
6953
|
+
autoRejoin: () => engine.autoRejoin(),
|
|
6954
|
+
currentRequest: requestAsNow,
|
|
6955
|
+
resume: options.connection?.resume,
|
|
6956
|
+
now,
|
|
6957
|
+
report: (level, message) => report(level, message)
|
|
6958
|
+
});
|
|
6959
|
+
const engine = {
|
|
6136
6960
|
join(request) {
|
|
6137
6961
|
if (joining !== null) return joining;
|
|
6138
6962
|
if (store.snapshot().phase !== "joining" && !store.dispatch({ type: "join", click: true })) {
|
|
@@ -6177,6 +7001,35 @@ function createRoomEngine(options) {
|
|
|
6177
7001
|
});
|
|
6178
7002
|
return joining;
|
|
6179
7003
|
},
|
|
7004
|
+
async autoRejoin() {
|
|
7005
|
+
const request = lastRequest;
|
|
7006
|
+
if (request === null || joining !== null) return joining ?? void 0;
|
|
7007
|
+
if (!store.dispatch({ type: "auto_rejoin" })) return;
|
|
7008
|
+
tokens.invalidate(request.roomName);
|
|
7009
|
+
current = null;
|
|
7010
|
+
joining = connect(request).catch((error) => {
|
|
7011
|
+
throw failJoin(error, "meet.autoRejoin");
|
|
7012
|
+
}).finally(() => {
|
|
7013
|
+
joining = null;
|
|
7014
|
+
});
|
|
7015
|
+
return joining;
|
|
7016
|
+
},
|
|
7017
|
+
resume(roomName) {
|
|
7018
|
+
const marker = supervisor.resumable(roomName);
|
|
7019
|
+
if (marker === null || joining !== null) return false;
|
|
7020
|
+
if (!store.dispatch({ type: "resumed" })) return false;
|
|
7021
|
+
lastRequest = { ...marker.request, roomName };
|
|
7022
|
+
report("info", "meet: this tab was in the call before the page reloaded \u2014 rejoining");
|
|
7023
|
+
tokens.invalidate(roomName);
|
|
7024
|
+
current = null;
|
|
7025
|
+
joining = connect(lastRequest).catch((error) => {
|
|
7026
|
+
throw failJoin(error, "meet.resume");
|
|
7027
|
+
}).finally(() => {
|
|
7028
|
+
joining = null;
|
|
7029
|
+
});
|
|
7030
|
+
joining.catch(() => void 0);
|
|
7031
|
+
return true;
|
|
7032
|
+
},
|
|
6180
7033
|
async moveToBreakout(roomId) {
|
|
6181
7034
|
const request = lastRequest;
|
|
6182
7035
|
if (request === null || driver === null) {
|
|
@@ -6331,6 +7184,10 @@ function createRoomEngine(options) {
|
|
|
6331
7184
|
if (driver === null) return;
|
|
6332
7185
|
await driver.switchDevice(kind, deviceId);
|
|
6333
7186
|
},
|
|
7187
|
+
enableAudio: () => media.enableAudio(),
|
|
7188
|
+
acceptDeviceOffer: () => media.acceptOffer(),
|
|
7189
|
+
dismissDeviceOffer: () => media.dismissOffer(),
|
|
7190
|
+
dismissDeviceNotice: () => media.dismissNotice(),
|
|
6334
7191
|
pin(who) {
|
|
6335
7192
|
store.patch({ pinned: who, layout: who === null ? "grid" : "speaker" });
|
|
6336
7193
|
},
|
|
@@ -6344,6 +7201,9 @@ function createRoomEngine(options) {
|
|
|
6344
7201
|
token: () => current,
|
|
6345
7202
|
async dispose() {
|
|
6346
7203
|
disposed = true;
|
|
7204
|
+
supervisor.dispose();
|
|
7205
|
+
media.stop();
|
|
7206
|
+
clearGhosts();
|
|
6347
7207
|
if (driver !== null) {
|
|
6348
7208
|
const target = driver;
|
|
6349
7209
|
driver = null;
|
|
@@ -6352,6 +7212,7 @@ function createRoomEngine(options) {
|
|
|
6352
7212
|
store.reset();
|
|
6353
7213
|
}
|
|
6354
7214
|
};
|
|
7215
|
+
return engine;
|
|
6355
7216
|
}
|
|
6356
7217
|
function canModerate(participant) {
|
|
6357
7218
|
return participant !== void 0 && isHostRole(participant.role);
|
|
@@ -7326,22 +8187,10 @@ function observedRole(role, standing) {
|
|
|
7326
8187
|
if (role === "host" || role === "cohost") return role;
|
|
7327
8188
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
7328
8189
|
}
|
|
7329
|
-
function observedDevice(track) {
|
|
8190
|
+
function observedDevice(track, permission = "unknown") {
|
|
7330
8191
|
if (track.enabled) return "on";
|
|
7331
|
-
const
|
|
7332
|
-
|
|
7333
|
-
switch (refused.code) {
|
|
7334
|
-
case "permission-denied":
|
|
7335
|
-
return "unavailable:denied";
|
|
7336
|
-
case "device-not-found":
|
|
7337
|
-
return "unavailable:none";
|
|
7338
|
-
case "device-in-use":
|
|
7339
|
-
return "unavailable:busy";
|
|
7340
|
-
case "insecure-context":
|
|
7341
|
-
return "unavailable:blocked_site";
|
|
7342
|
-
default:
|
|
7343
|
-
return "unavailable:failed";
|
|
7344
|
-
}
|
|
8192
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
8193
|
+
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
7345
8194
|
}
|
|
7346
8195
|
function endedRecord(snapshot, given) {
|
|
7347
8196
|
const record = snapshot?.meeting ?? given;
|
|
@@ -7374,9 +8223,10 @@ function observe(input) {
|
|
|
7374
8223
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
7375
8224
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
7376
8225
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
8226
|
+
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
8227
|
+
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
8228
|
+
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
8229
|
+
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
7380
8230
|
};
|
|
7381
8231
|
}
|
|
7382
8232
|
function staticObservation(phase, reason = null) {
|
|
@@ -7437,13 +8287,13 @@ function participantAttributes(p) {
|
|
|
7437
8287
|
"data-meet-participant-role": observedRole(p.role, p.standing),
|
|
7438
8288
|
"data-meet-participant-self": String(p.isLocal)
|
|
7439
8289
|
};
|
|
7440
|
-
const quality = observedQuality(p.quality);
|
|
8290
|
+
const quality = p.reconnecting === true ? "reconnecting" : observedQuality(p.quality);
|
|
7441
8291
|
if (quality !== null) attrs["data-meet-participant-connection"] = quality;
|
|
7442
8292
|
return attrs;
|
|
7443
8293
|
}
|
|
7444
|
-
function microphoneNoticeCode(track) {
|
|
7445
|
-
const
|
|
7446
|
-
if (
|
|
7447
|
-
return
|
|
8294
|
+
function microphoneNoticeCode(track, permission = "unknown") {
|
|
8295
|
+
const reason = deviceUnavailableReason(track, permission);
|
|
8296
|
+
if (reason === null) return null;
|
|
8297
|
+
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
7448
8298
|
}
|
|
7449
8299
|
//# sourceMappingURL=core.cjs.map
|