Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ const receiver = (sources: () => ContributingSourceLike[]) => ({
t += 200; poll.poll();
check('inside the inactivity window a stale entry is NOT yet a deactivation',
out.length === 1, JSON.stringify(out));
t += 300; poll.poll(); // 500ms > 400ms default
t += 700; poll.poll(); // 900ms > 800ms default
check('past inactiveMs the deactivation is SYNTHESIZED (the transport never sends this edge)',
out.length === 2 && out[1].active === false && out[1].csrc === 7 && out[1].tMs === t,
JSON.stringify(out));
check('the default inactivity window is 400ms', CSRC_INACTIVE_MS === 400);
check('the default inactivity window is 800ms', CSRC_INACTIVE_MS === 800);

// Speaking again is a NEW activation — turns are edges, not a level.
speaking = true; lastSpoke = t; poll.poll();
Expand Down Expand Up @@ -117,7 +117,7 @@ const receiver = (sources: () => ContributingSourceLike[]) => ({
JSON.stringify(out));
check('health counts both as active', poll.health().active === 2, JSON.stringify(poll.health()));
live.delete(11);
t += 500; poll.poll();
t += 900; poll.poll();
check('only the source that went quiet deactivates; the other stays open',
out.length === 3 && out[2].csrc === 11 && out[2].active === false && poll.health().active === 1,
JSON.stringify(out));
Expand Down
11 changes: 8 additions & 3 deletions core/meetings/modules/mixed-capture-core/src/csrc-poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export interface CsrcPollOptions {
receivers?: () => CsrcReceiverLike[];
/** Poll cadence in ms. Default 100 — the granularity the transport itself updates at. */
pollMs?: number;
/** How long a source stays active after its last observed contribution. Default 400 ms. */
/** How long a source stays active after its last observed contribution. Default 800 ms. */
inactiveMs?: number;
/** Epoch-ms clock (injectable for tests). */
now?: () => number;
Expand All @@ -133,8 +133,13 @@ export interface CsrcPoll {

/** The transport updates roughly per packet; 100 ms is one packet-train, not an arbitrary tick. */
export const CSRC_POLL_MS = 100;
/** Chosen to span a packet gap (jitter, DTX, a brief pause) without holding a finished turn open. */
export const CSRC_INACTIVE_MS = 400;
/** Chosen to span a packet gap (jitter, DTX, a brief pause) without holding a finished turn open.
* Measured on a live Microsoft Teams meeting (2026-08-20): replaying this transition logic over a
* 599-sample tape of a 6 s speech / 7 s silence cycle yields 10 deactivations at 400 ms —
* fragmenting one speech phase into segments as short as 0.1 s — against 7 at 800 ms, where the
* reconstructed segments match the fixture's real structure. Teams' mixer pauses inside a turn
* for longer than one packet-train, so 400 ms closes turns that are still open. */
Comment on lines +137 to +141

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move capture history out of source

The date, sample count, 400→800 comparison, and “real structure” assertion are a capture's historical evidence rather than the enduring contract for this exported constant. Leaving them here will make the source stale and unverifiable on the next tuning change; record/replay evidence belongs in an evaluation fixture or delivery artifact, and this comment should retain only the present behavioral rationale.

AGENTS.md reference: AGENTS.md:L165-L166

Useful? React with 👍 / 👎.

export const CSRC_INACTIVE_MS = 800;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the mixed-lane handoff grace

Keep the combined delay unchanged for the non-Teams consumers of this shared default. startCaptureBridge supplies these transitions for every mixed platform, while createBotPipeline routes Zoom/Jitsi through ChunkedTranscriber/CsrcTurnSource, which adds its own 600 ms hysteresis. When A stops and B starts within the new 800 ms sensor window, A has no pending close when B's activation arrives, so the state machine opens a contested turn; B's first roughly 800 ms is therefore emitted as unattributed even though the speakers did not overlap (previously roughly 400 ms). Keep the global 400 ms/default total grace, or make 800 a Teams-only caller option, rather than globally extending this producer delay.

AGENTS.md reference: AGENTS.md:L156-L161

Useful? React with 👍 / 👎.

/** Beyond this, a timestamp is not the clock we think it is. Mirrors the bridge's hint guard. */
const MAX_CLOCK_SKEW_MS = 10 * 60 * 1000;

Expand Down
Loading