fix(room): preserve Viewer capacity across sharing generations (#307)
This commit is contained in:
1 parent
a39b597396
commit
42e756e00a
7 files changed
+60
-16
No files matched your search
@@ -31,6 +31,10 @@ copy cap `C`: default `2`, configurable only as `1`, `2`, or `3`.
|
||||
- a client may advertise only `0..C` currently available copies, producing the
|
||||
controller's effective capacity.
|
||||
|
||||
That advertised capacity belongs to the connected endpoint, not one sharing
|
||||
generation. Replacing the room graph retains it; Viewer departure or room
|
||||
deletion clears it.
|
||||
|
||||
One transition-overlap reservation may temporarily raise a producer to
|
||||
`min(C + 1, 3)`. Commit or abort releases it. A fourth endpoint copy is never
|
||||
authorized. SFU ingress and subscription egress are independent server resources
|
||||
|
||||
@@ -821,15 +821,6 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) {
|
||||
commitViewerQualityEvidence(peerId, null);
|
||||
}
|
||||
|
||||
function clearAllViewerQualityEvidence(): void {
|
||||
viewerQualityEvidenceTimersRef.current.forEach((timer) =>
|
||||
window.clearTimeout(timer),
|
||||
);
|
||||
viewerQualityEvidenceTimersRef.current.clear();
|
||||
viewerQualityEvidenceRef.current = new Map();
|
||||
setViewerQualityEvidence(new Map());
|
||||
}
|
||||
|
||||
function acceptViewerQualityEvidence(evidence: ViewerQualityEvidence): void {
|
||||
const peer = peersRef.current.get(evidence.viewerPeerId);
|
||||
const directSnapshot = peer?.getSnapshot() ?? null;
|
||||
@@ -1395,7 +1386,6 @@ export function HostPage({ onAuthorizationRequired }: HostPageProps = {}) {
|
||||
setViewerPasswordEnabled(message.viewerPasswordEnabled);
|
||||
setViewerPasswordDraft(authenticatedProfile.roomPassword ?? "");
|
||||
setViewerPasswordVisible(false);
|
||||
clearAllViewerQualityEvidence();
|
||||
setSfuStandbyUrl(
|
||||
"sfuStandbyUrl" in message ? message.sfuStandbyUrl : null,
|
||||
);
|
||||
|
||||
@@ -682,9 +682,9 @@ export class HostPeer {
|
||||
}
|
||||
switch (this.limitationReason) {
|
||||
case "bandwidth":
|
||||
return "此发送连接持续按带宽估计降低分辨率或帧率";
|
||||
return "当前连接带宽受限,画质已自动降低";
|
||||
case "cpu":
|
||||
return "此发送连接持续因编码性能降低分辨率或帧率";
|
||||
return "编码性能受限,画质已自动降低";
|
||||
case "other":
|
||||
return "浏览器持续报告其他画质限制";
|
||||
default:
|
||||
|
||||
@@ -918,11 +918,22 @@ export class HybridMediaRouter {
|
||||
this.options.deleteConnectionId(roomId, peerId);
|
||||
if (room?.controller?.confirmDeparture(peerId, this.now())) {
|
||||
this.requestPump(roomId);
|
||||
} else if (room && !room.controller && room.advertisedCapacityByViewer.size === 0) {
|
||||
this.rooms.delete(roomId);
|
||||
}
|
||||
}
|
||||
|
||||
stopRoom(roomId: string): void {
|
||||
const capacities = new Map(
|
||||
this.rooms.get(roomId)?.advertisedCapacityByViewer ?? [],
|
||||
);
|
||||
this.clearRoom(roomId);
|
||||
if (capacities.size > 0) {
|
||||
const room = this.room(roomId);
|
||||
for (const [peerId, capacity] of capacities) {
|
||||
room.advertisedCapacityByViewer.set(peerId, capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteRoom(roomId: string): void {
|
||||
|
||||
@@ -23,10 +23,10 @@ describe("progressive connection details", () => {
|
||||
expect(
|
||||
qualityLimitationSummary([
|
||||
snapshot(null),
|
||||
snapshot("此发送连接持续按带宽估计降低分辨率或帧率"),
|
||||
snapshot("此发送连接持续因编码性能降低分辨率或帧率"),
|
||||
snapshot("当前连接带宽受限,画质已自动降低"),
|
||||
snapshot("编码性能受限,画质已自动降低"),
|
||||
]),
|
||||
).toBe("此发送连接持续按带宽估计降低分辨率或帧率");
|
||||
).toBe("当前连接带宽受限,画质已自动降低");
|
||||
expect(qualityLimitationSummary([snapshot(null)])).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -1379,7 +1379,9 @@ describe("HostPeer source replacement", () => {
|
||||
await sample();
|
||||
expect(updates.at(-1)?.qualityWarning).toBeNull();
|
||||
await sample();
|
||||
expect(updates.at(-1)?.qualityWarning).toContain("按带宽估计");
|
||||
expect(updates.at(-1)?.qualityWarning).toBe(
|
||||
"当前连接带宽受限,画质已自动降低",
|
||||
);
|
||||
expect(sender.setParameters).toHaveBeenCalledTimes(2);
|
||||
|
||||
await sample();
|
||||
|
||||
@@ -1326,6 +1326,43 @@ describe("HybridMediaRouter v9 runtime", () => {
|
||||
await router.close();
|
||||
});
|
||||
|
||||
it("preserves connected Viewer capacity across sharing generations", async () => {
|
||||
const { store, sent, router } = harness(1);
|
||||
const room = await store.createRoom();
|
||||
const host = connectHost(store, room);
|
||||
complete(router, host);
|
||||
const root = connectViewer(store, room, "retained-capacity-root");
|
||||
complete(router, root);
|
||||
await vi.waitFor(() => expect(preparedFor(sent, root.sessionId)).toBeDefined());
|
||||
const initialRoot = preparedFor(sent, root.sessionId)!;
|
||||
router.handleRouteReady(root, {
|
||||
type: "route-ready",
|
||||
revision: initialRoot.revision,
|
||||
phase: "prepare",
|
||||
});
|
||||
router.setViewerRelayCapacity(root, 1);
|
||||
|
||||
router.stopRoom(room.roomId);
|
||||
sent.set(root.sessionId, []);
|
||||
complete(router, host);
|
||||
await vi.waitFor(() => expect(preparedFor(sent, root.sessionId)).toBeDefined());
|
||||
const restartedRoot = preparedFor(sent, root.sessionId)!;
|
||||
router.handleRouteReady(root, {
|
||||
type: "route-ready",
|
||||
revision: restartedRoot.revision,
|
||||
phase: "prepare",
|
||||
});
|
||||
|
||||
const child = connectViewer(store, room, "retained-capacity-child");
|
||||
complete(router, child);
|
||||
await vi.waitFor(() => expect(preparedFor(sent, child.sessionId)).toBeDefined());
|
||||
expect(preparedFor(sent, child.sessionId)?.assignment.upstream).toEqual({
|
||||
kind: "peer",
|
||||
peerId: root.peerId,
|
||||
});
|
||||
await router.close();
|
||||
});
|
||||
|
||||
it("emits typed exhaustion when reconciliation has no candidate", async () => {
|
||||
const { store, sent, router } = harness(1);
|
||||
const room = await store.createRoom();
|
||||
|
||||
Reference in new issue
Block a user