@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.js
CHANGED
|
@@ -632,6 +632,8 @@ var MEET_ROUTES = {
|
|
|
632
632
|
noteTakerStop: "/api/v1/meet/agent/stop",
|
|
633
633
|
/** In-meeting Q&A. Streams NDJSON; body is `{meeting_id, question}` only. */
|
|
634
634
|
intelligenceAsk: "/api/v1/meet/intelligence/ask",
|
|
635
|
+
/** Anyone in the meeting (guests with the room pass included) asks for captions for themselves. */
|
|
636
|
+
noteTakerCaptions: "/api/v1/meet/agent/captions",
|
|
635
637
|
/** A host ends the meeting for everyone. Served by MRI-A6. */
|
|
636
638
|
meetingEnd: "/api/v1/meet/end",
|
|
637
639
|
/**
|
|
@@ -778,7 +780,8 @@ function parseRoomToken(body, roomName) {
|
|
|
778
780
|
breakoutRoomId: readString(payload, "breakout_room_id") ?? null,
|
|
779
781
|
observersVisibleTo: readString(payload, "observers_visible_to") === "hosts" ? "hosts" : "everyone",
|
|
780
782
|
connectionPolicy: connectionPolicyFrom(payload["connection_policy"]),
|
|
781
|
-
leavePolicy: leavePolicyFrom(payload["leave_policy"])
|
|
783
|
+
leavePolicy: leavePolicyFrom(payload["leave_policy"]),
|
|
784
|
+
canReclaim: payload["can_reclaim"] === true
|
|
782
785
|
};
|
|
783
786
|
}
|
|
784
787
|
function createTokenClient(options) {
|
|
@@ -1240,6 +1243,42 @@ function createMeetAi(options) {
|
|
|
1240
1243
|
},
|
|
1241
1244
|
"meet.ai.startNoteTaker"
|
|
1242
1245
|
),
|
|
1246
|
+
requestCaptions: async ({ meetingId }) => {
|
|
1247
|
+
const roomToken = options.resolveRoomToken?.() ?? null;
|
|
1248
|
+
const operation = "meet.ai.requestCaptions";
|
|
1249
|
+
try {
|
|
1250
|
+
const response = await send({
|
|
1251
|
+
path: MEET_ROUTES.noteTakerCaptions,
|
|
1252
|
+
body: { meeting_id: meetingId },
|
|
1253
|
+
operation,
|
|
1254
|
+
requireAuth: roomToken === null,
|
|
1255
|
+
...roomToken !== null ? { headers: { [MEETING_PASS_HEADER]: roomToken } } : {}
|
|
1256
|
+
});
|
|
1257
|
+
if (!response.ok) {
|
|
1258
|
+
options.store.patch({ captionsRefusal: await refusalFor(response, operation) });
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
let payload = null;
|
|
1262
|
+
try {
|
|
1263
|
+
payload = await response.json();
|
|
1264
|
+
} catch {
|
|
1265
|
+
payload = null;
|
|
1266
|
+
}
|
|
1267
|
+
const row = typeof payload === "object" && payload !== null ? payload : {};
|
|
1268
|
+
const detail = typeof row["detail"] === "string" ? row["detail"] : "";
|
|
1269
|
+
options.store.patch({
|
|
1270
|
+
captionsRefusal: row["dispatched"] === true ? null : { status: 200, reason: detail, remedy: "" }
|
|
1271
|
+
});
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
options.store.patch({
|
|
1274
|
+
captionsRefusal: {
|
|
1275
|
+
status: 0,
|
|
1276
|
+
reason: error.message,
|
|
1277
|
+
remedy: error.remedy ?? ""
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
},
|
|
1243
1282
|
stopNoteTaker: ({ meetingId }) => dispatch(
|
|
1244
1283
|
MEET_ROUTES.noteTakerStop,
|
|
1245
1284
|
{ meeting_id: meetingId },
|
|
@@ -1278,7 +1317,7 @@ function exactNoteTakerParticipant(meeting, participants) {
|
|
|
1278
1317
|
}
|
|
1279
1318
|
function noteTakerStatus(args) {
|
|
1280
1319
|
const present = exactNoteTakerParticipant(args.meeting, args.participants) !== null;
|
|
1281
|
-
const raw = args.meeting?.metadata[NOTE_TAKER_METADATA_KEY];
|
|
1320
|
+
const raw = args.meeting?.metadata?.[NOTE_TAKER_METADATA_KEY];
|
|
1282
1321
|
const row = typeof raw === "object" && raw !== null && !Array.isArray(raw) ? raw : {};
|
|
1283
1322
|
const recorded = readState(row["state"]);
|
|
1284
1323
|
const detail = typeof row["reason"] === "string" && row["reason"].length > 0 ? row["reason"] : null;
|
|
@@ -3011,6 +3050,8 @@ function planFromRow(row) {
|
|
|
3011
3050
|
minutes: num2(row["minutes"]) ?? 0,
|
|
3012
3051
|
openedAt: str(row["opened_at"]),
|
|
3013
3052
|
endsAt: str(row["ends_at"]),
|
|
3053
|
+
closingAt: str(row["closing_at"]),
|
|
3054
|
+
moveMode: row["move_mode"] === "auto" ? "auto" : "invite",
|
|
3014
3055
|
closedAt: str(row["closed_at"]),
|
|
3015
3056
|
broadcasts: (Array.isArray(row["broadcasts"]) ? row["broadcasts"] : []).map(broadcastFrom).filter((b) => b !== null)
|
|
3016
3057
|
};
|
|
@@ -3162,9 +3203,17 @@ function autoAssign(identities, rooms, exclude = []) {
|
|
|
3162
3203
|
return out;
|
|
3163
3204
|
}
|
|
3164
3205
|
function breakoutRemainingMs(plan, now) {
|
|
3165
|
-
if (plan === null || plan.state !== "open"
|
|
3166
|
-
const
|
|
3167
|
-
|
|
3206
|
+
if (plan === null || plan.state !== "open") return null;
|
|
3207
|
+
const ends = [plan.endsAt, plan.closingAt].map((stamp) => stamp === null ? Number.NaN : Date.parse(stamp)).filter((time) => !Number.isNaN(time));
|
|
3208
|
+
if (ends.length === 0) return null;
|
|
3209
|
+
return Math.max(0, Math.min(...ends) - now);
|
|
3210
|
+
}
|
|
3211
|
+
var BREAKOUT_COUNTDOWN_MS = 3e4;
|
|
3212
|
+
function breakoutClosingMs(plan, now) {
|
|
3213
|
+
if (plan === null || plan.state !== "open") return null;
|
|
3214
|
+
const remaining = breakoutRemainingMs(plan, now);
|
|
3215
|
+
if (remaining === null) return null;
|
|
3216
|
+
return plan.closingAt !== null || remaining <= BREAKOUT_COUNTDOWN_MS ? remaining : null;
|
|
3168
3217
|
}
|
|
3169
3218
|
function defaultCaptionLanguage(settings, viewerLanguage) {
|
|
3170
3219
|
const configured = settings?.captionTranslationLanguage.trim() ?? "";
|
|
@@ -3411,12 +3460,55 @@ function recordingLabel(state) {
|
|
|
3411
3460
|
}
|
|
3412
3461
|
}
|
|
3413
3462
|
|
|
3463
|
+
// src/core/live-policy.ts
|
|
3464
|
+
var DEFAULT_LIVE_POLICY = {
|
|
3465
|
+
chatMode: "everyone",
|
|
3466
|
+
avLock: "off",
|
|
3467
|
+
timeLimitMinutes: 0,
|
|
3468
|
+
timeLimitWarningMinutes: 5,
|
|
3469
|
+
recordingConsent: "notice"
|
|
3470
|
+
};
|
|
3471
|
+
function oneOf(value, allowed, fallback) {
|
|
3472
|
+
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
3473
|
+
}
|
|
3474
|
+
function whole(value, fallback) {
|
|
3475
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
3476
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
|
|
3477
|
+
}
|
|
3478
|
+
function livePolicyFrom(raw) {
|
|
3479
|
+
const o = typeof raw === "object" && raw !== null ? raw : {};
|
|
3480
|
+
return {
|
|
3481
|
+
chatMode: oneOf(o["chat_mode"], ["everyone", "hosts_only", "off"], DEFAULT_LIVE_POLICY.chatMode),
|
|
3482
|
+
avLock: oneOf(o["attendee_av_lock"], ["off", "mic", "camera", "both"], DEFAULT_LIVE_POLICY.avLock),
|
|
3483
|
+
timeLimitMinutes: whole(o["time_limit_minutes"], 0),
|
|
3484
|
+
timeLimitWarningMinutes: whole(o["time_limit_warning_minutes"], DEFAULT_LIVE_POLICY.timeLimitWarningMinutes),
|
|
3485
|
+
recordingConsent: oneOf(
|
|
3486
|
+
o["recording_consent"],
|
|
3487
|
+
["notice", "ok_or_leave", "view_only_until_agree"],
|
|
3488
|
+
DEFAULT_LIVE_POLICY.recordingConsent
|
|
3489
|
+
)
|
|
3490
|
+
};
|
|
3491
|
+
}
|
|
3492
|
+
function samePolicy(a, b) {
|
|
3493
|
+
return a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent;
|
|
3494
|
+
}
|
|
3495
|
+
var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
3496
|
+
function isLockedFor(policy, role, kind) {
|
|
3497
|
+
if (MODERATORS.has(role)) return false;
|
|
3498
|
+
return policy.avLock === "both" || (kind === "microphone" ? policy.avLock === "mic" : policy.avLock === "camera");
|
|
3499
|
+
}
|
|
3500
|
+
function consentPending(policy, role, recordingOn, given) {
|
|
3501
|
+
return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3414
3504
|
// src/core/attendance.ts
|
|
3505
|
+
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
3415
3506
|
var IDLE_MEDIA_REGION = {
|
|
3416
3507
|
permission: { microphone: "unknown", camera: "unknown" },
|
|
3417
3508
|
audioOutput: "ok",
|
|
3418
3509
|
notice: null,
|
|
3419
|
-
offer: null
|
|
3510
|
+
offer: null,
|
|
3511
|
+
preview: { microphone: IDLE_PREVIEW, camera: IDLE_PREVIEW }
|
|
3420
3512
|
};
|
|
3421
3513
|
var IDLE_TRACK = { enabled: false, unavailable: null };
|
|
3422
3514
|
var IDLE_MEDIA = {
|
|
@@ -3481,6 +3573,11 @@ var EMPTY = {
|
|
|
3481
3573
|
presenter: null,
|
|
3482
3574
|
waitingCount: 0,
|
|
3483
3575
|
leavePolicy: DEFAULT_LEAVE_POLICY,
|
|
3576
|
+
livePolicy: DEFAULT_LIVE_POLICY,
|
|
3577
|
+
canReclaim: false,
|
|
3578
|
+
recordingConsentGiven: false,
|
|
3579
|
+
sessionStartedAt: null,
|
|
3580
|
+
mutedByHost: false,
|
|
3484
3581
|
moderationNotice: null,
|
|
3485
3582
|
actionNotice: null,
|
|
3486
3583
|
privateChatRequest: null,
|
|
@@ -3490,6 +3587,8 @@ var EMPTY = {
|
|
|
3490
3587
|
names: {},
|
|
3491
3588
|
wrapUp: null,
|
|
3492
3589
|
noteTakerRefusal: null,
|
|
3590
|
+
captionsOn: false,
|
|
3591
|
+
captionsRefusal: null,
|
|
3493
3592
|
media: IDLE_MEDIA,
|
|
3494
3593
|
mediaRegion: IDLE_MEDIA_REGION,
|
|
3495
3594
|
failure: null,
|
|
@@ -3694,7 +3793,12 @@ function createMeetStore(initial = {}) {
|
|
|
3694
3793
|
if (current.enabled === next.enabled && current.unavailable?.code === next.unavailable?.code && current.unavailable?.reason === next.unavailable?.reason && current.unavailable?.message === next.unavailable?.message) {
|
|
3695
3794
|
return;
|
|
3696
3795
|
}
|
|
3697
|
-
commit({
|
|
3796
|
+
commit({
|
|
3797
|
+
...state,
|
|
3798
|
+
media: { ...state.media, [kind]: next },
|
|
3799
|
+
// The person turned their own microphone on again: the host's mute is over.
|
|
3800
|
+
...kind === "microphone" && next.enabled && state.mutedByHost ? { mutedByHost: false } : {}
|
|
3801
|
+
});
|
|
3698
3802
|
},
|
|
3699
3803
|
reset() {
|
|
3700
3804
|
const phase = nextPhase(state.phase, { type: "reset", teardown: true }) ?? EMPTY.phase;
|
|
@@ -3920,10 +4024,11 @@ function moderationText(kind, payload) {
|
|
|
3920
4024
|
switch (kind) {
|
|
3921
4025
|
case "muted": {
|
|
3922
4026
|
const source = text2(payload["source"]) ?? "microphone";
|
|
3923
|
-
|
|
4027
|
+
const who = payload["by_role"] === "host" || payload["by_role"] === "cohost" ? "the host" : by;
|
|
4028
|
+
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}.`;
|
|
3924
4029
|
}
|
|
3925
4030
|
case "ask-unmute":
|
|
3926
|
-
return `${by}
|
|
4031
|
+
return `${by === "The host" ? "The host" : by} asked you to unmute.`;
|
|
3927
4032
|
case "removed":
|
|
3928
4033
|
return `${by} removed you from this meeting.`;
|
|
3929
4034
|
default:
|
|
@@ -3949,7 +4054,7 @@ function applyModeration(store, payload, now) {
|
|
|
3949
4054
|
grants: { ...snapshot.grants, role },
|
|
3950
4055
|
moderationNotice: {
|
|
3951
4056
|
kind: "role",
|
|
3952
|
-
text: role === "host" ? "You are now the host." : role === "cohost" ? "You
|
|
4057
|
+
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.",
|
|
3953
4058
|
at: now()
|
|
3954
4059
|
}
|
|
3955
4060
|
});
|
|
@@ -3961,7 +4066,10 @@ function applyModeration(store, payload, now) {
|
|
|
3961
4066
|
case "removed": {
|
|
3962
4067
|
const sentence = moderationText(kind, payload);
|
|
3963
4068
|
if (sentence === null) return ignored("a moderation event with no sentence");
|
|
3964
|
-
store.patch({
|
|
4069
|
+
store.patch({
|
|
4070
|
+
moderationNotice: { kind, text: sentence, at: now() },
|
|
4071
|
+
...kind === "muted" && (text2(payload["source"]) ?? "microphone") === "microphone" ? { mutedByHost: true } : {}
|
|
4072
|
+
});
|
|
3965
4073
|
return HANDLED;
|
|
3966
4074
|
}
|
|
3967
4075
|
default:
|
|
@@ -4327,6 +4435,47 @@ function gridColumns(count2) {
|
|
|
4327
4435
|
return 5;
|
|
4328
4436
|
}
|
|
4329
4437
|
|
|
4438
|
+
// src/core/capabilities.ts
|
|
4439
|
+
var IN_APP = [
|
|
4440
|
+
[/Instagram/i, "Instagram"],
|
|
4441
|
+
[/FBAN|FBAV|FB_IAB/i, "Facebook"],
|
|
4442
|
+
[/LinkedInApp/i, "LinkedIn"],
|
|
4443
|
+
[/MicroMessenger/i, "WeChat"],
|
|
4444
|
+
[/Snapchat/i, "Snapchat"],
|
|
4445
|
+
[/\bLine\//i, "LINE"],
|
|
4446
|
+
[/Twitter|TwitterAndroid/i, "X"],
|
|
4447
|
+
[/TikTok|musical_ly|BytedanceWebview/i, "TikTok"]
|
|
4448
|
+
];
|
|
4449
|
+
function isMobileUserAgent(userAgent, maxTouchPoints = 0) {
|
|
4450
|
+
if (/iPhone|iPod|Android|Mobile|iPad/i.test(userAgent)) return true;
|
|
4451
|
+
return /Macintosh/.test(userAgent) && maxTouchPoints > 1;
|
|
4452
|
+
}
|
|
4453
|
+
function detectCapabilities(scope = typeof globalThis === "undefined" ? null : globalThis) {
|
|
4454
|
+
if (scope === null || scope.navigator === void 0) {
|
|
4455
|
+
return { canJoin: true, reason: null, inAppBrowser: null, mobile: false, canShareScreen: false };
|
|
4456
|
+
}
|
|
4457
|
+
const ua = scope.navigator.userAgent ?? "";
|
|
4458
|
+
const webrtc = typeof scope.RTCPeerConnection === "function" || typeof scope.webkitRTCPeerConnection === "function";
|
|
4459
|
+
const mobile = isMobileUserAgent(ua, scope.navigator.maxTouchPoints ?? 0);
|
|
4460
|
+
const inAppBrowser = IN_APP.find(([pattern]) => pattern.test(ua))?.[1] ?? null;
|
|
4461
|
+
return {
|
|
4462
|
+
canJoin: webrtc,
|
|
4463
|
+
reason: webrtc ? null : "no_webrtc",
|
|
4464
|
+
inAppBrowser,
|
|
4465
|
+
mobile,
|
|
4466
|
+
canShareScreen: !mobile && typeof scope.navigator.mediaDevices?.getDisplayMedia === "function"
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
function unsupportedWords(capabilities) {
|
|
4470
|
+
if (capabilities.inAppBrowser !== null) {
|
|
4471
|
+
return {
|
|
4472
|
+
title: `${capabilities.inAppBrowser}'s browser can't join calls`,
|
|
4473
|
+
body: "Open this link in Chrome, Safari, Edge or Firefox"
|
|
4474
|
+
};
|
|
4475
|
+
}
|
|
4476
|
+
return { title: "This browser can't join calls", body: "Open this link in Chrome, Safari, Edge or Firefox" };
|
|
4477
|
+
}
|
|
4478
|
+
|
|
4330
4479
|
// src/core/room-driver.ts
|
|
4331
4480
|
var DATA_TOPICS = {
|
|
4332
4481
|
reaction: "mx.reaction",
|
|
@@ -4481,7 +4630,15 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4481
4630
|
created.on(RoomEvent.ParticipantConnected, roster).on(RoomEvent.ParticipantDisconnected, (participant, reason) => {
|
|
4482
4631
|
handlers.participantLeft?.(participant.identity, reasonName(reason));
|
|
4483
4632
|
roster();
|
|
4484
|
-
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished,
|
|
4633
|
+
}).on(RoomEvent.TrackSubscribed, roster).on(RoomEvent.TrackUnsubscribed, roster).on(RoomEvent.TrackMuted, roster).on(RoomEvent.TrackUnmuted, roster).on(RoomEvent.LocalTrackPublished, (publication) => {
|
|
4634
|
+
if (publication.source === Track.Source.ScreenShare) {
|
|
4635
|
+
const ended = () => {
|
|
4636
|
+
void created.localParticipant.setScreenShareEnabled(false).catch(() => void 0);
|
|
4637
|
+
};
|
|
4638
|
+
publication.track?.mediaStreamTrack.addEventListener("ended", ended, { once: true });
|
|
4639
|
+
}
|
|
4640
|
+
roster();
|
|
4641
|
+
}).on(RoomEvent.LocalTrackUnpublished, roster).on(RoomEvent.ParticipantNameChanged, roster).on(RoomEvent.ParticipantAttributesChanged, roster).on(RoomEvent.ParticipantMetadataChanged, roster).on(RoomEvent.RoomMetadataChanged, (metadata) => {
|
|
4485
4642
|
handlers.roomMetadata?.(metadata ?? "");
|
|
4486
4643
|
}).on(RoomEvent.AudioPlaybackStatusChanged, (canPlay) => {
|
|
4487
4644
|
handlers.audioPlayback?.(canPlay);
|
|
@@ -4622,7 +4779,10 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4622
4779
|
},
|
|
4623
4780
|
async setScreenShareEnabled(enabled) {
|
|
4624
4781
|
try {
|
|
4625
|
-
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
4782
|
+
await requireRoom("setScreenShareEnabled").localParticipant.setScreenShareEnabled(
|
|
4783
|
+
enabled,
|
|
4784
|
+
enabled ? { audio: true, systemAudio: "include" } : void 0
|
|
4785
|
+
);
|
|
4626
4786
|
} catch (error) {
|
|
4627
4787
|
throw normalizeScreenShareError(error, "meet.setScreenShareEnabled");
|
|
4628
4788
|
}
|
|
@@ -4661,8 +4821,13 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4661
4821
|
supportsVideoEffects() {
|
|
4662
4822
|
return videoEffectsSupported();
|
|
4663
4823
|
},
|
|
4824
|
+
screenShareHasAudio() {
|
|
4825
|
+
const target = room;
|
|
4826
|
+
if (target === null) return false;
|
|
4827
|
+
return target.localParticipant.getTrackPublications().some((p) => String(p.source) === "screen_share_audio" && p.track !== void 0);
|
|
4828
|
+
},
|
|
4664
4829
|
supportsScreenShare() {
|
|
4665
|
-
return
|
|
4830
|
+
return detectCapabilities().canShareScreen;
|
|
4666
4831
|
},
|
|
4667
4832
|
attach(identity, kind, element) {
|
|
4668
4833
|
const target = requireRoom("attach");
|
|
@@ -4783,6 +4948,7 @@ function applyDoorRow(store, attendee) {
|
|
|
4783
4948
|
function decidedState(state) {
|
|
4784
4949
|
return WAITING_ADMISSION_STATES.includes(state) ? "expired" : state;
|
|
4785
4950
|
}
|
|
4951
|
+
var LIVE_POLICY_POLL_MS = 3e3;
|
|
4786
4952
|
function createMeetingSession(options) {
|
|
4787
4953
|
const { manager, repository, store, identity } = options;
|
|
4788
4954
|
let channel = null;
|
|
@@ -4924,6 +5090,46 @@ function createMeetingSession(options) {
|
|
|
4924
5090
|
stopWaiting();
|
|
4925
5091
|
}
|
|
4926
5092
|
}
|
|
5093
|
+
let policyTimer = null;
|
|
5094
|
+
let policyReading = false;
|
|
5095
|
+
async function readLivePolicy() {
|
|
5096
|
+
const meeting = store.snapshot().meeting;
|
|
5097
|
+
const pass = options.resolvePass?.() ?? null;
|
|
5098
|
+
if (meeting === null || pass === null || policyReading) return;
|
|
5099
|
+
policyReading = true;
|
|
5100
|
+
try {
|
|
5101
|
+
const response = await options.api.request({
|
|
5102
|
+
path: `${MEET_ROUTES.state}?meeting_id=${encodeURIComponent(meeting.id)}`,
|
|
5103
|
+
method: "GET",
|
|
5104
|
+
requireAuth: false,
|
|
5105
|
+
operation: "meet.state(policy)",
|
|
5106
|
+
headers: { [MEETING_PASS_HEADER]: pass }
|
|
5107
|
+
});
|
|
5108
|
+
if (!response.ok) return;
|
|
5109
|
+
const read = await response.json();
|
|
5110
|
+
const next = livePolicyFrom(read.live_policy);
|
|
5111
|
+
const started = read.session_started_at ? Date.parse(read.session_started_at) : NaN;
|
|
5112
|
+
const snapshot = store.snapshot();
|
|
5113
|
+
const startedAt = Number.isFinite(started) ? started : snapshot.sessionStartedAt;
|
|
5114
|
+
if (!samePolicy(snapshot.livePolicy, next) || startedAt !== snapshot.sessionStartedAt) {
|
|
5115
|
+
store.patch({ livePolicy: next, sessionStartedAt: startedAt });
|
|
5116
|
+
}
|
|
5117
|
+
} catch {
|
|
5118
|
+
} finally {
|
|
5119
|
+
policyReading = false;
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
function syncLivePolicyTimer() {
|
|
5123
|
+
const inCall = store.snapshot().phase === "in_call";
|
|
5124
|
+
if (inCall && policyTimer === null) {
|
|
5125
|
+
void readLivePolicy();
|
|
5126
|
+
policyTimer = setInterval(() => void readLivePolicy(), LIVE_POLICY_POLL_MS);
|
|
5127
|
+
} else if (!inCall && policyTimer !== null) {
|
|
5128
|
+
clearInterval(policyTimer);
|
|
5129
|
+
policyTimer = null;
|
|
5130
|
+
}
|
|
5131
|
+
}
|
|
5132
|
+
const unsubscribePolicy = store.subscribe(syncLivePolicyTimer);
|
|
4927
5133
|
async function readState2() {
|
|
4928
5134
|
if (reading !== null) return reading;
|
|
4929
5135
|
const meeting = store.snapshot().meeting;
|
|
@@ -5310,6 +5516,9 @@ function createMeetingSession(options) {
|
|
|
5310
5516
|
},
|
|
5311
5517
|
dispose() {
|
|
5312
5518
|
stopWaiting();
|
|
5519
|
+
unsubscribePolicy();
|
|
5520
|
+
if (policyTimer !== null) clearInterval(policyTimer);
|
|
5521
|
+
policyTimer = null;
|
|
5313
5522
|
channel?.close();
|
|
5314
5523
|
channel = null;
|
|
5315
5524
|
meetingChannel?.close();
|
|
@@ -5923,7 +6132,7 @@ function deviceFixSteps(kind, reason, where) {
|
|
|
5923
6132
|
case "safari":
|
|
5924
6133
|
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"];
|
|
5925
6134
|
default:
|
|
5926
|
-
return ["Click the icon
|
|
6135
|
+
return ["Click the site settings icon in the address bar", `Set ${device} to Allow`, "Press Try again"];
|
|
5927
6136
|
}
|
|
5928
6137
|
case "blocked_os": {
|
|
5929
6138
|
const app = where.browser === "chrome" ? "Google Chrome" : where.browser === "edge" ? "Microsoft Edge" : where.browser === "firefox" ? "Firefox" : "your browser";
|
|
@@ -6021,7 +6230,7 @@ function createMediaSupervisor(options) {
|
|
|
6021
6230
|
function setRegion(next) {
|
|
6022
6231
|
const current = region();
|
|
6023
6232
|
const merged = { ...current, ...next };
|
|
6024
|
-
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) {
|
|
6233
|
+
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) {
|
|
6025
6234
|
return;
|
|
6026
6235
|
}
|
|
6027
6236
|
store.patch({ mediaRegion: merged });
|
|
@@ -6117,7 +6326,7 @@ function createMediaSupervisor(options) {
|
|
|
6117
6326
|
if (permission !== "unknown") setPermission(kind, permission);
|
|
6118
6327
|
return unavailableFrom(normalized, classifyDeviceFailure(normalized, permission));
|
|
6119
6328
|
}
|
|
6120
|
-
|
|
6329
|
+
const api = {
|
|
6121
6330
|
start() {
|
|
6122
6331
|
if (started || platform === null) return;
|
|
6123
6332
|
started = true;
|
|
@@ -6139,6 +6348,16 @@ function createMediaSupervisor(options) {
|
|
|
6139
6348
|
started = false;
|
|
6140
6349
|
},
|
|
6141
6350
|
unavailable,
|
|
6351
|
+
async preview(kind, outcome) {
|
|
6352
|
+
api.start();
|
|
6353
|
+
let next;
|
|
6354
|
+
if (typeof outcome === "string") next = { state: outcome, unavailable: null };
|
|
6355
|
+
else next = { state: "unavailable", unavailable: await unavailable(outcome.error, kind, `meet.${kind}Preview`) };
|
|
6356
|
+
const current = region().preview;
|
|
6357
|
+
const was = current[kind];
|
|
6358
|
+
if (was.state === next.state && was.unavailable?.reason === next.unavailable?.reason && was.unavailable?.message === next.unavailable?.message) return;
|
|
6359
|
+
setRegion({ preview: { ...region().preview, [kind]: next } });
|
|
6360
|
+
},
|
|
6142
6361
|
onAudioPlayback(canPlay) {
|
|
6143
6362
|
setRegion({ audioOutput: canPlay ? "ok" : "blocked" });
|
|
6144
6363
|
},
|
|
@@ -6197,6 +6416,7 @@ function createMediaSupervisor(options) {
|
|
|
6197
6416
|
setRegion({ notice: null });
|
|
6198
6417
|
}
|
|
6199
6418
|
};
|
|
6419
|
+
return api;
|
|
6200
6420
|
}
|
|
6201
6421
|
|
|
6202
6422
|
// src/core/room.ts
|
|
@@ -6358,7 +6578,8 @@ function createRoomEngine(options) {
|
|
|
6358
6578
|
message: normalized.message,
|
|
6359
6579
|
remedy: normalized.remedy,
|
|
6360
6580
|
reason: normalized.reason,
|
|
6361
|
-
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3
|
|
6581
|
+
retryAt: normalized.retryAfterSeconds === null ? null : now() + normalized.retryAfterSeconds * 1e3,
|
|
6582
|
+
stage: "join"
|
|
6362
6583
|
}
|
|
6363
6584
|
});
|
|
6364
6585
|
store.dispatch({ type: "join_failed", reason: normalized.reason ?? null });
|
|
@@ -6627,6 +6848,7 @@ function createRoomEngine(options) {
|
|
|
6627
6848
|
lastServerUrl = token.serverUrl;
|
|
6628
6849
|
connectionPolicy = token.connectionPolicy ?? DEFAULT_CONNECTION_POLICY;
|
|
6629
6850
|
if (token.leavePolicy !== void 0) store.patch({ leavePolicy: token.leavePolicy });
|
|
6851
|
+
store.patch({ canReclaim: token.canReclaim === true });
|
|
6630
6852
|
store.patch({
|
|
6631
6853
|
localIdentity: token.identity,
|
|
6632
6854
|
breakoutRoomId: token.breakoutRoomId ?? null,
|
|
@@ -6748,6 +6970,10 @@ function createRoomEngine(options) {
|
|
|
6748
6970
|
}
|
|
6749
6971
|
departures.clear();
|
|
6750
6972
|
liveRoster = list;
|
|
6973
|
+
const localNow = list.find((p) => p.isLocal);
|
|
6974
|
+
if (localNow !== void 0 && !localNow.screenSharing && store.snapshot().share.state === "presenting") {
|
|
6975
|
+
store.patch({ share: NO_SHARE });
|
|
6976
|
+
}
|
|
6751
6977
|
const merged = mergeGhosts(list, ghosts, at);
|
|
6752
6978
|
ghosts = merged.kept;
|
|
6753
6979
|
const people = /* @__PURE__ */ new Map();
|
|
@@ -6967,7 +7193,10 @@ function createRoomEngine(options) {
|
|
|
6967
7193
|
try {
|
|
6968
7194
|
await driver.setScreenShareEnabled(enabled);
|
|
6969
7195
|
ambiguousShareDenials = 0;
|
|
6970
|
-
|
|
7196
|
+
const audio = enabled ? driver?.screenShareHasAudio?.() : void 0;
|
|
7197
|
+
store.patch({
|
|
7198
|
+
share: { state: enabled ? "presenting" : "none", reason: null, remedy: null, at: now(), ...audio !== void 0 ? { audio } : {} }
|
|
7199
|
+
});
|
|
6971
7200
|
} catch (error) {
|
|
6972
7201
|
const kind = classifyScreenShareError(error);
|
|
6973
7202
|
if (kind === "ambiguous") {
|
|
@@ -7034,13 +7263,14 @@ function createRoomEngine(options) {
|
|
|
7034
7263
|
return driver?.supportsVideoEffects?.() ?? false;
|
|
7035
7264
|
},
|
|
7036
7265
|
supportsScreenShare() {
|
|
7037
|
-
return driver?.supportsScreenShare?.() ??
|
|
7266
|
+
return driver?.supportsScreenShare?.() ?? detectCapabilities().canShareScreen;
|
|
7038
7267
|
},
|
|
7039
7268
|
async switchDevice(kind, deviceId) {
|
|
7040
7269
|
if (driver === null) return;
|
|
7041
7270
|
await driver.switchDevice(kind, deviceId);
|
|
7042
7271
|
},
|
|
7043
7272
|
enableAudio: () => media.enableAudio(),
|
|
7273
|
+
previewDevice: (kind, outcome) => media.preview(kind, outcome),
|
|
7044
7274
|
acceptDeviceOffer: () => media.acceptOffer(),
|
|
7045
7275
|
dismissDeviceOffer: () => media.dismissOffer(),
|
|
7046
7276
|
dismissDeviceNotice: () => media.dismissNotice(),
|
|
@@ -7126,8 +7356,12 @@ var MODERATION_ACTIONS = [
|
|
|
7126
7356
|
"lower_hand",
|
|
7127
7357
|
"lower_all_hands",
|
|
7128
7358
|
"make_cohost",
|
|
7359
|
+
"make_presenter",
|
|
7360
|
+
"revoke_presenter",
|
|
7361
|
+
"set_av_lock",
|
|
7129
7362
|
"revoke_cohost",
|
|
7130
7363
|
"transfer_host",
|
|
7364
|
+
"reclaim_host",
|
|
7131
7365
|
"rename",
|
|
7132
7366
|
"remove",
|
|
7133
7367
|
"report"
|
|
@@ -8057,14 +8291,31 @@ function takeMoveRequest(store) {
|
|
|
8057
8291
|
|
|
8058
8292
|
// src/core/observation.ts
|
|
8059
8293
|
function observedRole(role, standing) {
|
|
8060
|
-
if (role === "host" || role === "cohost") return role;
|
|
8294
|
+
if (role === "host" || role === "cohost" || role === "presenter") return role;
|
|
8061
8295
|
return isLinkFollower(standing) ? "guest" : "participant";
|
|
8062
8296
|
}
|
|
8063
|
-
function observedDevice(track, permission = "unknown") {
|
|
8297
|
+
function observedDevice(track, permission = "unknown", held = null) {
|
|
8298
|
+
if (held === "locked") return "locked";
|
|
8064
8299
|
if (track.enabled) return "on";
|
|
8300
|
+
if (held === "muted_by_host") return "muted_by_host";
|
|
8065
8301
|
const reason = deviceUnavailableReason(track, permission);
|
|
8066
8302
|
return reason === null ? "muted_by_me" : `unavailable:${reason}`;
|
|
8067
8303
|
}
|
|
8304
|
+
function observedPreview(preview, permission = "unknown") {
|
|
8305
|
+
if (permission === "denied") return "unavailable:denied";
|
|
8306
|
+
switch (preview.state) {
|
|
8307
|
+
case "on":
|
|
8308
|
+
return "on";
|
|
8309
|
+
case "off":
|
|
8310
|
+
return "muted_by_me";
|
|
8311
|
+
case "prompting":
|
|
8312
|
+
return "prompting";
|
|
8313
|
+
case "unavailable":
|
|
8314
|
+
return `unavailable:${preview.unavailable?.reason ?? "failed"}`;
|
|
8315
|
+
default:
|
|
8316
|
+
return null;
|
|
8317
|
+
}
|
|
8318
|
+
}
|
|
8068
8319
|
function endedRecord(snapshot, given) {
|
|
8069
8320
|
const record = snapshot?.meeting ?? given;
|
|
8070
8321
|
return record !== null && record.endedAt !== null;
|
|
@@ -8088,20 +8339,34 @@ function observe(input) {
|
|
|
8088
8339
|
const canAdmit = actual !== "none" && ADMITTER_ROLES.has(actual);
|
|
8089
8340
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
8090
8341
|
const deviceInPlay = phase === "joining" || inCall;
|
|
8342
|
+
const region = snapshot?.mediaRegion;
|
|
8343
|
+
const atPrejoin = phase === "prejoin" && region !== void 0;
|
|
8091
8344
|
return {
|
|
8092
8345
|
phase,
|
|
8093
8346
|
reason: phase === "ended" ? snapshot?.reason ?? null : snapshot?.reason ?? reasonOf(phase),
|
|
8094
8347
|
role,
|
|
8095
|
-
|
|
8348
|
+
// `net-blip`: in the call, a drop shorter than `reconnect_banner_delay_ms` is not something the
|
|
8349
|
+
// person sees — the contract says what is on screen, so it reads `stable` until the banner shows.
|
|
8350
|
+
connection: snapshot === null ? "stable" : inCall && snapshot.connection !== "stable" && !snapshot.connectionBanner ? "stable" : snapshot.connection,
|
|
8096
8351
|
hostPresent: inCall && snapshot !== null ? snapshot.participants.some((p) => ADMITTER_ROLES.has(p.role)) : snapshot !== null && isWaitingPhase(snapshot.phase) ? snapshot.waiting?.admittersPresent ?? null : null,
|
|
8097
8352
|
lobbyCount: canAdmit && snapshot !== null ? snapshot.lobby.filter((entry) => entry.state === "knocking").length : 0,
|
|
8098
8353
|
recording: snapshot?.recording === "recording" || snapshot?.recording === "stopping",
|
|
8099
8354
|
// S6c: the autoplay region is known only inside the room (no remote audio exists elsewhere).
|
|
8100
8355
|
audioBlocked: inCall && snapshot !== null ? snapshot.mediaRegion.audioOutput === "blocked" : null,
|
|
8101
|
-
camera: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.camera, snapshot.mediaRegion.permission.camera) : null,
|
|
8102
|
-
microphone: deviceInPlay && snapshot !== null ? observedDevice(snapshot.media.microphone, snapshot.mediaRegion.permission.microphone) : null
|
|
8356
|
+
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,
|
|
8357
|
+
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
|
|
8103
8358
|
};
|
|
8104
8359
|
}
|
|
8360
|
+
function lockedFor(snapshot, kind, inCall) {
|
|
8361
|
+
if (!inCall) return null;
|
|
8362
|
+
const role = effectiveRole(snapshot);
|
|
8363
|
+
if (isLockedFor(snapshot.livePolicy, role, kind)) return "locked";
|
|
8364
|
+
if (snapshot.livePolicy.recordingConsent === "view_only_until_agree" && consentPending(snapshot.livePolicy, role, snapshot.recording === "recording", snapshot.recordingConsentGiven)) {
|
|
8365
|
+
return "locked";
|
|
8366
|
+
}
|
|
8367
|
+
if (kind === "microphone" && snapshot.mutedByHost) return "muted_by_host";
|
|
8368
|
+
return null;
|
|
8369
|
+
}
|
|
8105
8370
|
function staticObservation(phase, reason = null) {
|
|
8106
8371
|
return {
|
|
8107
8372
|
phase,
|
|
@@ -8169,6 +8434,39 @@ function microphoneNoticeCode(track, permission = "unknown") {
|
|
|
8169
8434
|
if (reason === null) return null;
|
|
8170
8435
|
return deviceNoticeCode("microphone", reason) === "mic-blocked" ? "mic-blocked" : "device-lost";
|
|
8171
8436
|
}
|
|
8437
|
+
|
|
8438
|
+
// src/core/speaking-detector.ts
|
|
8439
|
+
function createSpeakingWhileMutedDetector(options = {}) {
|
|
8440
|
+
const threshold = options.threshold ?? 0.06;
|
|
8441
|
+
const holdMs = options.holdMs ?? 600;
|
|
8442
|
+
const gapMs = options.gapMs ?? 400;
|
|
8443
|
+
const cooldownMs = options.cooldownMs ?? 2e4;
|
|
8444
|
+
let speechStart = null;
|
|
8445
|
+
let lastLoud = null;
|
|
8446
|
+
let firedAt = null;
|
|
8447
|
+
return {
|
|
8448
|
+
feed(level, at) {
|
|
8449
|
+
if (level >= threshold) {
|
|
8450
|
+
if (speechStart === null || lastLoud === null || at - lastLoud > gapMs) speechStart = at;
|
|
8451
|
+
lastLoud = at;
|
|
8452
|
+
} else if (lastLoud !== null && at - lastLoud > gapMs) {
|
|
8453
|
+
speechStart = null;
|
|
8454
|
+
lastLoud = null;
|
|
8455
|
+
}
|
|
8456
|
+
if (speechStart === null || at - speechStart < holdMs) return false;
|
|
8457
|
+
if (firedAt !== null && at - firedAt < cooldownMs) return false;
|
|
8458
|
+
firedAt = at;
|
|
8459
|
+
speechStart = null;
|
|
8460
|
+
lastLoud = null;
|
|
8461
|
+
return true;
|
|
8462
|
+
},
|
|
8463
|
+
reset() {
|
|
8464
|
+
speechStart = null;
|
|
8465
|
+
lastLoud = null;
|
|
8466
|
+
firedAt = null;
|
|
8467
|
+
}
|
|
8468
|
+
};
|
|
8469
|
+
}
|
|
8172
8470
|
export {
|
|
8173
8471
|
ATTENDANCE_CSV_HEADER,
|
|
8174
8472
|
CALL_INVITE_KIND,
|
|
@@ -8250,6 +8548,7 @@ export {
|
|
|
8250
8548
|
autoAssign,
|
|
8251
8549
|
autoRejoinDelayMs,
|
|
8252
8550
|
availableActivities,
|
|
8551
|
+
breakoutClosingMs,
|
|
8253
8552
|
breakoutRemainingMs,
|
|
8254
8553
|
callInviteAction,
|
|
8255
8554
|
callInviteText,
|
|
@@ -8285,6 +8584,7 @@ export {
|
|
|
8285
8584
|
createReconnectPolicy,
|
|
8286
8585
|
createRecordingController,
|
|
8287
8586
|
createRoomEngine,
|
|
8587
|
+
createSpeakingWhileMutedDetector,
|
|
8288
8588
|
createTokenClient,
|
|
8289
8589
|
createWebDeviceStorage,
|
|
8290
8590
|
createWebEffectsStorage,
|
|
@@ -8293,6 +8593,7 @@ export {
|
|
|
8293
8593
|
createWebRoomTokenStorage,
|
|
8294
8594
|
defaultCaptionLanguage,
|
|
8295
8595
|
detectBrowser,
|
|
8596
|
+
detectCapabilities,
|
|
8296
8597
|
detectOs,
|
|
8297
8598
|
deviceChipLabel,
|
|
8298
8599
|
deviceFixSteps,
|
|
@@ -8317,6 +8618,7 @@ export {
|
|
|
8317
8618
|
isLinkFollower,
|
|
8318
8619
|
isListenOnly,
|
|
8319
8620
|
isLivePhase,
|
|
8621
|
+
isMobileUserAgent,
|
|
8320
8622
|
isNoteTaker,
|
|
8321
8623
|
isObserverRole,
|
|
8322
8624
|
isRealName,
|
|
@@ -8408,6 +8710,7 @@ export {
|
|
|
8408
8710
|
transcriptDocument,
|
|
8409
8711
|
unavailableFrom,
|
|
8410
8712
|
uniqueAttendees,
|
|
8713
|
+
unsupportedWords,
|
|
8411
8714
|
untranslatedLines,
|
|
8412
8715
|
userTopic,
|
|
8413
8716
|
wasRefused,
|