A real-time RTP/media sender paced by `setInterval(fn, 20)` that sends at most one packet per tick becomes progressively delayed and eventually drops packets/produces audible breakup under any event-loop load (GC pauses, other synchronous work, timer coalescing). Node's `setInterval` does not guarantee firing exactly on schedule and never fires twice to compensate for a late/skipped tick, so if the event loop is blocked for e.g. 100ms, that tick fires once, ~5 pending 20ms frames worth of real time have elapsed but only 1 packet is sent, and the sender falls permanently behind real time. The outbound queue then grows until it hits its cap, after which packets must be dropped -- producing dropouts, and because the receiver's jitter buffer is being fed increasingly stale audio, an audible fixed lag that never recovers.

node.js · verified Jul 11, 2026

Fix: Replace the fixed 'one packet per tick' pacer with a drift-corrected pacer anchored to a monotonic clock (e.g. `performance.now()`): on each timer wake, compute `target = floor((now - gridStartMs) / frameMs)` (how many frame-slots *should* have been sent by now given real elapsed time), and drain up to `target - framesSent` packets from the queue in that single wake (looping while both budget and queue remain), rather than exactly one. This makes the sender self-correct after any stall -- a late wake simply results in a larger catch-up batch instead of permanent drift -- while packets are still emitted with correctly-spaced (e.g. 960-sample) timestamp deltas since the RTP timestamp is advanced deterministically per logical frame-slot, not per wall-clock tick. If the queue is empty when there is remaining budget (real silence, not backlog), advance the frame/timestamp counters to `target` directly without looping, so the RTP timeline still reflects true elapsed time (this pairs with the separate fix for timestamp-freeze-during-silence causing overlap). This is a standard leaky-bucket/credit-based pacing pattern; the key defect being fixed is using a fixed-count-per-tick assumption on top of a non-real-time timer.

nodejsrtpwebrtcpacingsetintervalaudiojitter

References