fix(lotus): set_quality null actually clears a cap on the sender
A null cap only removed the key from the sticky settings and applyToRoom then skipped that sender, leaving the previously written maxBitrate / maxFramerate live on the RTCRtpSender — contrary to the host contract documented in cinny's CallControl. Track which keys this module wrote per sender and write them back as undefined on clear. Unit-tested. Fixes #11 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
co-authored by
Claude Opus 5
parent
fd957badff
commit
cbd42bf975
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2026 Lotus Guild
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { buildPatch, patchSender } from "./lotusQuality";
|
||||
|
||||
function makeSender(
|
||||
initialEncodings: RTCRtpEncodingParameters[] = [{}],
|
||||
): RTCRtpSender {
|
||||
let encodings = initialEncodings;
|
||||
return {
|
||||
getParameters: vi.fn(() => ({ encodings })),
|
||||
setParameters: vi.fn(async (params: RTCRtpSendParameters) => {
|
||||
await Promise.resolve();
|
||||
encodings = params.encodings ?? [];
|
||||
}),
|
||||
} as unknown as RTCRtpSender;
|
||||
}
|
||||
|
||||
describe("lotusQuality set_quality -> clear (#11)", () => {
|
||||
test("clearing a previously-set cap actively unsets it on the sender", async () => {
|
||||
const sender = makeSender();
|
||||
const writtenKeys = new WeakMap<
|
||||
RTCRtpSender,
|
||||
Set<keyof RTCRtpEncodingParameters>
|
||||
>();
|
||||
|
||||
// Set: audioMaxBitrate = 64000.
|
||||
const setPatch = buildPatch(sender, { maxBitrate: 64_000 }, writtenKeys);
|
||||
expect(setPatch).toEqual({ maxBitrate: 64_000 });
|
||||
await patchSender(sender, setPatch, writtenKeys);
|
||||
|
||||
expect(sender.getParameters().encodings[0].maxBitrate).toBe(64_000);
|
||||
expect(writtenKeys.get(sender)).toEqual(new Set(["maxBitrate"]));
|
||||
|
||||
// Clear: host sends `null`, so the caller now wants an empty desired
|
||||
// patch. buildPatch must still emit an explicit `undefined` for the key
|
||||
// it previously wrote, instead of an empty patch that leaves the stale
|
||||
// cap on the sender.
|
||||
const clearPatch = buildPatch(sender, {}, writtenKeys);
|
||||
expect(clearPatch).toEqual({ maxBitrate: undefined });
|
||||
await patchSender(sender, clearPatch, writtenKeys);
|
||||
|
||||
const finalEncoding = sender.getParameters().encodings[0];
|
||||
expect(finalEncoding.maxBitrate).toBeUndefined();
|
||||
expect("maxBitrate" in finalEncoding).toBe(true); // explicitly cleared, not merely absent
|
||||
expect(writtenKeys.has(sender)).toBe(false);
|
||||
});
|
||||
|
||||
test("clearing one of several caps leaves the others (and their tracking) intact", async () => {
|
||||
const sender = makeSender();
|
||||
const writtenKeys = new WeakMap<
|
||||
RTCRtpSender,
|
||||
Set<keyof RTCRtpEncodingParameters>
|
||||
>();
|
||||
|
||||
const setPatch = buildPatch(
|
||||
sender,
|
||||
{ maxBitrate: 500_000, maxFramerate: 24 },
|
||||
writtenKeys,
|
||||
);
|
||||
await patchSender(sender, setPatch, writtenKeys);
|
||||
expect(writtenKeys.get(sender)).toEqual(
|
||||
new Set(["maxBitrate", "maxFramerate"]),
|
||||
);
|
||||
|
||||
// Only maxFramerate is still desired; maxBitrate should be actively
|
||||
// cleared.
|
||||
const clearPatch = buildPatch(sender, { maxFramerate: 24 }, writtenKeys);
|
||||
expect(clearPatch).toEqual({ maxFramerate: 24, maxBitrate: undefined });
|
||||
await patchSender(sender, clearPatch, writtenKeys);
|
||||
|
||||
const finalEncoding = sender.getParameters().encodings[0];
|
||||
expect(finalEncoding.maxBitrate).toBeUndefined();
|
||||
expect(finalEncoding.maxFramerate).toBe(24);
|
||||
expect(writtenKeys.get(sender)).toEqual(new Set(["maxFramerate"]));
|
||||
});
|
||||
|
||||
test("no sender means an empty patch and no call", () => {
|
||||
const writtenKeys = new WeakMap<
|
||||
RTCRtpSender,
|
||||
Set<keyof RTCRtpEncodingParameters>
|
||||
>();
|
||||
expect(buildPatch(undefined, { maxBitrate: 1000 }, writtenKeys)).toEqual(
|
||||
{},
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user