merge: fold in lotus commits e36aef8a..cbd42bf9 landed during the v0.25.0 sync
Six fix(lotus) commits reached `lotus` while the upstream merge was in progress (deafen via global output mute, denoise race/fallback fixes, strictOriginCheck on the widget transport, call_state standalone skip, set_quality null-clears-cap, plus four new src/lotus/*.test.ts files). Merged them on top of the v0.25.0 sync so this branch is a superset of current `lotus`. One conflict, src/lotus/lotusDenoiseProcessor.ts: the sync commit had dropped a meaningless `void` (oxlint no-meaningless-void-operator) on a line the incoming commit also touched; kept the void-less form so oxlint stays clean. Verification after the fold-in (Node 24.11.1 / pnpm 11.21.0): tsc clean; oxlint clean; oxfmt --check clean; knip exit 0; vitest unit 88 files / 639 passed / 9 skipped; build:embedded OK, staged to embedded/web/dist, all six io.lotus.* actions present in the bundle. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
+62
-16
@@ -45,6 +45,14 @@ export function startLotusQuality(vm: CallViewModel): () => void {
|
||||
if (!w) return () => undefined;
|
||||
|
||||
const settings: QualitySettings = {};
|
||||
// [lotus] Tracks which RTCRtpEncodingParameters keys this module has
|
||||
// actively written on each sender, so a later `null` (clear) can write
|
||||
// `undefined` into those same keys instead of just dropping the sticky
|
||||
// setting and leaving the stale cap live on the sender (#11).
|
||||
const writtenKeys = new WeakMap<
|
||||
RTCRtpSender,
|
||||
Set<keyof RTCRtpEncodingParameters>
|
||||
>();
|
||||
// Per-room LocalTrackPublished listeners, so sticky settings re-apply on
|
||||
// every (re)publish.
|
||||
const roomListeners = new Map<LivekitRoom, () => void>();
|
||||
@@ -57,24 +65,27 @@ export function startLotusQuality(vm: CallViewModel): () => void {
|
||||
const applyToRoom = (room: LivekitRoom): void => {
|
||||
const lp = room.localParticipant;
|
||||
|
||||
if (settings.audioMaxBitrate !== undefined) {
|
||||
const mic = lp.getTrackPublication(Track.Source.Microphone)?.track as
|
||||
| LocalTrack
|
||||
| undefined;
|
||||
void patchSender(mic?.sender, { maxBitrate: settings.audioMaxBitrate });
|
||||
}
|
||||
const mic = lp.getTrackPublication(Track.Source.Microphone)?.track as
|
||||
| LocalTrack
|
||||
| undefined;
|
||||
const micDesired: Partial<RTCRtpEncodingParameters> = {};
|
||||
if (settings.audioMaxBitrate !== undefined)
|
||||
micDesired.maxBitrate = settings.audioMaxBitrate;
|
||||
const micPatch = buildPatch(mic?.sender, micDesired, writtenKeys);
|
||||
if (Object.keys(micPatch).length > 0)
|
||||
void patchSender(mic?.sender, micPatch, writtenKeys);
|
||||
|
||||
const ssPatch: Partial<RTCRtpEncodingParameters> = {};
|
||||
const ssDesired: Partial<RTCRtpEncodingParameters> = {};
|
||||
if (settings.screenshareMaxBitrate !== undefined)
|
||||
ssPatch.maxBitrate = settings.screenshareMaxBitrate;
|
||||
ssDesired.maxBitrate = settings.screenshareMaxBitrate;
|
||||
if (settings.screenshareMaxFramerate !== undefined)
|
||||
ssPatch.maxFramerate = settings.screenshareMaxFramerate;
|
||||
if (Object.keys(ssPatch).length > 0) {
|
||||
const ss = lp.getTrackPublication(Track.Source.ScreenShare)?.track as
|
||||
| LocalTrack
|
||||
| undefined;
|
||||
void patchSender(ss?.sender, ssPatch);
|
||||
}
|
||||
ssDesired.maxFramerate = settings.screenshareMaxFramerate;
|
||||
const ss = lp.getTrackPublication(Track.Source.ScreenShare)?.track as
|
||||
| LocalTrack
|
||||
| undefined;
|
||||
const ssPatch = buildPatch(ss?.sender, ssDesired, writtenKeys);
|
||||
if (Object.keys(ssPatch).length > 0)
|
||||
void patchSender(ss?.sender, ssPatch, writtenKeys);
|
||||
};
|
||||
|
||||
const applyToAll = (): void => rooms.forEach(applyToRoom);
|
||||
@@ -164,9 +175,35 @@ export function startLotusQuality(vm: CallViewModel): () => void {
|
||||
};
|
||||
}
|
||||
|
||||
async function patchSender(
|
||||
// [lotus] Build the patch to actually send to a sender: the desired caps,
|
||||
// plus an explicit `undefined` for any key this module previously wrote to
|
||||
// this sender but no longer wants (see writtenKeys / #11) — otherwise a
|
||||
// cleared sticky setting would simply be skipped here and the stale
|
||||
// maxBitrate/maxFramerate would stay live on the RTCRtpSender.
|
||||
// Exported for unit testing only (see lotusQuality.test.ts) — not part of
|
||||
// the module's public surface used by callers.
|
||||
export function buildPatch(
|
||||
sender: RTCRtpSender | undefined,
|
||||
desired: Partial<RTCRtpEncodingParameters>,
|
||||
writtenKeys: WeakMap<RTCRtpSender, Set<keyof RTCRtpEncodingParameters>>,
|
||||
): Partial<RTCRtpEncodingParameters> {
|
||||
if (!sender) return {};
|
||||
const patch: Partial<RTCRtpEncodingParameters> = { ...desired };
|
||||
const prev = writtenKeys.get(sender);
|
||||
if (prev) {
|
||||
for (const key of prev) {
|
||||
if (!(key in patch)) patch[key] = undefined;
|
||||
}
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
// Exported for unit testing only (see lotusQuality.test.ts) — not part of
|
||||
// the module's public surface used by callers.
|
||||
export async function patchSender(
|
||||
sender: RTCRtpSender | undefined,
|
||||
patch: Partial<RTCRtpEncodingParameters>,
|
||||
writtenKeys: WeakMap<RTCRtpSender, Set<keyof RTCRtpEncodingParameters>>,
|
||||
): Promise<void> {
|
||||
if (!sender) return;
|
||||
try {
|
||||
@@ -178,6 +215,15 @@ async function patchSender(
|
||||
// full-resolution layer — the real bandwidth hog — is a later encoding.
|
||||
for (const enc of params.encodings) Object.assign(enc, patch);
|
||||
await sender.setParameters(params);
|
||||
// Remember only the caps that are still active (defined) after this
|
||||
// write, so a later clear knows exactly which keys to unset.
|
||||
const active = new Set<keyof RTCRtpEncodingParameters>();
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
if (value !== undefined)
|
||||
active.add(key as keyof RTCRtpEncodingParameters);
|
||||
}
|
||||
if (active.size > 0) writtenKeys.set(sender, active);
|
||||
else writtenKeys.delete(sender);
|
||||
} catch (e) {
|
||||
logger.warn("[lotus] set_quality: setParameters failed", e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user