lotus(#7): fix screenshare cap per review
- Apply the encoding patch to ALL simulcast layers, not just encodings[0]: screenshare publishes simulcast (VP8), so the full-res layer (the real bandwidth hog) was left uncapped (CRITICAL). - Re-apply on TrackUnmuted/restart + a 500ms settle, since LiveKit's refreshSenderEncodings() overwrites our caps on replaceTrack (device/ source switch, processor toggle) without firing LocalTrackPublished (HIGH). - Clamp values to sane ranges so a typo can't brick the encoder (MED). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
22be058108
commit
7d792c33bf
+32
-17
@@ -89,17 +89,24 @@ export function startLotusQuality(vm: CallViewModel): () => void {
|
||||
// Add listeners for new rooms + apply current settings to them.
|
||||
for (const room of next) {
|
||||
if (!roomListeners.has(room)) {
|
||||
const onPublished = (): void => applyToRoom(room);
|
||||
room.localParticipant.on(
|
||||
// Re-apply on (re)publish AND unmute/track-restart: LiveKit's
|
||||
// refreshSenderEncodings() overwrites maxBitrate/maxFramerate from the
|
||||
// publish presets on replaceTrack (device/source switch, processor
|
||||
// toggle, restart-on-unmute), and those paths don't emit
|
||||
// LocalTrackPublished. The settle re-apply lands after LiveKit's async
|
||||
// recompute so our cap wins.
|
||||
const reapply = (): void => {
|
||||
applyToRoom(room);
|
||||
setTimeout(() => applyToRoom(room), 500);
|
||||
};
|
||||
const events = [
|
||||
ParticipantEvent.LocalTrackPublished,
|
||||
onPublished,
|
||||
);
|
||||
roomListeners.set(room, () =>
|
||||
room.localParticipant.off(
|
||||
ParticipantEvent.LocalTrackPublished,
|
||||
onPublished,
|
||||
),
|
||||
);
|
||||
ParticipantEvent.TrackUnmuted,
|
||||
] as const;
|
||||
for (const e of events) room.localParticipant.on(e, reapply);
|
||||
roomListeners.set(room, () => {
|
||||
for (const e of events) room.localParticipant.off(e, reapply);
|
||||
});
|
||||
applyToRoom(room);
|
||||
}
|
||||
}
|
||||
@@ -109,14 +116,19 @@ export function startLotusQuality(vm: CallViewModel): () => void {
|
||||
void w.api.transport.reply(ev.detail, {});
|
||||
const data = ev.detail.data as Record<string, unknown> | undefined;
|
||||
if (!data) return;
|
||||
for (const key of [
|
||||
"audioMaxBitrate",
|
||||
"screenshareMaxBitrate",
|
||||
"screenshareMaxFramerate",
|
||||
] as const) {
|
||||
// Clamp to sane ranges so a typo can't brick the encoder (e.g. a 1 bps mic).
|
||||
const ranges: Record<keyof QualitySettings, [number, number]> = {
|
||||
audioMaxBitrate: [6_000, 510_000],
|
||||
screenshareMaxBitrate: [50_000, 20_000_000],
|
||||
screenshareMaxFramerate: [1, 60],
|
||||
};
|
||||
for (const key of Object.keys(ranges) as (keyof QualitySettings)[]) {
|
||||
const v = data[key];
|
||||
if (v === null) settings[key] = undefined;
|
||||
else if (typeof v === "number" && v > 0) settings[key] = v;
|
||||
else if (typeof v === "number" && Number.isFinite(v)) {
|
||||
const [lo, hi] = ranges[key];
|
||||
settings[key] = Math.min(hi, Math.max(lo, v));
|
||||
}
|
||||
}
|
||||
applyToAll();
|
||||
};
|
||||
@@ -139,7 +151,10 @@ async function patchSender(
|
||||
const params = sender.getParameters();
|
||||
if (!params.encodings || params.encodings.length === 0)
|
||||
params.encodings = [{}];
|
||||
Object.assign(params.encodings[0], patch);
|
||||
// Apply to EVERY encoding, not just encodings[0]: screenshare publishes
|
||||
// with simulcast (VP8), so encodings[0] is the small layer and the
|
||||
// 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);
|
||||
} catch (e) {
|
||||
logger.warn("[lotus] set_quality: setParameters failed", e);
|
||||
|
||||
Reference in New Issue
Block a user