@ai-matrx/meet 0.11.43 → 0.11.45
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 +12 -0
- package/dist/{collab-BNJNgXFB.d.cts → collab-D1TX5uf9.d.cts} +7 -0
- package/dist/{collab-BNJNgXFB.d.ts → collab-D1TX5uf9.d.ts} +7 -0
- package/dist/{components-BDtGzHf4.d.ts → components-CNwBgmYU.d.ts} +1 -1
- package/dist/{components-BxK993iW.d.cts → components-CkyDos0f.d.cts} +1 -1
- package/dist/core.cjs +206 -20
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +121 -4
- package/dist/core.d.ts +121 -4
- package/dist/core.js +206 -20
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +206 -20
- 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 +206 -20
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +554 -50
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js +960 -456
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +374 -39
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +4 -3
- package/dist/skins/meet.d.ts +4 -3
- package/dist/skins/meet.js +721 -386
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +17 -1
- package/package.json +5 -5
package/dist/core.js
CHANGED
|
@@ -323,7 +323,8 @@ function createWebResumeStorage() {
|
|
|
323
323
|
}
|
|
324
324
|
};
|
|
325
325
|
}
|
|
326
|
-
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase
|
|
326
|
+
var CLEARS_RESUME = (phase) => phase === "left" || phase === "removed" || phase === "superseded" || phase === "inactive" || phase === "ended" || phase === "record" || phase === "prejoin" || phase === "denied" || phase === "knock_expired" || phase.startsWith("refused:");
|
|
327
|
+
var WAITING_RESUMES = (phase) => phase.startsWith("waiting:");
|
|
327
328
|
function autoRejoinDelayMs(attempt, random = Math.random) {
|
|
328
329
|
const base = Math.min(15e3, 1e3 * 2 ** Math.max(0, attempt));
|
|
329
330
|
return Math.round(base * (0.5 + random()));
|
|
@@ -455,6 +456,10 @@ function createConnectionSupervisor(options) {
|
|
|
455
456
|
rejoinAttempts = 0;
|
|
456
457
|
stopRejoin();
|
|
457
458
|
saveMarker(true);
|
|
459
|
+
} else if (WAITING_RESUMES(phase)) {
|
|
460
|
+
stopRejoin();
|
|
461
|
+
driverRegion = null;
|
|
462
|
+
saveMarker(true);
|
|
458
463
|
} else if (phase === "disconnected") {
|
|
459
464
|
driverRegion = null;
|
|
460
465
|
saveMarker(true);
|
|
@@ -758,6 +763,7 @@ function parseRoomToken(body, roomName) {
|
|
|
758
763
|
connectionIdentity: readString(payload, "connection_identity") ?? personKey,
|
|
759
764
|
connectionKind: readString(payload, "connection_kind") === "companion" ? "companion" : "main",
|
|
760
765
|
secondDeviceChoice,
|
|
766
|
+
secondDeviceRule: readString(payload, "second_device_rule"),
|
|
761
767
|
displayName: readString(payload, "display_name") ?? readString(payload, "displayName") ?? identity,
|
|
762
768
|
expiresAt,
|
|
763
769
|
canPublish: readBoolean(payload, "can_publish", true),
|
|
@@ -1496,6 +1502,12 @@ var JOINABLE_FROM = /* @__PURE__ */ new Set([
|
|
|
1496
1502
|
// allows the press; the server's gate decides whether it knocks or is refused again.
|
|
1497
1503
|
"refused:lobby_rejects"
|
|
1498
1504
|
]);
|
|
1505
|
+
var RETRYABLE_REFUSALS = /* @__PURE__ */ new Set([
|
|
1506
|
+
"refused:locked",
|
|
1507
|
+
"refused:too_early",
|
|
1508
|
+
"refused:meeting_full",
|
|
1509
|
+
"refused:platform_capacity"
|
|
1510
|
+
]);
|
|
1499
1511
|
var NOT_ENDABLE = /* @__PURE__ */ new Set([
|
|
1500
1512
|
"ended",
|
|
1501
1513
|
"record",
|
|
@@ -1561,7 +1573,7 @@ function nextPhase(from, event) {
|
|
|
1561
1573
|
case "link_state":
|
|
1562
1574
|
return from === "resolving" && event.reason !== null && isJoinRefusalReason(event.reason) ? refusalPhase(event.reason) : null;
|
|
1563
1575
|
case "join":
|
|
1564
|
-
return JOINABLE_FROM.has(from) ? "joining" : null;
|
|
1576
|
+
return JOINABLE_FROM.has(from) || RETRYABLE_REFUSALS.has(from) ? "joining" : null;
|
|
1565
1577
|
case "auto_rejoin":
|
|
1566
1578
|
return from === "disconnected" ? "joining" : null;
|
|
1567
1579
|
case "resumed":
|
|
@@ -1986,6 +1998,10 @@ var RPCS = {
|
|
|
1986
1998
|
admitAll: "meet_admit_all",
|
|
1987
1999
|
setLock: "meet_set_lock",
|
|
1988
2000
|
recordConsent: "meet_record_consent",
|
|
2001
|
+
/** One meeting rule as the gate obeys it (the knob ladder, this meeting's rung included). */
|
|
2002
|
+
meetingRule: "meet_policy",
|
|
2003
|
+
/** A host sets one rule for THIS meeting (the `meet_meeting` rung); refusals come back as `{ok:false}`. */
|
|
2004
|
+
setMeetingRule: "meet_policy_set",
|
|
1989
2005
|
/*
|
|
1990
2006
|
* ── MEET WAVE 1 — management (0.8). Every write is a SECURITY DEFINER door
|
|
1991
2007
|
* authorized by the meeting's host or a real iam grant (editor to change,
|
|
@@ -2032,7 +2048,7 @@ var INVITEE_COLUMNS = "id,meeting_id,invitee_user_id,email,display_name,role,rsv
|
|
|
2032
2048
|
var RECORDING_COLUMNS = "id,meeting_id,state,started_at,ended_at,started_by,file_id,failure_reason";
|
|
2033
2049
|
var SEGMENT_COLUMNS = "id,meeting_id,identity,speaker,text,started_at,is_final,metadata";
|
|
2034
2050
|
var NOTE_COLUMNS = "id,meeting_id,text,kind,assignee_user_id,created_at,metadata";
|
|
2035
|
-
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions";
|
|
2051
|
+
var ATTENDEE_COLUMNS = "id,meeting_id,identity,participant_user_id,display_name,avatar_url,role,admission_state,knocked_at,knock_expires_at,is_agent,joined_at,left_at,sessions:metadata->sessions,knock_flag:metadata->>knock_flag";
|
|
2036
2052
|
function projectStays(value) {
|
|
2037
2053
|
if (!Array.isArray(value)) return [];
|
|
2038
2054
|
const stays = [];
|
|
@@ -2432,6 +2448,8 @@ function createMeetRepository(options) {
|
|
|
2432
2448
|
admissionState: states.includes(admission) ? admission : "admitted",
|
|
2433
2449
|
knockedAt: text(row, "knocked_at"),
|
|
2434
2450
|
knockExpiresAt: text(row, "knock_expires_at"),
|
|
2451
|
+
// The list read selects `metadata->>knock_flag`; a Postgres Changes row carries `metadata`.
|
|
2452
|
+
knockFlag: text(row, "knock_flag") ?? (typeof row["metadata"] === "object" && row["metadata"] !== null ? text(row["metadata"], "knock_flag") : null),
|
|
2435
2453
|
isAgent: bool(row, "is_agent", false),
|
|
2436
2454
|
joinedAt: text(row, "joined_at"),
|
|
2437
2455
|
leftAt: text(row, "left_at"),
|
|
@@ -2755,6 +2773,25 @@ function createMeetRepository(options) {
|
|
|
2755
2773
|
);
|
|
2756
2774
|
return projectMeeting(row);
|
|
2757
2775
|
},
|
|
2776
|
+
async meetingRule(meetingId, key) {
|
|
2777
|
+
const response = await db.rpc(RPCS.meetingRule, { p_meeting_id: meetingId, p_key: key });
|
|
2778
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.meetingRule");
|
|
2779
|
+
return response.data;
|
|
2780
|
+
},
|
|
2781
|
+
async setMeetingRule(meetingId, key, value) {
|
|
2782
|
+
const response = await db.rpc(RPCS.setMeetingRule, { p_meeting_id: meetingId, p_key: key, p_value: value });
|
|
2783
|
+
if (response.error !== null) throw normalizeMeetError(response.error, "meet.setMeetingRule");
|
|
2784
|
+
const answer = response.data ?? {};
|
|
2785
|
+
if (answer["ok"] !== true) {
|
|
2786
|
+
throw new MeetError(
|
|
2787
|
+
"forbidden",
|
|
2788
|
+
typeof answer["detail"] === "string" ? answer["detail"] : "This meeting's rule was not changed.",
|
|
2789
|
+
"Only the meeting's host or a co-host sets its rules; an organization can lock a rule for everyone.",
|
|
2790
|
+
answer
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
return answer["effective_value"];
|
|
2794
|
+
},
|
|
2758
2795
|
async transcript(meetingId, limit = 1e3) {
|
|
2759
2796
|
const response = await db.from(TABLES.transcripts).select(SEGMENT_COLUMNS).eq("meeting_id", meetingId).order("started_at", { ascending: true }).order("id", { ascending: true }).limit(limit);
|
|
2760
2797
|
if (response.error !== null) throw normalizeMeetError(response.error, "meet.transcript");
|
|
@@ -3502,11 +3539,13 @@ function consentPending(policy, role, recordingOn, given) {
|
|
|
3502
3539
|
}
|
|
3503
3540
|
|
|
3504
3541
|
// src/core/attendance.ts
|
|
3542
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
3505
3543
|
var IDLE_MEDIA_REGION = {
|
|
3506
3544
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
3507
3545
|
audioOutput: "ok",
|
|
3508
3546
|
notice: null,
|
|
3509
|
-
offer: null
|
|
3547
|
+
offer: null,
|
|
3548
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
3510
3549
|
};
|
|
3511
3550
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
3512
3551
|
var IDLE_MEDIA = {
|
|
@@ -4433,6 +4472,47 @@ function gridColumns(count2) {
|
|
|
4433
4472
|
return 5;
|
|
4434
4473
|
}
|
|
4435
4474
|
|
|
4475
|
+
// src/core/capabilities.ts
|
|
4476
|
+
var IN_APP = [
|
|
4477
|
+
[/Instagram/i, "Instagram"],
|
|
4478
|
+
[/FBAN|FBAV|FB_IAB/i, "Facebook"],
|
|
4479
|
+
[/LinkedInApp/i, "LinkedIn"],
|
|
4480
|
+
[/MicroMessenger/i, "WeChat"],
|
|
4481
|
+
[/Snapchat/i, "Snapchat"],
|
|
4482
|
+
[/\bLine\//i, "LINE"],
|
|
4483
|
+
[/Twitter|TwitterAndroid/i, "X"],
|
|
4484
|
+
[/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
|
|
4485
|
+
];
|
|
4486
|
+
function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
|
|
4487
|
+
if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
|
|
4488
|
+
return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
|
|
4489
|
+
}
|
|
4490
|
+
function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
|
|
4491
|
+
if (scope === null || scope.navigator === void 0) {
|
|
4492
|
+
return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
|
|
4493
|
+
}
|
|
4494
|
+
const ua = scope.navigator.userAgent ?? "";
|
|
4495
|
+
const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
|
|
4496
|
+
const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
|
|
4497
|
+
const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
|
|
4498
|
+
return {
|
|
4499
|
+
canJoin: webrtc,
|
|
4500
|
+
reason: webrtc ? null : "no_webrtc",
|
|
4501
|
+
inAppBrowser,
|
|
4502
|
+
mobile,
|
|
4503
|
+
canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
function unsupportedWords(capabilities) {
|
|
4507
|
+
if (capabilities.inAppBrowser !== null) {
|
|
4508
|
+
return {
|
|
4509
|
+
title: `${capabilities.inAppBrowser}'s browser can't join calls`,
|
|
4510
|
+
body: "Open this link in Chrome, Safari, Edge or Firefox"
|
|
4511
|
+
};
|
|
4512
|
+
}
|
|
4513
|
+
return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
|
|
4514
|
+
}
|
|
4515
|
+
|
|
4436
4516
|
// src/core/room-driver.ts
|
|
4437
4517
|
var DATA_TOPICS = {
|
|
4438
4518
|
reaction: "mx.reaction",
|
|
@@ -4587,7 +4667,15 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4587
4667
|
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4588
4668
|
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4589
4669
|
roster();
|
|
4590
|
-
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished,
|
|
4670
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
|
|
4671
|
+
if (publication.source === Track.Source.ScreenShare) {
|
|
4672
|
+
const ended = () => {
|
|
4673
|
+
void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
|
|
4674
|
+
};
|
|
4675
|
+
publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
|
|
4676
|
+
}
|
|
4677
|
+
roster();
|
|
4678
|
+
}).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4591
4679
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4592
4680
|
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
4593
4681
|
handlers.audioPlayback?.(canPlay);
|
|
@@ -4728,7 +4816,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4728
4816
|
},
|
|
4729
4817
|
async setScreenShareEnabled(enabled) {
|
|
4730
4818
|
try {
|
|
4731
|
-
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
4819
|
+
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
4820
|
+
enabled,
|
|
4821
|
+
enabled ? { audio: true, systemAudio: "include" } : void 0
|
|
4822
|
+
);
|
|
4732
4823
|
} catch (error) {
|
|
4733
4824
|
throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
|
|
4734
4825
|
}
|
|
@@ -4767,8 +4858,13 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4767
4858
|
supportsVideoEffects() {
|
|
4768
4859
|
return videoEffectsSupported();
|
|
4769
4860
|
},
|
|
4861
|
+
screenShareHasAudio() {
|
|
4862
|
+
const target = room;
|
|
4863
|
+
if (target === null) return false;
|
|
4864
|
+
return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
|
|
4865
|
+
},
|
|
4770
4866
|
supportsScreenShare() {
|
|
4771
|
-
return
|
|
4867
|
+
return detectCapabilities().canShareScreen;
|
|
4772
4868
|
},
|
|
4773
4869
|
attach(identity, kind, element) {
|
|
4774
4870
|
const target = requireRoom("attach");
|
|
@@ -4861,7 +4957,8 @@ function lobbyEntryFrom(attendee, now = Date.now()) {
|
|
|
4861
4957
|
knockedAt: attendee.knockedAt ?? attendee.joinedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4862
4958
|
knockExpiresAt: attendee.knockExpiresAt ?? null,
|
|
4863
4959
|
decidedAt: null,
|
|
4864
|
-
decidedBy: null
|
|
4960
|
+
decidedBy: null,
|
|
4961
|
+
flag: attendee.knockFlag ?? null
|
|
4865
4962
|
};
|
|
4866
4963
|
}
|
|
4867
4964
|
function deniedEntryFrom(attendee) {
|
|
@@ -5393,6 +5490,17 @@ function createMeetingSession(options) {
|
|
|
5393
5490
|
async askAgain() {
|
|
5394
5491
|
await options.rejoin?.();
|
|
5395
5492
|
},
|
|
5493
|
+
async meetingRule(key) {
|
|
5494
|
+
const meeting = requireMeeting("meet.meetingRule");
|
|
5495
|
+
return repository.meetingRule(meeting.id, key);
|
|
5496
|
+
},
|
|
5497
|
+
async setMeetingRule(key, value) {
|
|
5498
|
+
requireHostUser("meet.setMeetingRule");
|
|
5499
|
+
const meeting = requireMeeting("meet.setMeetingRule");
|
|
5500
|
+
const effective = await repository.setMeetingRule(meeting.id, key, value);
|
|
5501
|
+
hint();
|
|
5502
|
+
return effective;
|
|
5503
|
+
},
|
|
5396
5504
|
async setLock(locked) {
|
|
5397
5505
|
const by = requireHostUser("meet.setLock");
|
|
5398
5506
|
const meeting = requireMeeting("meet.setLock");
|
|
@@ -6073,7 +6181,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
6073
6181
|
case "safari":
|
|
6074
6182
|
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"];
|
|
6075
6183
|
default:
|
|
6076
|
-
return ["Click the icon
|
|
6184
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
6077
6185
|
}
|
|
6078
6186
|
case "blocked_os": {
|
|
6079
6187
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -6171,7 +6279,7 @@ function createMediaSupervisor(options) {
|
|
|
6171
6279
|
function setRegion(next) {
|
|
6172
6280
|
const current = region();
|
|
6173
6281
|
const merged = { ...current, ...next };
|
|
6174
|
-
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) {
|
|
6282
|
+
if (merged.audioOutput === current.audioOutput && merged.notice === current.notice && merged.offer === current.offer && merged.preview === current.preview && merged.permission.microphone === current.permission.microphone && merged.permission.camera === current.permission.camera) {
|
|
6175
6283
|
return;
|
|
6176
6284
|
}
|
|
6177
6285
|
store.patch({ mediaRegion: merged });
|
|
@@ -6267,7 +6375,7 @@ function createMediaSupervisor(options) {
|
|
|
6267
6375
|
if (permission !== "unknown") setPermission(kind, permission);
|
|
6268
6376
|
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
6269
6377
|
}
|
|
6270
|
-
|
|
6378
|
+
const api = {
|
|
6271
6379
|
start() {
|
|
6272
6380
|
if (started || platform === null) return;
|
|
6273
6381
|
started = true;
|
|
@@ -6289,6 +6397,16 @@ function createMediaSupervisor(options) {
|
|
|
6289
6397
|
started = false;
|
|
6290
6398
|
},
|
|
6291
6399
|
unavailable,
|
|
6400
|
+
async preview(kind, outcome) {
|
|
6401
|
+
api.start();
|
|
6402
|
+
let next;
|
|
6403
|
+
if (typeof outcome === "string") next = { state: outcome, unavailable: null };
|
|
6404
|
+
else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
|
|
6405
|
+
const current = region().preview;
|
|
6406
|
+
const was = current[kind];
|
|
6407
|
+
if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
|
|
6408
|
+
setRegion({ preview: { ...region().preview, [kind]: next } });
|
|
6409
|
+
},
|
|
6292
6410
|
onAudioPlayback(canPlay) {
|
|
6293
6411
|
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
6294
6412
|
},
|
|
@@ -6347,6 +6465,7 @@ function createMediaSupervisor(options) {
|
|
|
6347
6465
|
setRegion({ notice: null });
|
|
6348
6466
|
}
|
|
6349
6467
|
};
|
|
6468
|
+
return api;
|
|
6350
6469
|
}
|
|
6351
6470
|
|
|
6352
6471
|
// src/core/room.ts
|
|
@@ -6508,7 +6627,8 @@ function createRoomEngine(options) {
|
|
|
6508
6627
|
message: normalized.message,
|
|
6509
6628
|
remedy: normalized.remedy,
|
|
6510
6629
|
reason: normalized.reason,
|
|
6511
|
-
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
6630
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
|
|
6631
|
+
stage: "join"
|
|
6512
6632
|
}
|
|
6513
6633
|
});
|
|
6514
6634
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
@@ -6772,7 +6892,7 @@ function createRoomEngine(options) {
|
|
|
6772
6892
|
const { secondDevice: _answered, ...rest } = lastRequest;
|
|
6773
6893
|
lastRequest = rest;
|
|
6774
6894
|
}
|
|
6775
|
-
options.tabClaim?.inRoom(String(request.roomName), String(token.identity));
|
|
6895
|
+
options.tabClaim?.inRoom(String(request.roomName), String(token.identity), token.secondDeviceRule ?? null);
|
|
6776
6896
|
current = token;
|
|
6777
6897
|
lastServerUrl = token.serverUrl;
|
|
6778
6898
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
@@ -6899,6 +7019,10 @@ function createRoomEngine(options) {
|
|
|
6899
7019
|
}
|
|
6900
7020
|
departures.clear();
|
|
6901
7021
|
liveRoster = list;
|
|
7022
|
+
const localNow = list.find((p) => p.isLocal);
|
|
7023
|
+
if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
|
|
7024
|
+
store.patch({ share: NO_SHARE });
|
|
7025
|
+
}
|
|
6902
7026
|
const merged = mergeGhosts(list, ghosts, at);
|
|
6903
7027
|
ghosts = merged.kept;
|
|
6904
7028
|
const people = /* @__PURE__ */ new Map();
|
|
@@ -7080,10 +7204,12 @@ function createRoomEngine(options) {
|
|
|
7080
7204
|
if (claim === void 0 || userId === null) return false;
|
|
7081
7205
|
const before = store.snapshot();
|
|
7082
7206
|
if (before.phase !== "prejoin" || before.secondDevice !== null) return false;
|
|
7083
|
-
|
|
7207
|
+
const person = `user:${String(userId)}`;
|
|
7208
|
+
const found = claim.presenceElsewhere !== void 0 ? await claim.presenceElsewhere(String(roomName), person) : await claim.presentElsewhere(String(roomName), person) ? { rule: null } : null;
|
|
7209
|
+
if (found === null || found.rule === "allow_both") return false;
|
|
7084
7210
|
const now2 = store.snapshot();
|
|
7085
7211
|
if (now2.phase !== "prejoin" || now2.secondDevice !== null) return false;
|
|
7086
|
-
store.patch({ secondDevice: { rule: "ask_switch_or_join" } });
|
|
7212
|
+
store.patch({ secondDevice: { rule: found.rule ?? "ask_switch_or_join" } });
|
|
7087
7213
|
report("info", "meet: this browser already has you in this meeting \u2014 offering the choice before Join");
|
|
7088
7214
|
return true;
|
|
7089
7215
|
},
|
|
@@ -7118,7 +7244,10 @@ function createRoomEngine(options) {
|
|
|
7118
7244
|
try {
|
|
7119
7245
|
await driver.setScreenShareEnabled(enabled);
|
|
7120
7246
|
ambiguousShareDenials = 0;
|
|
7121
|
-
|
|
7247
|
+
const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
|
|
7248
|
+
store.patch({
|
|
7249
|
+
share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
|
|
7250
|
+
});
|
|
7122
7251
|
} catch (error) {
|
|
7123
7252
|
const kind = classifyScreenShareError(error);
|
|
7124
7253
|
if (kind === "ambiguous") {
|
|
@@ -7185,13 +7314,14 @@ function createRoomEngine(options) {
|
|
|
7185
7314
|
return driver?.supportsVideoEffects?.() ?? false;
|
|
7186
7315
|
},
|
|
7187
7316
|
supportsScreenShare() {
|
|
7188
|
-
return driver?.supportsScreenShare?.() ??
|
|
7317
|
+
return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
|
|
7189
7318
|
},
|
|
7190
7319
|
async switchDevice(kind, deviceId) {
|
|
7191
7320
|
if (driver === null) return;
|
|
7192
7321
|
await driver.switchDevice(kind, deviceId);
|
|
7193
7322
|
},
|
|
7194
7323
|
enableAudio: () => media.enableAudio(),
|
|
7324
|
+
previewDevice: (kind, outcome) => media.preview(kind, outcome),
|
|
7195
7325
|
acceptDeviceOffer: () => media.acceptOffer(),
|
|
7196
7326
|
dismissDeviceOffer: () => media.dismissOffer(),
|
|
7197
7327
|
dismissDeviceNotice: () => media.dismissNotice(),
|
|
@@ -8222,6 +8352,21 @@ function observedDevice(track, permission = "unknown", held = null) {
|
|
|
8222
8352
|
const reason = deviceUnavailableReason(track, permission);
|
|
8223
8353
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
8224
8354
|
}
|
|
8355
|
+
function observedPreview(preview, permission = "unknown") {
|
|
8356
|
+
if (permission === "denied") return "unavailable:denied";
|
|
8357
|
+
switch (preview.state) {
|
|
8358
|
+
case "on":
|
|
8359
|
+
return "on";
|
|
8360
|
+
case "off":
|
|
8361
|
+
return "muted_by_me";
|
|
8362
|
+
case "prompting":
|
|
8363
|
+
return "prompting";
|
|
8364
|
+
case "unavailable":
|
|
8365
|
+
return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
|
|
8366
|
+
default:
|
|
8367
|
+
return null;
|
|
8368
|
+
}
|
|
8369
|
+
}
|
|
8225
8370
|
function endedRecord(snapshot, given) {
|
|
8226
8371
|
const record = snapshot?.meeting ?? given;
|
|
8227
8372
|
return record !== null && record.endedAt !== null;
|
|
@@ -8245,18 +8390,22 @@ function observe(input) {
|
|
|
8245
8390
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
8246
8391
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
8247
8392
|
const deviceInPlay = phase === "joining" || inCall;
|
|
8393
|
+
const region = snapshot?.mediaRegion;
|
|
8394
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
8248
8395
|
return {
|
|
8249
8396
|
phase,
|
|
8250
8397
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
8251
8398
|
role,
|
|
8252
|
-
|
|
8399
|
+
// `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
|
|
8400
|
+
// person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
|
|
8401
|
+
connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
|
|
8253
8402
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
8254
8403
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
8255
8404
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
8256
8405
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
8257
8406
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
8258
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : null,
|
|
8259
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : null
|
|
8407
|
+
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera, lockedFor(snapshot, "camera", inCall)) : atPrejoin ? observedPreview(region.preview.camera, region.permission.camera) : null,
|
|
8408
|
+
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone, lockedFor(snapshot, "microphone", inCall)) : atPrejoin ? observedPreview(region.preview.microphone, region.permission.microphone) : null
|
|
8260
8409
|
};
|
|
8261
8410
|
}
|
|
8262
8411
|
function lockedFor(snapshot, kind, inCall) {
|
|
@@ -8336,6 +8485,39 @@ function microphoneNoticeCode(track, permission = "unknown") {
|
|
|
8336
8485
|
if (reason === null) return null;
|
|
8337
8486
|
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
8338
8487
|
}
|
|
8488
|
+
|
|
8489
|
+
// src/core/speaking-detector.ts
|
|
8490
|
+
function createSpeakingWhileMutedDetector(options = {}) {
|
|
8491
|
+
const threshold = options.threshold ?? 0.06;
|
|
8492
|
+
const holdMs = options.holdMs ?? 600;
|
|
8493
|
+
const gapMs = options.gapMs ?? 400;
|
|
8494
|
+
const cooldownMs = options.cooldownMs ?? 2e4;
|
|
8495
|
+
let speechStart = null;
|
|
8496
|
+
let lastLoud = null;
|
|
8497
|
+
let firedAt = null;
|
|
8498
|
+
return {
|
|
8499
|
+
feed(level, at) {
|
|
8500
|
+
if (level >= threshold) {
|
|
8501
|
+
if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
|
|
8502
|
+
lastLoud = at;
|
|
8503
|
+
} else if (lastLoud !== null && at - lastLoud > gapMs) {
|
|
8504
|
+
speechStart = null;
|
|
8505
|
+
lastLoud = null;
|
|
8506
|
+
}
|
|
8507
|
+
if (speechStart === null || at - speechStart < holdMs) return false;
|
|
8508
|
+
if (firedAt !== null && at - firedAt < cooldownMs) return false;
|
|
8509
|
+
firedAt = at;
|
|
8510
|
+
speechStart = null;
|
|
8511
|
+
lastLoud = null;
|
|
8512
|
+
return true;
|
|
8513
|
+
},
|
|
8514
|
+
reset() {
|
|
8515
|
+
speechStart = null;
|
|
8516
|
+
lastLoud = null;
|
|
8517
|
+
firedAt = null;
|
|
8518
|
+
}
|
|
8519
|
+
};
|
|
8520
|
+
}
|
|
8339
8521
|
export {
|
|
8340
8522
|
ATTENDANCE_CSV_HEADER,
|
|
8341
8523
|
CALL_INVITE_KIND,
|
|
@@ -8453,6 +8635,7 @@ export {
|
|
|
8453
8635
|
createReconnectPolicy,
|
|
8454
8636
|
createRecordingController,
|
|
8455
8637
|
createRoomEngine,
|
|
8638
|
+
createSpeakingWhileMutedDetector,
|
|
8456
8639
|
createTokenClient,
|
|
8457
8640
|
createWebDeviceStorage,
|
|
8458
8641
|
createWebEffectsStorage,
|
|
@@ -8461,6 +8644,7 @@ export {
|
|
|
8461
8644
|
createWebRoomTokenStorage,
|
|
8462
8645
|
defaultCaptionLanguage,
|
|
8463
8646
|
detectBrowser,
|
|
8647
|
+
detectCapabilities,
|
|
8464
8648
|
detectOs,
|
|
8465
8649
|
deviceChipLabel,
|
|
8466
8650
|
deviceFixSteps,
|
|
@@ -8485,6 +8669,7 @@ export {
|
|
|
8485
8669
|
isLinkFollower,
|
|
8486
8670
|
isListenOnly,
|
|
8487
8671
|
isLivePhase,
|
|
8672
|
+
isMobileUserAgent,
|
|
8488
8673
|
isNoteTaker,
|
|
8489
8674
|
isObserverRole,
|
|
8490
8675
|
isRealName,
|
|
@@ -8576,6 +8761,7 @@ export {
|
|
|
8576
8761
|
transcriptDocument,
|
|
8577
8762
|
unavailableFrom,
|
|
8578
8763
|
uniqueAttendees,
|
|
8764
|
+
unsupportedWords,
|
|
8579
8765
|
untranslatedLines,
|
|
8580
8766
|
userTopic,
|
|
8581
8767
|
wasRefused,
|