@ai-matrx/meet 0.11.42 → 0.11.44
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-Bf5evcLO.d.cts → collab-BNJNgXFB.d.cts} +10 -1
- package/dist/{collab-Bf5evcLO.d.ts → collab-BNJNgXFB.d.ts} +10 -1
- package/dist/{components-Frpvls4N.d.ts → components-BDtGzHf4.d.ts} +4 -2
- package/dist/{components-roCEgyFH.d.cts → components-BxK993iW.d.cts} +4 -2
- package/dist/core.cjs +328 -25
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +152 -7
- package/dist/core.d.ts +152 -7
- package/dist/core.js +328 -25
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +328 -25
- 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 +328 -25
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +839 -77
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +22 -5
- package/dist/react.d.ts +22 -5
- package/dist/react.js +1225 -463
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +581 -60
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +6 -3
- package/dist/skins/meet.d.ts +6 -3
- package/dist/skins/meet.js +908 -387
- package/dist/skins/meet.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -110,6 +110,7 @@ __export(src_exports, {
|
|
|
110
110
|
autoAssign: () => autoAssign,
|
|
111
111
|
autoRejoinDelayMs: () => autoRejoinDelayMs,
|
|
112
112
|
availableActivities: () => availableActivities,
|
|
113
|
+
breakoutClosingMs: () => breakoutClosingMs,
|
|
113
114
|
breakoutRemainingMs: () => breakoutRemainingMs,
|
|
114
115
|
callInviteAction: () => callInviteAction,
|
|
115
116
|
callInviteText: () => callInviteText,
|
|
@@ -145,6 +146,7 @@ __export(src_exports, {
|
|
|
145
146
|
createReconnectPolicy: () => createReconnectPolicy,
|
|
146
147
|
createRecordingController: () => createRecordingController,
|
|
147
148
|
createRoomEngine: () => createRoomEngine,
|
|
149
|
+
createSpeakingWhileMutedDetector: () => createSpeakingWhileMutedDetector,
|
|
148
150
|
createTokenClient: () => createTokenClient,
|
|
149
151
|
createWebDeviceStorage: () => createWebDeviceStorage,
|
|
150
152
|
createWebEffectsStorage: () => createWebEffectsStorage,
|
|
@@ -153,6 +155,7 @@ __export(src_exports, {
|
|
|
153
155
|
createWebRoomTokenStorage: () => createWebRoomTokenStorage,
|
|
154
156
|
defaultCaptionLanguage: () => defaultCaptionLanguage,
|
|
155
157
|
detectBrowser: () => detectBrowser,
|
|
158
|
+
detectCapabilities: () => detectCapabilities,
|
|
156
159
|
detectOs: () => detectOs,
|
|
157
160
|
deviceChipLabel: () => deviceChipLabel,
|
|
158
161
|
deviceFixSteps: () => deviceFixSteps,
|
|
@@ -177,6 +180,7 @@ __export(src_exports, {
|
|
|
177
180
|
isLinkFollower: () => isLinkFollower,
|
|
178
181
|
isListenOnly: () => isListenOnly,
|
|
179
182
|
isLivePhase: () => isLivePhase,
|
|
183
|
+
isMobileUserAgent: () => isMobileUserAgent,
|
|
180
184
|
isNoteTaker: () => isNoteTaker,
|
|
181
185
|
isObserverRole: () => isObserverRole,
|
|
182
186
|
isRealName: () => isRealName,
|
|
@@ -268,6 +272,7 @@ __export(src_exports, {
|
|
|
268
272
|
transcriptDocument: () => transcriptDocument,
|
|
269
273
|
unavailableFrom: () => unavailableFrom,
|
|
270
274
|
uniqueAttendees: () => uniqueAttendees,
|
|
275
|
+
unsupportedWords: () => unsupportedWords,
|
|
271
276
|
untranslatedLines: () => untranslatedLines,
|
|
272
277
|
userTopic: () => userTopic,
|
|
273
278
|
wasRefused: () => wasRefused,
|
|
@@ -911,6 +916,8 @@ var MEET_ROUTES = {
|
|
|
911
916
|
noteTakerStop: "/api/v1/meet/agent/stop",
|
|
912
917
|
/** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
|
|
913
918
|
intelligenceAsk: "/api/v1/meet/intelligence/ask",
|
|
919
|
+
/** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
|
|
920
|
+
noteTakerCaptions: "/api/v1/meet/agent/captions",
|
|
914
921
|
/** A host ends the meeting for everyone. Served by MRI-A6. */
|
|
915
922
|
meetingEnd: "/api/v1/meet/end",
|
|
916
923
|
/**
|
|
@@ -1057,7 +1064,8 @@ function parseRoomToken(body, roomName) {
|
|
|
1057
1064
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
1058
1065
|
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
1059
1066
|
connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
|
|
1060
|
-
leavePolicy: leavePolicyFrom(payload["leave_policy"])
|
|
1067
|
+
leavePolicy: leavePolicyFrom(payload["leave_policy"]),
|
|
1068
|
+
canReclaim: payload["can_reclaim"] === true
|
|
1061
1069
|
};
|
|
1062
1070
|
}
|
|
1063
1071
|
function createTokenClient(options) {
|
|
@@ -1519,6 +1527,42 @@ function createMeetAi(options) {
|
|
|
1519
1527
|
},
|
|
1520
1528
|
"meet.ai.startNoteTaker"
|
|
1521
1529
|
),
|
|
1530
|
+
requestCaptions: async ({ meetingId }) => {
|
|
1531
|
+
const roomToken = options.resolveRoomToken?.() ?? null;
|
|
1532
|
+
const operation = "meet.ai.requestCaptions";
|
|
1533
|
+
try {
|
|
1534
|
+
const response = await send({
|
|
1535
|
+
path: MEET_ROUTES.noteTakerCaptions,
|
|
1536
|
+
body: { meeting_id: meetingId },
|
|
1537
|
+
operation,
|
|
1538
|
+
requireAuth: roomToken === null,
|
|
1539
|
+
...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {}
|
|
1540
|
+
});
|
|
1541
|
+
if (!response.ok) {
|
|
1542
|
+
options.store.patch({ captionsRefusal: await refusalFor(response, operation) });
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
let payload = null;
|
|
1546
|
+
try {
|
|
1547
|
+
payload = await response.json();
|
|
1548
|
+
} catch {
|
|
1549
|
+
payload = null;
|
|
1550
|
+
}
|
|
1551
|
+
const row = typeof payload === "object" && payload !== null ? payload : {};
|
|
1552
|
+
const detail = typeof row["detail"] === "string" ? row["detail"] : "";
|
|
1553
|
+
options.store.patch({
|
|
1554
|
+
captionsRefusal: row["dispatched"] === true ? null : { status: 200, reason: detail, remedy: "" }
|
|
1555
|
+
});
|
|
1556
|
+
} catch (error) {
|
|
1557
|
+
options.store.patch({
|
|
1558
|
+
captionsRefusal: {
|
|
1559
|
+
status: 0,
|
|
1560
|
+
reason: error.message,
|
|
1561
|
+
remedy: error.remedy ?? ""
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
},
|
|
1522
1566
|
stopNoteTaker: ({ meetingId }) => dispatch(
|
|
1523
1567
|
MEET_ROUTES.noteTakerStop,
|
|
1524
1568
|
{ meeting_id: meetingId },
|
|
@@ -1557,7 +1601,7 @@ function exactNoteTakerParticipant(meeting, participants) {
|
|
|
1557
1601
|
}
|
|
1558
1602
|
function noteTakerStatus(args) {
|
|
1559
1603
|
const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
|
|
1560
|
-
const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
|
|
1604
|
+
const raw = args.meeting?.metadata?.[NOTE_TAKER_METADATA_KEY];
|
|
1561
1605
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
1562
1606
|
const recorded = readState(row["state"]);
|
|
1563
1607
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
@@ -3290,6 +3334,8 @@ function planFromRow(row) {
|
|
|
3290
3334
|
minutes: num2(row["minutes"]) ?? 0,
|
|
3291
3335
|
openedAt: str(row["opened_at"]),
|
|
3292
3336
|
endsAt: str(row["ends_at"]),
|
|
3337
|
+
closingAt: str(row["closing_at"]),
|
|
3338
|
+
moveMode: row["move_mode"] === "auto" ? "auto" : "invite",
|
|
3293
3339
|
closedAt: str(row["closed_at"]),
|
|
3294
3340
|
broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
|
|
3295
3341
|
};
|
|
@@ -3441,9 +3487,17 @@ function autoAssign(identities, rooms, exclude = []) {
|
|
|
3441
3487
|
return out;
|
|
3442
3488
|
}
|
|
3443
3489
|
function breakoutRemainingMs(plan, now) {
|
|
3444
|
-
if (plan === null || plan.state !== "open"
|
|
3445
|
-
const
|
|
3446
|
-
|
|
3490
|
+
if (plan === null || plan.state !== "open") return null;
|
|
3491
|
+
const ends = [plan.endsAt, plan.closingAt].map((stamp) => stamp === null ? Number.NaN : Date.parse(stamp)).filter((time) => !Number.isNaN(time));
|
|
3492
|
+
if (ends.length === 0) return null;
|
|
3493
|
+
return Math.max(0, Math.min(...ends) - now);
|
|
3494
|
+
}
|
|
3495
|
+
var BREAKOUT_COUNTDOWN_MS = 3e4;
|
|
3496
|
+
function breakoutClosingMs(plan, now) {
|
|
3497
|
+
if (plan === null || plan.state !== "open") return null;
|
|
3498
|
+
const remaining = breakoutRemainingMs(plan, now);
|
|
3499
|
+
if (remaining === null) return null;
|
|
3500
|
+
return plan.closingAt !== null || remaining <= BREAKOUT_COUNTDOWN_MS ? remaining : null;
|
|
3447
3501
|
}
|
|
3448
3502
|
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
3449
3503
|
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
@@ -3690,12 +3744,55 @@ function recordingLabel(state) {
|
|
|
3690
3744
|
}
|
|
3691
3745
|
}
|
|
3692
3746
|
|
|
3747
|
+
// src/core/live-policy.ts
|
|
3748
|
+
var DEFAULT_LIVE_POLICY = {
|
|
3749
|
+
chatMode: "everyone",
|
|
3750
|
+
avLock: "off",
|
|
3751
|
+
timeLimitMinutes: 0,
|
|
3752
|
+
timeLimitWarningMinutes: 5,
|
|
3753
|
+
recordingConsent: "notice"
|
|
3754
|
+
};
|
|
3755
|
+
function oneOf(value, allowed, fallback) {
|
|
3756
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
3757
|
+
}
|
|
3758
|
+
function whole(value, fallback) {
|
|
3759
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
3760
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
3761
|
+
}
|
|
3762
|
+
function livePolicyFrom(raw) {
|
|
3763
|
+
const o = typeof raw === "object" && raw !== null ? raw : {};
|
|
3764
|
+
return {
|
|
3765
|
+
chatMode: oneOf(o["chat_mode"], ["everyone", "hosts_only", "off"], DEFAULT_LIVE_POLICY.chatMode),
|
|
3766
|
+
avLock: oneOf(o["attendee_av_lock"], ["off", "mic", "camera", "both"], DEFAULT_LIVE_POLICY.avLock),
|
|
3767
|
+
timeLimitMinutes: whole(o["time_limit_minutes"], 0),
|
|
3768
|
+
timeLimitWarningMinutes: whole(o["time_limit_warning_minutes"], DEFAULT_LIVE_POLICY.timeLimitWarningMinutes),
|
|
3769
|
+
recordingConsent: oneOf(
|
|
3770
|
+
o["recording_consent"],
|
|
3771
|
+
["notice", "ok_or_leave", "view_only_until_agree"],
|
|
3772
|
+
DEFAULT_LIVE_POLICY.recordingConsent
|
|
3773
|
+
)
|
|
3774
|
+
};
|
|
3775
|
+
}
|
|
3776
|
+
function samePolicy(a, b) {
|
|
3777
|
+
return a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent;
|
|
3778
|
+
}
|
|
3779
|
+
var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
3780
|
+
function isLockedFor(policy, role, kind) {
|
|
3781
|
+
if (MODERATORS.has(role)) return false;
|
|
3782
|
+
return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
|
|
3783
|
+
}
|
|
3784
|
+
function consentPending(policy, role, recordingOn, given) {
|
|
3785
|
+
return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3693
3788
|
// src/core/attendance.ts
|
|
3789
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
3694
3790
|
var IDLE_MEDIA_REGION = {
|
|
3695
3791
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
3696
3792
|
audioOutput: "ok",
|
|
3697
3793
|
notice: null,
|
|
3698
|
-
offer: null
|
|
3794
|
+
offer: null,
|
|
3795
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
3699
3796
|
};
|
|
3700
3797
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
3701
3798
|
var IDLE_MEDIA = {
|
|
@@ -3760,6 +3857,11 @@ var EMPTY = {
|
|
|
3760
3857
|
presenter: null,
|
|
3761
3858
|
waitingCount: 0,
|
|
3762
3859
|
leavePolicy: DEFAULT_LEAVE_POLICY,
|
|
3860
|
+
livePolicy: DEFAULT_LIVE_POLICY,
|
|
3861
|
+
canReclaim: false,
|
|
3862
|
+
recordingConsentGiven: false,
|
|
3863
|
+
sessionStartedAt: null,
|
|
3864
|
+
mutedByHost: false,
|
|
3763
3865
|
moderationNotice: null,
|
|
3764
3866
|
actionNotice: null,
|
|
3765
3867
|
privateChatRequest: null,
|
|
@@ -3769,6 +3871,8 @@ var EMPTY = {
|
|
|
3769
3871
|
names: {},
|
|
3770
3872
|
wrapUp: null,
|
|
3771
3873
|
noteTakerRefusal: null,
|
|
3874
|
+
captionsOn: false,
|
|
3875
|
+
captionsRefusal: null,
|
|
3772
3876
|
media: IDLE_MEDIA,
|
|
3773
3877
|
mediaRegion: IDLE_MEDIA_REGION,
|
|
3774
3878
|
failure: null,
|
|
@@ -3973,7 +4077,12 @@ function createMeetStore(initial = {}) {
|
|
|
3973
4077
|
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
3974
4078
|
return;
|
|
3975
4079
|
}
|
|
3976
|
-
commit({
|
|
4080
|
+
commit({
|
|
4081
|
+
...state,
|
|
4082
|
+
media: { ...state.media, [kind]: next },
|
|
4083
|
+
// The person turned their own microphone on again: the host's mute is over.
|
|
4084
|
+
...kind === "microphone" && next.enabled && state.mutedByHost ? { mutedByHost: false } : {}
|
|
4085
|
+
});
|
|
3977
4086
|
},
|
|
3978
4087
|
reset() {
|
|
3979
4088
|
const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
|
|
@@ -4199,10 +4308,11 @@ function moderationText(kind, payload) {
|
|
|
4199
4308
|
switch (kind) {
|
|
4200
4309
|
case "muted": {
|
|
4201
4310
|
const source = text2(payload["source"]) ?? "microphone";
|
|
4202
|
-
|
|
4311
|
+
const who = payload["by_role"] === "host" || payload["by_role"] === "cohost" ? "the host" : by;
|
|
4312
|
+
return source === "microphone" ? `You've been muted by ${who}.` : source === "camera" ? `Your camera was turned off by ${who}.` : `Your screen share was stopped by ${who}.`;
|
|
4203
4313
|
}
|
|
4204
4314
|
case "ask-unmute":
|
|
4205
|
-
return `${by}
|
|
4315
|
+
return `${by === "The host" ? "The host" : by} asked you to unmute.`;
|
|
4206
4316
|
case "removed":
|
|
4207
4317
|
return `${by} removed you from this meeting.`;
|
|
4208
4318
|
default:
|
|
@@ -4228,7 +4338,7 @@ function applyModeration(store, payload, now) {
|
|
|
4228
4338
|
grants: { ...snapshot.grants, role },
|
|
4229
4339
|
moderationNotice: {
|
|
4230
4340
|
kind: "role",
|
|
4231
|
-
text: role === "host" ? "You are now the host." : role === "cohost" ? "You
|
|
4341
|
+
text: role === "host" ? "You are now the host." : role === "cohost" ? "You're now a co-host." : role === "presenter" ? "You're now a presenter." : snapshot.grants.role === "presenter" ? "You're now a participant: you can no longer present." : "You're now a participant: you are no longer a co-host.",
|
|
4232
4342
|
at: now()
|
|
4233
4343
|
}
|
|
4234
4344
|
});
|
|
@@ -4240,7 +4350,10 @@ function applyModeration(store, payload, now) {
|
|
|
4240
4350
|
case "removed": {
|
|
4241
4351
|
const sentence = moderationText(kind, payload);
|
|
4242
4352
|
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
4243
|
-
store.patch({
|
|
4353
|
+
store.patch({
|
|
4354
|
+
moderationNotice: { kind, text: sentence, at: now() },
|
|
4355
|
+
...kind === "muted" && (text2(payload["source"]) ?? "microphone") === "microphone" ? { mutedByHost: true } : {}
|
|
4356
|
+
});
|
|
4244
4357
|
return HANDLED;
|
|
4245
4358
|
}
|
|
4246
4359
|
default:
|
|
@@ -4606,6 +4719,47 @@ function gridColumns(count2) {
|
|
|
4606
4719
|
return 5;
|
|
4607
4720
|
}
|
|
4608
4721
|
|
|
4722
|
+
// src/core/capabilities.ts
|
|
4723
|
+
var IN_APP = [
|
|
4724
|
+
[/Instagram/i, "Instagram"],
|
|
4725
|
+
[/FBAN|FBAV|FB_IAB/i, "Facebook"],
|
|
4726
|
+
[/LinkedInApp/i, "LinkedIn"],
|
|
4727
|
+
[/MicroMessenger/i, "WeChat"],
|
|
4728
|
+
[/Snapchat/i, "Snapchat"],
|
|
4729
|
+
[/\bLine\//i, "LINE"],
|
|
4730
|
+
[/Twitter|TwitterAndroid/i, "X"],
|
|
4731
|
+
[/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
|
|
4732
|
+
];
|
|
4733
|
+
function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
|
|
4734
|
+
if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
|
|
4735
|
+
return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
|
|
4736
|
+
}
|
|
4737
|
+
function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
|
|
4738
|
+
if (scope === null || scope.navigator === void 0) {
|
|
4739
|
+
return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
|
|
4740
|
+
}
|
|
4741
|
+
const ua = scope.navigator.userAgent ?? "";
|
|
4742
|
+
const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
|
|
4743
|
+
const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
|
|
4744
|
+
const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
|
|
4745
|
+
return {
|
|
4746
|
+
canJoin: webrtc,
|
|
4747
|
+
reason: webrtc ? null : "no_webrtc",
|
|
4748
|
+
inAppBrowser,
|
|
4749
|
+
mobile,
|
|
4750
|
+
canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
function unsupportedWords(capabilities) {
|
|
4754
|
+
if (capabilities.inAppBrowser !== null) {
|
|
4755
|
+
return {
|
|
4756
|
+
title: `${capabilities.inAppBrowser}'s browser can't join calls`,
|
|
4757
|
+
body: "Open this link in Chrome, Safari, Edge or Firefox"
|
|
4758
|
+
};
|
|
4759
|
+
}
|
|
4760
|
+
return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4609
4763
|
// src/core/room-driver.ts
|
|
4610
4764
|
var DATA_TOPICS = {
|
|
4611
4765
|
reaction: "mx.reaction",
|
|
@@ -4760,7 +4914,15 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4760
4914
|
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4761
4915
|
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4762
4916
|
roster();
|
|
4763
|
-
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished,
|
|
4917
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
|
|
4918
|
+
if (publication.source === Track.Source.ScreenShare) {
|
|
4919
|
+
const ended = () => {
|
|
4920
|
+
void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
|
|
4921
|
+
};
|
|
4922
|
+
publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
|
|
4923
|
+
}
|
|
4924
|
+
roster();
|
|
4925
|
+
}).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4764
4926
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4765
4927
|
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
4766
4928
|
handlers.audioPlayback?.(canPlay);
|
|
@@ -4901,7 +5063,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4901
5063
|
},
|
|
4902
5064
|
async setScreenShareEnabled(enabled) {
|
|
4903
5065
|
try {
|
|
4904
|
-
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
5066
|
+
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
5067
|
+
enabled,
|
|
5068
|
+
enabled ? { audio: true, systemAudio: "include" } : void 0
|
|
5069
|
+
);
|
|
4905
5070
|
} catch (error) {
|
|
4906
5071
|
throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
|
|
4907
5072
|
}
|
|
@@ -4940,8 +5105,13 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4940
5105
|
supportsVideoEffects() {
|
|
4941
5106
|
return videoEffectsSupported();
|
|
4942
5107
|
},
|
|
5108
|
+
screenShareHasAudio() {
|
|
5109
|
+
const target = room;
|
|
5110
|
+
if (target === null) return false;
|
|
5111
|
+
return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
|
|
5112
|
+
},
|
|
4943
5113
|
supportsScreenShare() {
|
|
4944
|
-
return
|
|
5114
|
+
return detectCapabilities().canShareScreen;
|
|
4945
5115
|
},
|
|
4946
5116
|
attach(identity, kind, element) {
|
|
4947
5117
|
const target = requireRoom("attach");
|
|
@@ -5062,6 +5232,7 @@ function applyDoorRow(store, attendee) {
|
|
|
5062
5232
|
function decidedState(state) {
|
|
5063
5233
|
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
5064
5234
|
}
|
|
5235
|
+
var LIVE_POLICY_POLL_MS = 3e3;
|
|
5065
5236
|
function createMeetingSession(options) {
|
|
5066
5237
|
const { manager, repository, store, identity } = options;
|
|
5067
5238
|
let channel = null;
|
|
@@ -5203,6 +5374,46 @@ function createMeetingSession(options) {
|
|
|
5203
5374
|
stopWaiting();
|
|
5204
5375
|
}
|
|
5205
5376
|
}
|
|
5377
|
+
let policyTimer = null;
|
|
5378
|
+
let policyReading = false;
|
|
5379
|
+
async function readLivePolicy() {
|
|
5380
|
+
const meeting = store.snapshot().meeting;
|
|
5381
|
+
const pass = options.resolvePass?.() ?? null;
|
|
5382
|
+
if (meeting === null || pass === null || policyReading) return;
|
|
5383
|
+
policyReading = true;
|
|
5384
|
+
try {
|
|
5385
|
+
const response = await options.api.request({
|
|
5386
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
5387
|
+
method: "GET",
|
|
5388
|
+
requireAuth: false,
|
|
5389
|
+
operation: "meet.state(policy)",
|
|
5390
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
5391
|
+
});
|
|
5392
|
+
if (!response.ok) return;
|
|
5393
|
+
const read = await response.json();
|
|
5394
|
+
const next = livePolicyFrom(read.live_policy);
|
|
5395
|
+
const started = read.session_started_at ? Date.parse(read.session_started_at) : NaN;
|
|
5396
|
+
const snapshot = store.snapshot();
|
|
5397
|
+
const startedAt = Number.isFinite(started) ? started : snapshot.sessionStartedAt;
|
|
5398
|
+
if (!samePolicy(snapshot.livePolicy, next) || startedAt !== snapshot.sessionStartedAt) {
|
|
5399
|
+
store.patch({ livePolicy: next, sessionStartedAt: startedAt });
|
|
5400
|
+
}
|
|
5401
|
+
} catch {
|
|
5402
|
+
} finally {
|
|
5403
|
+
policyReading = false;
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
function syncLivePolicyTimer() {
|
|
5407
|
+
const inCall = store.snapshot().phase === "in_call";
|
|
5408
|
+
if (inCall && policyTimer === null) {
|
|
5409
|
+
void readLivePolicy();
|
|
5410
|
+
policyTimer = setInterval(() => void readLivePolicy(), LIVE_POLICY_POLL_MS);
|
|
5411
|
+
} else if (!inCall && policyTimer !== null) {
|
|
5412
|
+
clearInterval(policyTimer);
|
|
5413
|
+
policyTimer = null;
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
const unsubscribePolicy = store.subscribe(syncLivePolicyTimer);
|
|
5206
5417
|
async function readState2() {
|
|
5207
5418
|
if (reading !== null) return reading;
|
|
5208
5419
|
const meeting = store.snapshot().meeting;
|
|
@@ -5589,6 +5800,9 @@ function createMeetingSession(options) {
|
|
|
5589
5800
|
},
|
|
5590
5801
|
dispose() {
|
|
5591
5802
|
stopWaiting();
|
|
5803
|
+
unsubscribePolicy();
|
|
5804
|
+
if (policyTimer !== null) clearInterval(policyTimer);
|
|
5805
|
+
policyTimer = null;
|
|
5592
5806
|
channel?.close();
|
|
5593
5807
|
channel = null;
|
|
5594
5808
|
meetingChannel?.close();
|
|
@@ -6202,7 +6416,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
6202
6416
|
case "safari":
|
|
6203
6417
|
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"];
|
|
6204
6418
|
default:
|
|
6205
|
-
return ["Click the icon
|
|
6419
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
6206
6420
|
}
|
|
6207
6421
|
case "blocked_os": {
|
|
6208
6422
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -6300,7 +6514,7 @@ function createMediaSupervisor(options) {
|
|
|
6300
6514
|
function setRegion(next) {
|
|
6301
6515
|
const current = region();
|
|
6302
6516
|
const merged = { ...current, ...next };
|
|
6303
|
-
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) {
|
|
6517
|
+
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) {
|
|
6304
6518
|
return;
|
|
6305
6519
|
}
|
|
6306
6520
|
store.patch({ mediaRegion: merged });
|
|
@@ -6396,7 +6610,7 @@ function createMediaSupervisor(options) {
|
|
|
6396
6610
|
if (permission !== "unknown") setPermission(kind, permission);
|
|
6397
6611
|
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
6398
6612
|
}
|
|
6399
|
-
|
|
6613
|
+
const api = {
|
|
6400
6614
|
start() {
|
|
6401
6615
|
if (started || platform === null) return;
|
|
6402
6616
|
started = true;
|
|
@@ -6418,6 +6632,16 @@ function createMediaSupervisor(options) {
|
|
|
6418
6632
|
started = false;
|
|
6419
6633
|
},
|
|
6420
6634
|
unavailable,
|
|
6635
|
+
async preview(kind, outcome) {
|
|
6636
|
+
api.start();
|
|
6637
|
+
let next;
|
|
6638
|
+
if (typeof outcome === "string") next = { state: outcome, unavailable: null };
|
|
6639
|
+
else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
|
|
6640
|
+
const current = region().preview;
|
|
6641
|
+
const was = current[kind];
|
|
6642
|
+
if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
|
|
6643
|
+
setRegion({ preview: { ...region().preview, [kind]: next } });
|
|
6644
|
+
},
|
|
6421
6645
|
onAudioPlayback(canPlay) {
|
|
6422
6646
|
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
6423
6647
|
},
|
|
@@ -6476,6 +6700,7 @@ function createMediaSupervisor(options) {
|
|
|
6476
6700
|
setRegion({ notice: null });
|
|
6477
6701
|
}
|
|
6478
6702
|
};
|
|
6703
|
+
return api;
|
|
6479
6704
|
}
|
|
6480
6705
|
|
|
6481
6706
|
// src/core/room.ts
|
|
@@ -6637,7 +6862,8 @@ function createRoomEngine(options) {
|
|
|
6637
6862
|
message: normalized.message,
|
|
6638
6863
|
remedy: normalized.remedy,
|
|
6639
6864
|
reason: normalized.reason,
|
|
6640
|
-
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
6865
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
|
|
6866
|
+
stage: "join"
|
|
6641
6867
|
}
|
|
6642
6868
|
});
|
|
6643
6869
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
@@ -6906,6 +7132,7 @@ function createRoomEngine(options) {
|
|
|
6906
7132
|
lastServerUrl = token.serverUrl;
|
|
6907
7133
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6908
7134
|
if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
|
|
7135
|
+
store.patch({ canReclaim: token.canReclaim === true });
|
|
6909
7136
|
store.patch({
|
|
6910
7137
|
localIdentity: token.identity,
|
|
6911
7138
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -7027,6 +7254,10 @@ function createRoomEngine(options) {
|
|
|
7027
7254
|
}
|
|
7028
7255
|
departures.clear();
|
|
7029
7256
|
liveRoster = list;
|
|
7257
|
+
const localNow = list.find((p) => p.isLocal);
|
|
7258
|
+
if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
|
|
7259
|
+
store.patch({ share: NO_SHARE });
|
|
7260
|
+
}
|
|
7030
7261
|
const merged = mergeGhosts(list, ghosts, at);
|
|
7031
7262
|
ghosts = merged.kept;
|
|
7032
7263
|
const people = /* @__PURE__ */ new Map();
|
|
@@ -7246,7 +7477,10 @@ function createRoomEngine(options) {
|
|
|
7246
7477
|
try {
|
|
7247
7478
|
await driver.setScreenShareEnabled(enabled);
|
|
7248
7479
|
ambiguousShareDenials = 0;
|
|
7249
|
-
|
|
7480
|
+
const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
|
|
7481
|
+
store.patch({
|
|
7482
|
+
share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
|
|
7483
|
+
});
|
|
7250
7484
|
} catch (error) {
|
|
7251
7485
|
const kind = classifyScreenShareError(error);
|
|
7252
7486
|
if (kind === "ambiguous") {
|
|
@@ -7313,13 +7547,14 @@ function createRoomEngine(options) {
|
|
|
7313
7547
|
return driver?.supportsVideoEffects?.() ?? false;
|
|
7314
7548
|
},
|
|
7315
7549
|
supportsScreenShare() {
|
|
7316
|
-
return driver?.supportsScreenShare?.() ??
|
|
7550
|
+
return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
|
|
7317
7551
|
},
|
|
7318
7552
|
async switchDevice(kind, deviceId) {
|
|
7319
7553
|
if (driver === null) return;
|
|
7320
7554
|
await driver.switchDevice(kind, deviceId);
|
|
7321
7555
|
},
|
|
7322
7556
|
enableAudio: () => media.enableAudio(),
|
|
7557
|
+
previewDevice: (kind, outcome) => media.preview(kind, outcome),
|
|
7323
7558
|
acceptDeviceOffer: () => media.acceptOffer(),
|
|
7324
7559
|
dismissDeviceOffer: () => media.dismissOffer(),
|
|
7325
7560
|
dismissDeviceNotice: () => media.dismissNotice(),
|
|
@@ -7405,8 +7640,12 @@ var MODERATION_ACTIONS = [
|
|
|
7405
7640
|
"lower_hand",
|
|
7406
7641
|
"lower_all_hands",
|
|
7407
7642
|
"make_cohost",
|
|
7643
|
+
"make_presenter",
|
|
7644
|
+
"revoke_presenter",
|
|
7645
|
+
"set_av_lock",
|
|
7408
7646
|
"revoke_cohost",
|
|
7409
7647
|
"transfer_host",
|
|
7648
|
+
"reclaim_host",
|
|
7410
7649
|
"rename",
|
|
7411
7650
|
"remove",
|
|
7412
7651
|
"report"
|
|
@@ -8336,14 +8575,31 @@ function takeMoveRequest(store) {
|
|
|
8336
8575
|
|
|
8337
8576
|
// src/core/observation.ts
|
|
8338
8577
|
function observedRole(role, standing) {
|
|
8339
|
-
if (role === "host" || role === "cohost") return role;
|
|
8578
|
+
if (role === "host" || role === "cohost" || role === "presenter") return role;
|
|
8340
8579
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
8341
8580
|
}
|
|
8342
|
-
function observedDevice(track, permission = "unknown") {
|
|
8581
|
+
function observedDevice(track, permission = "unknown", held = null) {
|
|
8582
|
+
if (held === "locked") return "locked";
|
|
8343
8583
|
if (track.enabled) return "on";
|
|
8584
|
+
if (held === "muted_by_host") return "muted_by_host";
|
|
8344
8585
|
const reason = deviceUnavailableReason(track, permission);
|
|
8345
8586
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
8346
8587
|
}
|
|
8588
|
+
function observedPreview(preview, permission = "unknown") {
|
|
8589
|
+
if (permission === "denied") return "unavailable:denied";
|
|
8590
|
+
switch (preview.state) {
|
|
8591
|
+
case "on":
|
|
8592
|
+
return "on";
|
|
8593
|
+
case "off":
|
|
8594
|
+
return "muted_by_me";
|
|
8595
|
+
case "prompting":
|
|
8596
|
+
return "prompting";
|
|
8597
|
+
case "unavailable":
|
|
8598
|
+
return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
|
|
8599
|
+
default:
|
|
8600
|
+
return null;
|
|
8601
|
+
}
|
|
8602
|
+
}
|
|
8347
8603
|
function endedRecord(snapshot, given) {
|
|
8348
8604
|
const record = snapshot?.meeting ?? given;
|
|
8349
8605
|
return record !== null && record.endedAt !== null;
|
|
@@ -8367,20 +8623,34 @@ function observe(input) {
|
|
|
8367
8623
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
8368
8624
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
8369
8625
|
const deviceInPlay = phase === "joining" || inCall;
|
|
8626
|
+
const region = snapshot?.mediaRegion;
|
|
8627
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
8370
8628
|
return {
|
|
8371
8629
|
phase,
|
|
8372
8630
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
8373
8631
|
role,
|
|
8374
|
-
|
|
8632
|
+
// `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
|
|
8633
|
+
// person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
|
|
8634
|
+
connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
|
|
8375
8635
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
8376
8636
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
8377
8637
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
8378
8638
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
8379
8639
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
8380
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
8381
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
8640
|
+
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,
|
|
8641
|
+
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
|
|
8382
8642
|
};
|
|
8383
8643
|
}
|
|
8644
|
+
function lockedFor(snapshot, kind, inCall) {
|
|
8645
|
+
if (!inCall) return null;
|
|
8646
|
+
const role = effectiveRole(snapshot);
|
|
8647
|
+
if (isLockedFor(snapshot.livePolicy, role, kind)) return "locked";
|
|
8648
|
+
if (snapshot.livePolicy.recordingConsent === "view_only_until_agree" && consentPending(snapshot.livePolicy, role, snapshot.recording === "recording", snapshot.recordingConsentGiven)) {
|
|
8649
|
+
return "locked";
|
|
8650
|
+
}
|
|
8651
|
+
if (kind === "microphone" && snapshot.mutedByHost) return "muted_by_host";
|
|
8652
|
+
return null;
|
|
8653
|
+
}
|
|
8384
8654
|
function staticObservation(phase, reason = null) {
|
|
8385
8655
|
return {
|
|
8386
8656
|
phase,
|
|
@@ -8448,4 +8718,37 @@ function microphoneNoticeCode(track, permission = "unknown") {
|
|
|
8448
8718
|
if (reason === null) return null;
|
|
8449
8719
|
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
8450
8720
|
}
|
|
8721
|
+
|
|
8722
|
+
// src/core/speaking-detector.ts
|
|
8723
|
+
function createSpeakingWhileMutedDetector(options = {}) {
|
|
8724
|
+
const threshold = options.threshold ?? 0.06;
|
|
8725
|
+
const holdMs = options.holdMs ?? 600;
|
|
8726
|
+
const gapMs = options.gapMs ?? 400;
|
|
8727
|
+
const cooldownMs = options.cooldownMs ?? 2e4;
|
|
8728
|
+
let speechStart = null;
|
|
8729
|
+
let lastLoud = null;
|
|
8730
|
+
let firedAt = null;
|
|
8731
|
+
return {
|
|
8732
|
+
feed(level, at) {
|
|
8733
|
+
if (level >= threshold) {
|
|
8734
|
+
if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
|
|
8735
|
+
lastLoud = at;
|
|
8736
|
+
} else if (lastLoud !== null && at - lastLoud > gapMs) {
|
|
8737
|
+
speechStart = null;
|
|
8738
|
+
lastLoud = null;
|
|
8739
|
+
}
|
|
8740
|
+
if (speechStart === null || at - speechStart < holdMs) return false;
|
|
8741
|
+
if (firedAt !== null && at - firedAt < cooldownMs) return false;
|
|
8742
|
+
firedAt = at;
|
|
8743
|
+
speechStart = null;
|
|
8744
|
+
lastLoud = null;
|
|
8745
|
+
return true;
|
|
8746
|
+
},
|
|
8747
|
+
reset() {
|
|
8748
|
+
speechStart = null;
|
|
8749
|
+
lastLoud = null;
|
|
8750
|
+
firedAt = null;
|
|
8751
|
+
}
|
|
8752
|
+
};
|
|
8753
|
+
}
|
|
8451
8754
|
//# sourceMappingURL=index.cjs.map
|