@ai-matrx/meet 0.11.47 → 0.11.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/{collab-D1TX5uf9.d.cts → collab-Dk3r37MW.d.cts} +5 -0
- package/dist/{collab-D1TX5uf9.d.ts → collab-Dk3r37MW.d.ts} +5 -0
- package/dist/{components-CNwBgmYU.d.ts → components-BWqIoc_V.d.ts} +1 -1
- package/dist/{components-CkyDos0f.d.cts → components-fpQrqtcs.d.cts} +1 -1
- package/dist/core.cjs +367 -11
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +79 -5
- package/dist/core.d.ts +79 -5
- package/dist/core.js +367 -11
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +367 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +367 -11
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +481 -25
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +11 -4
- package/dist/react.d.ts +11 -4
- package/dist/react.js +481 -25
- package/dist/react.js.map +1 -1
- package/dist/skins/meet.cjs +125 -16
- package/dist/skins/meet.cjs.map +1 -1
- package/dist/skins/meet.d.cts +2 -2
- package/dist/skins/meet.d.ts +2 -2
- package/dist/skins/meet.js +125 -16
- package/dist/skins/meet.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog — `@ai-matrx/meet`
|
|
2
2
|
|
|
3
|
+
## 0.11.48
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/meet/v0.11.47..npm/meet/v0.11.48 -- apps/shared/meet`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
3
9
|
## 0.11.47
|
|
4
10
|
|
|
5
11
|
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
@@ -150,6 +150,11 @@ interface MeetParticipant {
|
|
|
150
150
|
* `reconnecting` tile for `reconnect_grace_seconds`. Absent/false = a live participant.
|
|
151
151
|
*/
|
|
152
152
|
readonly reconnecting?: boolean | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* P1 `net-video-auto-off`: this person's camera is paused by their network (to keep their
|
|
155
|
+
* audio), not by their choice — from their own heartbeat. Absent/false = not paused.
|
|
156
|
+
*/
|
|
157
|
+
readonly videoPaused?: boolean | undefined;
|
|
153
158
|
readonly joinedAt: string;
|
|
154
159
|
/**
|
|
155
160
|
* LiveKit participant attributes, verbatim. The note-taker announces itself
|
|
@@ -150,6 +150,11 @@ interface MeetParticipant {
|
|
|
150
150
|
* `reconnecting` tile for `reconnect_grace_seconds`. Absent/false = a live participant.
|
|
151
151
|
*/
|
|
152
152
|
readonly reconnecting?: boolean | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* P1 `net-video-auto-off`: this person's camera is paused by their network (to keep their
|
|
155
|
+
* audio), not by their choice — from their own heartbeat. Absent/false = not paused.
|
|
156
|
+
*/
|
|
157
|
+
readonly videoPaused?: boolean | undefined;
|
|
153
158
|
readonly joinedAt: string;
|
|
154
159
|
/**
|
|
155
160
|
* LiveKit participant attributes, verbatim. The note-taker announces itself
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-
|
|
2
|
+
import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-Dk3r37MW.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-
|
|
2
|
+
import { M as MeetingRecord, P as ParticipantIdentity, R as RoomName, a as MeetParticipant } from './collab-Dk3r37MW.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* THE PRODUCT UI (R6/R7). Not a headless kernel with a note telling the host to
|
package/dist/core.cjs
CHANGED
|
@@ -496,10 +496,11 @@ function invalidResponse(operation, detail) {
|
|
|
496
496
|
|
|
497
497
|
// src/core/connection-supervisor.ts
|
|
498
498
|
var DEFAULT_CONNECTION_POLICY = {
|
|
499
|
-
bannerDelayMs:
|
|
499
|
+
bannerDelayMs: 5e3,
|
|
500
500
|
autoRejoin: true,
|
|
501
501
|
autoRejoinMaxAttempts: 5,
|
|
502
|
-
reconnectGraceSeconds: 30
|
|
502
|
+
reconnectGraceSeconds: 30,
|
|
503
|
+
videoAutoOffAfterPoorSeconds: 20
|
|
503
504
|
};
|
|
504
505
|
function clampInt(value, min, max, fallback) {
|
|
505
506
|
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
@@ -513,7 +514,8 @@ function connectionPolicyFrom(raw) {
|
|
|
513
514
|
bannerDelayMs: clampInt(row["reconnect_banner_delay_ms"], 0, 1e4, d.bannerDelayMs),
|
|
514
515
|
autoRejoin: typeof row["auto_rejoin"] === "boolean" ? row["auto_rejoin"] : d.autoRejoin,
|
|
515
516
|
autoRejoinMaxAttempts: clampInt(row["auto_rejoin_max_attempts"], 0, 20, d.autoRejoinMaxAttempts),
|
|
516
|
-
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds)
|
|
517
|
+
reconnectGraceSeconds: clampInt(row["reconnect_grace_seconds"], 0, 600, d.reconnectGraceSeconds),
|
|
518
|
+
videoAutoOffAfterPoorSeconds: clampInt(row["video_auto_off_after_poor_seconds"], 0, 600, d.videoAutoOffAfterPoorSeconds)
|
|
517
519
|
};
|
|
518
520
|
}
|
|
519
521
|
var LIVEKIT_BASE_DELAYS_MS = [0, 300, 1200, 2700, 4800, 7e3];
|
|
@@ -3784,11 +3786,14 @@ function recordingLabel(state) {
|
|
|
3784
3786
|
|
|
3785
3787
|
// src/core/live-policy.ts
|
|
3786
3788
|
var DEFAULT_LIVE_POLICY = {
|
|
3789
|
+
headlessPowers: "members_admit",
|
|
3787
3790
|
chatMode: "everyone",
|
|
3788
3791
|
avLock: "off",
|
|
3789
3792
|
timeLimitMinutes: 0,
|
|
3790
3793
|
timeLimitWarningMinutes: 5,
|
|
3791
|
-
recordingConsent: "notice"
|
|
3794
|
+
recordingConsent: "notice",
|
|
3795
|
+
shareWhoCan: "everyone",
|
|
3796
|
+
shareConcurrency: "one_takeover"
|
|
3792
3797
|
};
|
|
3793
3798
|
function oneOf(value, allowed, fallback) {
|
|
3794
3799
|
return typeof value === "string" && allowed.includes(value) ? value : fallback;
|
|
@@ -3800,6 +3805,7 @@ function whole(value, fallback) {
|
|
|
3800
3805
|
function livePolicyFrom(raw) {
|
|
3801
3806
|
const o = typeof raw === "object" && raw !== null ? raw : {};
|
|
3802
3807
|
return {
|
|
3808
|
+
headlessPowers: oneOf(o["headless_powers"], ["members_admit", "none", "cohosts_only"], DEFAULT_LIVE_POLICY.headlessPowers),
|
|
3803
3809
|
chatMode: oneOf(o["chat_mode"], ["everyone", "hosts_only", "off"], DEFAULT_LIVE_POLICY.chatMode),
|
|
3804
3810
|
avLock: oneOf(o["attendee_av_lock"], ["off", "mic", "camera", "both"], DEFAULT_LIVE_POLICY.avLock),
|
|
3805
3811
|
timeLimitMinutes: whole(o["time_limit_minutes"], 0),
|
|
@@ -3808,11 +3814,13 @@ function livePolicyFrom(raw) {
|
|
|
3808
3814
|
o["recording_consent"],
|
|
3809
3815
|
["notice", "ok_or_leave", "view_only_until_agree"],
|
|
3810
3816
|
DEFAULT_LIVE_POLICY.recordingConsent
|
|
3811
|
-
)
|
|
3817
|
+
),
|
|
3818
|
+
shareWhoCan: oneOf(o["share_who_can"], ["everyone", "host_only", "org", "presenters"], DEFAULT_LIVE_POLICY.shareWhoCan),
|
|
3819
|
+
shareConcurrency: oneOf(o["share_concurrency"], ["one_takeover", "one_replace", "multiple"], DEFAULT_LIVE_POLICY.shareConcurrency)
|
|
3812
3820
|
};
|
|
3813
3821
|
}
|
|
3814
3822
|
function samePolicy(a, b) {
|
|
3815
|
-
return a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent;
|
|
3823
|
+
return a.headlessPowers === b.headlessPowers && a.chatMode === b.chatMode && a.avLock === b.avLock && a.timeLimitMinutes === b.timeLimitMinutes && a.timeLimitWarningMinutes === b.timeLimitWarningMinutes && a.recordingConsent === b.recordingConsent && a.shareWhoCan === b.shareWhoCan && a.shareConcurrency === b.shareConcurrency;
|
|
3816
3824
|
}
|
|
3817
3825
|
var MODERATORS = /* @__PURE__ */ new Set(["host", "cohost"]);
|
|
3818
3826
|
function isLockedFor(policy, role, kind) {
|
|
@@ -3822,6 +3830,12 @@ function isLockedFor(policy, role, kind) {
|
|
|
3822
3830
|
function consentPending(policy, role, recordingOn, given) {
|
|
3823
3831
|
return recordingOn && !given && policy.recordingConsent !== "notice" && !MODERATORS.has(role);
|
|
3824
3832
|
}
|
|
3833
|
+
function mayAdmitNow(role, participants, policy) {
|
|
3834
|
+
if (role === "none") return false;
|
|
3835
|
+
if (role === "host" || role === "cohost") return true;
|
|
3836
|
+
const hostIn = participants.some((p) => p.role === "host" || p.role === "cohost");
|
|
3837
|
+
return !hostIn && policy.headlessPowers === "members_admit";
|
|
3838
|
+
}
|
|
3825
3839
|
|
|
3826
3840
|
// src/core/attendance.ts
|
|
3827
3841
|
var IDLE_PREVIEW = { state: "idle", unavailable: null };
|
|
@@ -3861,6 +3875,7 @@ var NO_GRANTS = {
|
|
|
3861
3875
|
canPublish: false,
|
|
3862
3876
|
canSubscribe: false
|
|
3863
3877
|
};
|
|
3878
|
+
var UNKNOWN_LOCAL_NETWORK = { path: "unknown", quality: "unknown", videoPaused: false };
|
|
3864
3879
|
var NO_SHARE = { state: "none", reason: null, remedy: null, at: 0 };
|
|
3865
3880
|
var EMPTY = {
|
|
3866
3881
|
phase: "resolving",
|
|
@@ -3869,6 +3884,7 @@ var EMPTY = {
|
|
|
3869
3884
|
roomName: null,
|
|
3870
3885
|
connection: "stable",
|
|
3871
3886
|
connectionBanner: false,
|
|
3887
|
+
localNetwork: UNKNOWN_LOCAL_NETWORK,
|
|
3872
3888
|
share: NO_SHARE,
|
|
3873
3889
|
call: null,
|
|
3874
3890
|
waiting: null,
|
|
@@ -4383,6 +4399,14 @@ function applyModeration(store, payload, now) {
|
|
|
4383
4399
|
}
|
|
4384
4400
|
return HANDLED;
|
|
4385
4401
|
}
|
|
4402
|
+
case "share-ended": {
|
|
4403
|
+
const reason = text2(payload["reason"]) === "not_allowed" ? "not_allowed" : "replaced";
|
|
4404
|
+
const by = text2(payload["by"]);
|
|
4405
|
+
store.patch({
|
|
4406
|
+
share: { state: "ended", reason, remedy: null, at: now(), ...by !== null ? { by } : {} }
|
|
4407
|
+
});
|
|
4408
|
+
return HANDLED;
|
|
4409
|
+
}
|
|
4386
4410
|
case "muted":
|
|
4387
4411
|
case "ask-unmute":
|
|
4388
4412
|
case "removed": {
|
|
@@ -4818,6 +4842,187 @@ var TRANSCRIPTION_ATTRIBUTES = {
|
|
|
4818
4842
|
speakerIdentity: "lk.transcribed_participant_identity"
|
|
4819
4843
|
};
|
|
4820
4844
|
|
|
4845
|
+
// src/core/network-health.ts
|
|
4846
|
+
function pathOf(reading) {
|
|
4847
|
+
if (reading.relayed === null && reading.tcp === null) return "unknown";
|
|
4848
|
+
return reading.relayed === true || reading.tcp === true ? "restricted" : "direct";
|
|
4849
|
+
}
|
|
4850
|
+
var POOR_RTT_MS = 300;
|
|
4851
|
+
var RECOVERED_RTT_MS = 220;
|
|
4852
|
+
var POOR_LOSS = 0.08;
|
|
4853
|
+
var RECOVERED_LOSS = 0.03;
|
|
4854
|
+
function createSelfAssessor(options = {}) {
|
|
4855
|
+
const poorSamples = options.poorSamples ?? 2;
|
|
4856
|
+
const goodSamples = options.goodSamples ?? 3;
|
|
4857
|
+
let quality = "unknown";
|
|
4858
|
+
let badRun = 0;
|
|
4859
|
+
let goodRun = 0;
|
|
4860
|
+
return {
|
|
4861
|
+
push(reading) {
|
|
4862
|
+
const { rttMs, lossRatio } = reading;
|
|
4863
|
+
if (rttMs === null && lossRatio === null) return quality;
|
|
4864
|
+
const bad = rttMs !== null && rttMs >= POOR_RTT_MS || lossRatio !== null && lossRatio >= POOR_LOSS;
|
|
4865
|
+
const clear = (rttMs === null || rttMs < RECOVERED_RTT_MS) && (lossRatio === null || lossRatio < RECOVERED_LOSS);
|
|
4866
|
+
badRun = bad ? badRun + 1 : 0;
|
|
4867
|
+
goodRun = clear ? goodRun + 1 : 0;
|
|
4868
|
+
if (badRun >= poorSamples) quality = "poor";
|
|
4869
|
+
else if (quality !== "poor" && !bad) quality = "good";
|
|
4870
|
+
else if (quality === "poor" && goodRun >= goodSamples) quality = "good";
|
|
4871
|
+
return quality;
|
|
4872
|
+
},
|
|
4873
|
+
current: () => quality,
|
|
4874
|
+
reset() {
|
|
4875
|
+
quality = "unknown";
|
|
4876
|
+
badRun = 0;
|
|
4877
|
+
goodRun = 0;
|
|
4878
|
+
}
|
|
4879
|
+
};
|
|
4880
|
+
}
|
|
4881
|
+
var RANK = {
|
|
4882
|
+
lost: 0,
|
|
4883
|
+
poor: 1,
|
|
4884
|
+
good: 2,
|
|
4885
|
+
excellent: 3,
|
|
4886
|
+
unknown: 4
|
|
4887
|
+
};
|
|
4888
|
+
function worstQuality(a, b) {
|
|
4889
|
+
if (b === null || b === void 0 || b === "unknown") return a;
|
|
4890
|
+
if (a === "unknown") return b;
|
|
4891
|
+
return RANK[b] < RANK[a] ? b : a;
|
|
4892
|
+
}
|
|
4893
|
+
var ALIVE_TOPIC = "mx.alive";
|
|
4894
|
+
function encodeBeat(beat) {
|
|
4895
|
+
return JSON.stringify({ q: beat.q, vp: beat.vp });
|
|
4896
|
+
}
|
|
4897
|
+
function decodeBeat(payload) {
|
|
4898
|
+
try {
|
|
4899
|
+
const raw = JSON.parse(payload);
|
|
4900
|
+
const q = raw.q === "good" || raw.q === "poor" ? raw.q : "unknown";
|
|
4901
|
+
return { q, vp: raw.vp === true };
|
|
4902
|
+
} catch {
|
|
4903
|
+
return null;
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
function heartbeatIntervalMs(roomSize) {
|
|
4907
|
+
return 2e3 * Math.max(1, Math.ceil(Math.max(1, roomSize) / 25));
|
|
4908
|
+
}
|
|
4909
|
+
function staleAfterMs(roomSize) {
|
|
4910
|
+
return heartbeatIntervalMs(roomSize) * 3 + 1e3;
|
|
4911
|
+
}
|
|
4912
|
+
function createLivenessTracker(now) {
|
|
4913
|
+
const lastHeard = /* @__PURE__ */ new Map();
|
|
4914
|
+
const beats = /* @__PURE__ */ new Map();
|
|
4915
|
+
return {
|
|
4916
|
+
heard(who, beat) {
|
|
4917
|
+
lastHeard.set(who, now());
|
|
4918
|
+
if (beat !== null) beats.set(who, beat);
|
|
4919
|
+
},
|
|
4920
|
+
stale(present, roomSize) {
|
|
4921
|
+
const limit = staleAfterMs(roomSize);
|
|
4922
|
+
const at = now();
|
|
4923
|
+
const out = /* @__PURE__ */ new Set();
|
|
4924
|
+
for (const who of present) {
|
|
4925
|
+
const heard = lastHeard.get(who);
|
|
4926
|
+
if (heard !== void 0 && at - heard > limit) out.add(who);
|
|
4927
|
+
}
|
|
4928
|
+
return out;
|
|
4929
|
+
},
|
|
4930
|
+
reported: (who) => beats.get(who) ?? null,
|
|
4931
|
+
restartClock() {
|
|
4932
|
+
const at = now();
|
|
4933
|
+
for (const who of lastHeard.keys()) lastHeard.set(who, at);
|
|
4934
|
+
},
|
|
4935
|
+
forget(who) {
|
|
4936
|
+
lastHeard.delete(who);
|
|
4937
|
+
beats.delete(who);
|
|
4938
|
+
},
|
|
4939
|
+
clear() {
|
|
4940
|
+
lastHeard.clear();
|
|
4941
|
+
beats.clear();
|
|
4942
|
+
}
|
|
4943
|
+
};
|
|
4944
|
+
}
|
|
4945
|
+
function createVideoAutoOff(afterMs, now) {
|
|
4946
|
+
let poorSince = null;
|
|
4947
|
+
let armed = true;
|
|
4948
|
+
return {
|
|
4949
|
+
step(quality, cameraOn) {
|
|
4950
|
+
if (quality === "good") {
|
|
4951
|
+
poorSince = null;
|
|
4952
|
+
armed = true;
|
|
4953
|
+
return null;
|
|
4954
|
+
}
|
|
4955
|
+
if (quality !== "poor" || !cameraOn) {
|
|
4956
|
+
poorSince = quality === "poor" ? poorSince : null;
|
|
4957
|
+
return null;
|
|
4958
|
+
}
|
|
4959
|
+
const at = now();
|
|
4960
|
+
if (poorSince === null) poorSince = at;
|
|
4961
|
+
const limit = afterMs();
|
|
4962
|
+
if (!armed || limit <= 0 || at - poorSince < limit) return null;
|
|
4963
|
+
armed = false;
|
|
4964
|
+
return "pause";
|
|
4965
|
+
},
|
|
4966
|
+
retried() {
|
|
4967
|
+
armed = false;
|
|
4968
|
+
},
|
|
4969
|
+
reset() {
|
|
4970
|
+
poorSince = null;
|
|
4971
|
+
armed = true;
|
|
4972
|
+
}
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
function readingFromStats(entries) {
|
|
4976
|
+
const byId2 = /* @__PURE__ */ new Map();
|
|
4977
|
+
for (const entry of entries) byId2.set(entry.id, entry);
|
|
4978
|
+
let pair;
|
|
4979
|
+
for (const entry of byId2.values()) {
|
|
4980
|
+
if (entry.type === "transport" && typeof entry["selectedCandidatePairId"] === "string") {
|
|
4981
|
+
pair = byId2.get(entry["selectedCandidatePairId"]);
|
|
4982
|
+
if (pair !== void 0) break;
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4985
|
+
if (pair === void 0) {
|
|
4986
|
+
for (const entry of byId2.values()) {
|
|
4987
|
+
if (entry.type === "candidate-pair" && entry["nominated"] === true && entry["state"] === "succeeded") {
|
|
4988
|
+
pair = entry;
|
|
4989
|
+
break;
|
|
4990
|
+
}
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
let lossRatio = null;
|
|
4994
|
+
for (const entry of byId2.values()) {
|
|
4995
|
+
if (entry.type === "remote-inbound-rtp" && typeof entry["fractionLost"] === "number") {
|
|
4996
|
+
lossRatio = Math.max(lossRatio ?? 0, entry["fractionLost"]);
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
if (pair === void 0) return { rttMs: null, lossRatio, relayed: null, tcp: null };
|
|
5000
|
+
const rtt = pair["currentRoundTripTime"];
|
|
5001
|
+
const local = typeof pair["localCandidateId"] === "string" ? byId2.get(pair["localCandidateId"]) : void 0;
|
|
5002
|
+
const candidateType = local?.["candidateType"];
|
|
5003
|
+
const protocol = local?.["protocol"];
|
|
5004
|
+
const relayProtocol = local?.["relayProtocol"];
|
|
5005
|
+
return {
|
|
5006
|
+
rttMs: typeof rtt === "number" ? Math.round(rtt * 1e3) : null,
|
|
5007
|
+
lossRatio,
|
|
5008
|
+
relayed: typeof candidateType === "string" ? candidateType === "relay" : null,
|
|
5009
|
+
tcp: typeof protocol === "string" || typeof relayProtocol === "string" ? protocol === "tcp" || relayProtocol === "tcp" || relayProtocol === "tls" : null
|
|
5010
|
+
};
|
|
5011
|
+
}
|
|
5012
|
+
function mergeReadings(readings) {
|
|
5013
|
+
const max = (values) => {
|
|
5014
|
+
const known = values.filter((v) => v !== null);
|
|
5015
|
+
return known.length === 0 ? null : Math.max(...known);
|
|
5016
|
+
};
|
|
5017
|
+
const any = (values) => values.every((v) => v === null) ? null : values.some((v) => v === true);
|
|
5018
|
+
return {
|
|
5019
|
+
rttMs: max(readings.map((r) => r.rttMs)),
|
|
5020
|
+
lossRatio: max(readings.map((r) => r.lossRatio)),
|
|
5021
|
+
relayed: any(readings.map((r) => r.relayed)),
|
|
5022
|
+
tcp: any(readings.map((r) => r.tcp))
|
|
5023
|
+
};
|
|
5024
|
+
}
|
|
5025
|
+
|
|
4821
5026
|
// src/core/livekit-driver.ts
|
|
4822
5027
|
function mapQuality(quality) {
|
|
4823
5028
|
switch (quality) {
|
|
@@ -4907,6 +5112,30 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4907
5112
|
let projectRoster = null;
|
|
4908
5113
|
let audioCapture = DEFAULT_AUDIO_PROCESSING;
|
|
4909
5114
|
let videoEffect = { kind: "none" };
|
|
5115
|
+
let statsTimer = null;
|
|
5116
|
+
function stopStatsPoll() {
|
|
5117
|
+
if (statsTimer !== null) clearInterval(statsTimer);
|
|
5118
|
+
statsTimer = null;
|
|
5119
|
+
}
|
|
5120
|
+
function startStatsPoll(target) {
|
|
5121
|
+
stopStatsPoll();
|
|
5122
|
+
if (handlers.networkReading === void 0) return;
|
|
5123
|
+
const sample = async () => {
|
|
5124
|
+
if (room !== target) return;
|
|
5125
|
+
const manager = target.engine.pcManager;
|
|
5126
|
+
if (manager === void 0) return;
|
|
5127
|
+
const readings = [];
|
|
5128
|
+
for (const transport of [manager.publisher, manager.subscriber]) {
|
|
5129
|
+
try {
|
|
5130
|
+
const report = await transport?.getStats();
|
|
5131
|
+
if (report !== void 0) readings.push(readingFromStats(report.values()));
|
|
5132
|
+
} catch {
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
if (readings.length > 0 && room === target) handlers.networkReading?.(mergeReadings(readings));
|
|
5136
|
+
};
|
|
5137
|
+
statsTimer = setInterval(() => void sample(), 2e3);
|
|
5138
|
+
}
|
|
4910
5139
|
async function load() {
|
|
4911
5140
|
if (room !== null) return room;
|
|
4912
5141
|
let module2;
|
|
@@ -4981,6 +5210,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
4981
5210
|
participant?.identity ?? null
|
|
4982
5211
|
);
|
|
4983
5212
|
}).on(RoomEvent.Disconnected, (reason) => {
|
|
5213
|
+
stopStatsPoll();
|
|
4984
5214
|
audioContainer?.remove();
|
|
4985
5215
|
audioContainer = null;
|
|
4986
5216
|
handlers.disconnected(reasonName(reason));
|
|
@@ -5066,6 +5296,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5066
5296
|
projectRoster?.();
|
|
5067
5297
|
handlers.roomMetadata?.(target.metadata ?? "");
|
|
5068
5298
|
handlers.audioPlayback?.(target.canPlaybackAudio);
|
|
5299
|
+
startStatsPoll(target);
|
|
5069
5300
|
},
|
|
5070
5301
|
async disconnect() {
|
|
5071
5302
|
if (room === null) return;
|
|
@@ -5076,6 +5307,7 @@ function createLiveKitDriver(handlers, options = {}) {
|
|
|
5076
5307
|
} catch {
|
|
5077
5308
|
}
|
|
5078
5309
|
projectRoster = null;
|
|
5310
|
+
stopStatsPoll();
|
|
5079
5311
|
audioContainer?.remove();
|
|
5080
5312
|
audioContainer = null;
|
|
5081
5313
|
await target.disconnect();
|
|
@@ -6852,6 +7084,76 @@ function createRoomEngine(options) {
|
|
|
6852
7084
|
return personConnections.get(personOf(who)) ?? [who];
|
|
6853
7085
|
}
|
|
6854
7086
|
const departures = /* @__PURE__ */ new Map();
|
|
7087
|
+
const liveness = createLivenessTracker(now);
|
|
7088
|
+
const assessor = createSelfAssessor();
|
|
7089
|
+
const autoOff = createVideoAutoOff(() => connectionPolicy.videoAutoOffAfterPoorSeconds * 1e3, now);
|
|
7090
|
+
const lkQuality = /* @__PURE__ */ new Map();
|
|
7091
|
+
let silent = /* @__PURE__ */ new Set();
|
|
7092
|
+
let healthTimer = null;
|
|
7093
|
+
let lastBeatAt = 0;
|
|
7094
|
+
let wasUnstable = false;
|
|
7095
|
+
function patchLocalNetwork(next) {
|
|
7096
|
+
const before = store.snapshot().localNetwork;
|
|
7097
|
+
const merged = { ...before, ...next };
|
|
7098
|
+
if (merged.path !== before.path || merged.quality !== before.quality || merged.videoPaused !== before.videoPaused) {
|
|
7099
|
+
store.patch({ localNetwork: merged });
|
|
7100
|
+
}
|
|
7101
|
+
}
|
|
7102
|
+
function effectiveQuality(person, connection, isLocal) {
|
|
7103
|
+
const fromServer = lkQuality.get(person) ?? "unknown";
|
|
7104
|
+
if (isLocal) return worstQuality(fromServer, assessor.current());
|
|
7105
|
+
return worstQuality(fromServer, connection === void 0 ? null : liveness.reported(connection)?.q);
|
|
7106
|
+
}
|
|
7107
|
+
function refreshQualities() {
|
|
7108
|
+
const snap = store.snapshot();
|
|
7109
|
+
for (const p of snap.participants) {
|
|
7110
|
+
if (p.reconnecting === true) continue;
|
|
7111
|
+
const next = effectiveQuality(p.identity, mainConnection.get(p.identity), p.isLocal);
|
|
7112
|
+
if (next !== "unknown" && next !== p.quality) store.setQuality(p.identity, next);
|
|
7113
|
+
}
|
|
7114
|
+
}
|
|
7115
|
+
function stopHealth() {
|
|
7116
|
+
if (healthTimer !== null) clearInterval(healthTimer);
|
|
7117
|
+
healthTimer = null;
|
|
7118
|
+
liveness.clear();
|
|
7119
|
+
assessor.reset();
|
|
7120
|
+
autoOff.reset();
|
|
7121
|
+
silent = /* @__PURE__ */ new Set();
|
|
7122
|
+
lkQuality.clear();
|
|
7123
|
+
lastBeatAt = 0;
|
|
7124
|
+
wasUnstable = false;
|
|
7125
|
+
if (store.snapshot().localNetwork !== UNKNOWN_LOCAL_NETWORK) store.patch({ localNetwork: UNKNOWN_LOCAL_NETWORK });
|
|
7126
|
+
}
|
|
7127
|
+
function healthTick() {
|
|
7128
|
+
const snap = store.snapshot();
|
|
7129
|
+
const target = driver;
|
|
7130
|
+
if (disposed || target === null || snap.phase !== "in_call") return;
|
|
7131
|
+
if (snap.connection !== "stable") {
|
|
7132
|
+
wasUnstable = true;
|
|
7133
|
+
return;
|
|
7134
|
+
}
|
|
7135
|
+
if (wasUnstable) {
|
|
7136
|
+
wasUnstable = false;
|
|
7137
|
+
liveness.restartClock();
|
|
7138
|
+
}
|
|
7139
|
+
const size = liveRoster.length;
|
|
7140
|
+
const at = now();
|
|
7141
|
+
if (at - lastBeatAt >= heartbeatIntervalMs(size)) {
|
|
7142
|
+
lastBeatAt = at;
|
|
7143
|
+
const beat = encodeBeat({ q: assessor.current(), vp: snap.localNetwork.videoPaused });
|
|
7144
|
+
void target.publishData(ALIVE_TOPIC, beat).catch(() => void 0);
|
|
7145
|
+
}
|
|
7146
|
+
const remotes = liveRoster.filter((p) => !p.isLocal).map((p) => p.identity);
|
|
7147
|
+
const next = liveness.stale(remotes, size);
|
|
7148
|
+
if (next.size !== silent.size || [...next].some((who) => !silent.has(who))) {
|
|
7149
|
+
silent = next;
|
|
7150
|
+
projectRoster(liveRoster, new Date(at).toISOString());
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
function startHealth() {
|
|
7154
|
+
if (healthTimer !== null) return;
|
|
7155
|
+
healthTimer = setInterval(healthTick, 1e3);
|
|
7156
|
+
}
|
|
6855
7157
|
function requestAsNow() {
|
|
6856
7158
|
if (lastRequest === null) return null;
|
|
6857
7159
|
const media2 = store.snapshot().media;
|
|
@@ -6870,6 +7172,7 @@ function createRoomEngine(options) {
|
|
|
6870
7172
|
media.onDisconnected();
|
|
6871
7173
|
store.patch({ share: NO_SHARE, connection: "stable" });
|
|
6872
7174
|
clearGhosts();
|
|
7175
|
+
stopHealth();
|
|
6873
7176
|
await target?.disconnect();
|
|
6874
7177
|
}
|
|
6875
7178
|
async function leaveRoom() {
|
|
@@ -6961,6 +7264,8 @@ function createRoomEngine(options) {
|
|
|
6961
7264
|
},
|
|
6962
7265
|
participantLeft(who, reason) {
|
|
6963
7266
|
departures.set(who, reason);
|
|
7267
|
+
liveness.forget(who);
|
|
7268
|
+
silent.delete(who);
|
|
6964
7269
|
},
|
|
6965
7270
|
roomMetadata(metadata) {
|
|
6966
7271
|
let parsed = {};
|
|
@@ -6994,7 +7299,27 @@ function createRoomEngine(options) {
|
|
|
6994
7299
|
store.setSpeaking([...new Set(identities.map((who) => personOf(who)))]);
|
|
6995
7300
|
},
|
|
6996
7301
|
quality(who, value) {
|
|
6997
|
-
|
|
7302
|
+
const person = personOf(who);
|
|
7303
|
+
lkQuality.set(person, value);
|
|
7304
|
+
const local = store.snapshot().participants.find((p) => p.identity === person)?.isLocal === true;
|
|
7305
|
+
store.setQuality(person, value === "lost" ? value : effectiveQuality(person, who, local));
|
|
7306
|
+
},
|
|
7307
|
+
networkReading(reading) {
|
|
7308
|
+
const snap = store.snapshot();
|
|
7309
|
+
if (snap.phase !== "in_call" || snap.connection !== "stable") return;
|
|
7310
|
+
startHealth();
|
|
7311
|
+
const quality = assessor.push(reading);
|
|
7312
|
+
const path = pathOf(reading);
|
|
7313
|
+
patchLocalNetwork({ quality, ...path !== "unknown" ? { path } : {} });
|
|
7314
|
+
refreshQualities();
|
|
7315
|
+
const cameraOn = snap.media.camera.enabled;
|
|
7316
|
+
if (autoOff.step(quality, cameraOn) === "pause" && driver !== null) {
|
|
7317
|
+
const target = driver;
|
|
7318
|
+
report("info", "meet: the camera was paused because the connection is poor", "The audio is kept; Try video again turns it back on.");
|
|
7319
|
+
void enableTrack(target, "camera", false).then((state) => {
|
|
7320
|
+
if (!state.enabled) patchLocalNetwork({ videoPaused: true });
|
|
7321
|
+
});
|
|
7322
|
+
}
|
|
6998
7323
|
},
|
|
6999
7324
|
connectionState(state) {
|
|
7000
7325
|
supervisor.driverState(state);
|
|
@@ -7089,6 +7414,22 @@ function createRoomEngine(options) {
|
|
|
7089
7414
|
return;
|
|
7090
7415
|
}
|
|
7091
7416
|
const sender = personOf(from ?? "");
|
|
7417
|
+
if (from !== null) {
|
|
7418
|
+
const beat = topic === ALIVE_TOPIC ? decodeBeat(payload) : null;
|
|
7419
|
+
const before = liveness.reported(from);
|
|
7420
|
+
liveness.heard(from, beat);
|
|
7421
|
+
if (topic === ALIVE_TOPIC) {
|
|
7422
|
+
startHealth();
|
|
7423
|
+
if (beat !== null && (before?.q !== beat.q || before?.vp !== beat.vp || silent.has(from))) {
|
|
7424
|
+
silent.delete(from);
|
|
7425
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
7426
|
+
refreshQualities();
|
|
7427
|
+
} else if (silent.delete(from)) {
|
|
7428
|
+
projectRoster(liveRoster, new Date(now()).toISOString());
|
|
7429
|
+
}
|
|
7430
|
+
return;
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7092
7433
|
switch (topic) {
|
|
7093
7434
|
case DATA_TOPICS.reaction:
|
|
7094
7435
|
if (typeof envelope.emoji === "string") {
|
|
@@ -7331,7 +7672,16 @@ function createRoomEngine(options) {
|
|
|
7331
7672
|
store.setParticipants(
|
|
7332
7673
|
[...people.values()].map(({ entry }) => {
|
|
7333
7674
|
const projected = projectParticipant(entry, joinedAt);
|
|
7334
|
-
|
|
7675
|
+
if (entry.reconnecting === true || !entry.isLocal && silent.has(entry.identity)) {
|
|
7676
|
+
return { ...projected, reconnecting: true, quality: "lost" };
|
|
7677
|
+
}
|
|
7678
|
+
const videoPaused = entry.isLocal ? store.snapshot().localNetwork.videoPaused : liveness.reported(entry.identity)?.vp === true;
|
|
7679
|
+
const quality = effectiveQuality(projected.identity, entry.identity, entry.isLocal);
|
|
7680
|
+
return {
|
|
7681
|
+
...projected,
|
|
7682
|
+
...quality !== "unknown" && quality !== "lost" ? { quality } : {},
|
|
7683
|
+
...videoPaused ? { videoPaused: true } : {}
|
|
7684
|
+
};
|
|
7335
7685
|
})
|
|
7336
7686
|
);
|
|
7337
7687
|
scheduleGhostExpiry();
|
|
@@ -7517,7 +7867,12 @@ function createRoomEngine(options) {
|
|
|
7517
7867
|
},
|
|
7518
7868
|
async setCameraEnabled(enabled) {
|
|
7519
7869
|
if (driver === null) return;
|
|
7520
|
-
await enableTrack(driver, "camera", enabled);
|
|
7870
|
+
const state = await enableTrack(driver, "camera", enabled);
|
|
7871
|
+
if (enabled && state.enabled && store.snapshot().localNetwork.videoPaused) {
|
|
7872
|
+
autoOff.retried();
|
|
7873
|
+
patchLocalNetwork({ videoPaused: false });
|
|
7874
|
+
}
|
|
7875
|
+
if (!enabled && store.snapshot().localNetwork.videoPaused) patchLocalNetwork({ videoPaused: false });
|
|
7521
7876
|
},
|
|
7522
7877
|
async setScreenShareEnabled(enabled) {
|
|
7523
7878
|
if (driver === null) return;
|
|
@@ -7563,7 +7918,8 @@ function createRoomEngine(options) {
|
|
|
7563
7918
|
}
|
|
7564
7919
|
},
|
|
7565
7920
|
dismissShareNotice() {
|
|
7566
|
-
|
|
7921
|
+
const state = store.snapshot().share.state;
|
|
7922
|
+
if (state === "refused" || state === "ended") store.patch({ share: NO_SHARE });
|
|
7567
7923
|
},
|
|
7568
7924
|
async react(emoji) {
|
|
7569
7925
|
const local = store.snapshot().localIdentity;
|
|
@@ -8681,7 +9037,7 @@ function observe(input) {
|
|
|
8681
9037
|
const phase = observedPhase(input);
|
|
8682
9038
|
const inCall = phase === "in_call";
|
|
8683
9039
|
const actual = inCall && snapshot !== null ? effectiveRole(snapshot) : "none";
|
|
8684
|
-
const canAdmit =
|
|
9040
|
+
const canAdmit = snapshot !== null && mayAdmitNow(actual, snapshot.participants, snapshot.livePolicy);
|
|
8685
9041
|
const role = actual === "none" || snapshot === null ? "none" : observedRole(actual, effectiveStanding(snapshot));
|
|
8686
9042
|
const deviceInPlay = phase === "joining" || inCall;
|
|
8687
9043
|
const region = snapshot?.mediaRegion;
|