Flow
Build order for a delay tracker
Thresholds come last, once the audio is complete and every attempt says why it failed.
1Stamp at the source
Each chunk carries the real time of its first sample
2Check it's all there
Report starved when audio arrives slower than real time
3Log every attempt
Outcome, match strength and best delay
4Average many windows
A faint echo piles up at one delay, noise washes out
5Version injected scripts
By their own contents, so a change actually lands
Every release that day ended the same way: the delay between the TV and the webcam read null. The box behind my TV keeps a copy of the TV's sound and listens for it in the room. To match the two, it needs to know how late the room hears the TV.
A coding agent did the work. I read the status after each try.
First, the copy's clock was off. The page made its audio on a timer that ran a little fast or slow, and the code only fixed a constant gap. So the timing slid further off every second. The agent fit the rate as well. Audio callbacks only ever arrive late, so it trusts the earliest one in each short stretch.
Second, most of the audio was missing. The audio engine in the page ran at about a quarter speed and dropped almost everything. So the page now reads the media track directly, stamps each chunk with the real time of its first sample, and says "starved" if audio comes in too slowly.
Third, the tracker never said why it failed. Now each attempt reports an outcome, how strong the match was, and its best guess at the delay.
Fourth, those reports showed the same best guess every time, at a strength no better than noise. One short window can't tell a faint echo from noise. Many windows can. Each window's match now goes into a running average over most of a minute. The real echo piles up at one delay and noise washes out.
In the agent's offline test, a faint echo under louder voices locked in under a minute, and a test with no echo never locked.
If you build something like this, do it in this order: get the clock right, make sure no audio is missing, log why each attempt failed, and only then touch thresholds. Version any script you inject into a page by its own contents. Mine changed without a new version, so the page kept the old one.
I still haven't seen it lock live.