<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: smsm</title>
    <description>The latest articles on DEV Community by smsm (@smsmy).</description>
    <link>https://dev.to/smsmy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4123130%2F47db6abd-61a2-4cfd-9665-d47febe712bb.jpeg</url>
      <title>DEV Community: smsm</title>
      <link>https://dev.to/smsmy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/smsmy"/>
    <language>en</language>
    <item>
      <title>Keeping a browser video and a locally processed audio track in sync — and the 0.2s leak that broke it</title>
      <dc:creator>smsm</dc:creator>
      <pubDate>Sun, 13 Sep 2026 12:23:10 +0000</pubDate>
      <link>https://dev.to/smsmy/keeping-a-browser-video-and-a-locally-processed-audio-track-in-sync-and-the-02s-leak-that-broke-26fe</link>
      <guid>https://dev.to/smsmy/keeping-a-browser-video-and-a-locally-processed-audio-track-in-sync-and-the-02s-leak-that-broke-26fe</guid>
      <description>&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;We build &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;HaramLite&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;https://haramlite.com&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;, a desktop app that removes music and
instrumentals from video and audio on the user's own machine — no uploads, no
accounts, no cloud. This is the story of a sync bug in its browser side, because
the fix is a nice illustration of a class of bug that is easy to ship and hard to
see.

&lt;span class="gu"&gt;## The setup&lt;/span&gt;

The browser extension never re-encodes anything. When you watch a YouTube video
with the music removed, it does two things:
&lt;span class="p"&gt;
1.&lt;/span&gt; mutes the page video element, and
&lt;span class="p"&gt;2.&lt;/span&gt; plays a second &lt;span class="sb"&gt;`&amp;lt;audio&amp;gt;`&lt;/span&gt; element — the file the desktop app produced — and
   keeps the two in step.

The audio file is not the same length as the video, because the stretches that
held only music were &lt;span class="ge"&gt;*cut out*&lt;/span&gt; of it. So the timeline is compressed, and we keep
a map of the ranges that survived: &lt;span class="sb"&gt;`kept = [[0,10],[12,20]]`&lt;/span&gt; reads as "seconds
0–10 and 12–20 of the video exist in the audio".

Whenever the video enters a stretch that is missing from the audio, we jump the
&lt;span class="gs"&gt;**picture**&lt;/span&gt; forward over it:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
js&lt;br&gt;
const target = skipVideoGaps(video.currentTime, kept);   // full timeline&lt;br&gt;
if (Math.abs(target - video.currentTime) &amp;gt; 0.15) {&lt;br&gt;
  video.currentTime = target;&lt;br&gt;
  // ...and the audio has to follow, or it keeps playing content that&lt;br&gt;
  // belongs after the skip.&lt;br&gt;
}&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## The bug

The audio was re-anchored **only** when a hold flag was set, which happened only
when a seek had landed inside a removed stretch. On an ordinary jump the picture
moved and the sound did not — for about two tenths of a second. The user heard
the first word of the next sentence twice: once from the tail of the stretch that
should have been skipped, then again when the audio finally landed in the right
place.

Why did nothing correct it? There *is* a periodic drift corrector. It runs every
second and fixes the audio when it has drifted more than **0.35s**. The leak was
**0.20s** — comfortably inside the tolerance, so it was never corrected. It only
snapped back once leaks accumulated past the threshold.

## Two details that matter in the fix

**Compute from the value you just seeked to, not from the element.** A media
seek is asynchronous; reading `video.currentTime` immediately after assigning it
is unreliable. The old code re-derived the audio position from
`video.currentTime`, which could read stale. The fix maps the *target* instead:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
js&lt;br&gt;
const reanchorAudio = (fullT) =&amp;gt; {&lt;br&gt;
  const want = clamp(mapFullToCut(fullT, kept), 0, audio.duration - 0.05);&lt;br&gt;
  if (Math.abs(audio.currentTime - want) &amp;lt;= 0.05) return;&lt;br&gt;
  // mute across the seek so the fragment is inaudible, unmute on 'seeked'&lt;br&gt;
  audio.muted = true;&lt;br&gt;
  audio.currentTime = want;&lt;br&gt;
  audio.addEventListener('seeked', unmute, { once: true });&lt;br&gt;
  setTimeout(unmute, 120);          // fallback if 'seeked' never arrives&lt;br&gt;
};&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
**Keep the wide tolerance for the periodic corrector, tighten only at the jump.**
The 0.35s window exists so the corrector does not fight the player's own seeks.
Loosening or removing it globally trades one bug for another. The jump path uses
its own 0.05s threshold, because there we know exactly where the audio should be.

## Prove it without a browser

The mapping functions are pure, so the fix can be demonstrated arithmetically —
no autoplay policies, no headless quirks, no flaky test. Extracting the real
functions from the shipped file and running the case above:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
plaintext&lt;br&gt;
words 0-10, removed 10-12, words 12-20&lt;br&gt;
audio drifted to 10.20s during the gap&lt;br&gt;
expected after the jump      10.00s&lt;br&gt;
error                         0.20s   &amp;lt; 0.35s tolerance  =&amp;gt; never corrected&lt;br&gt;
new re-anchor threshold       0.05s   =&amp;gt; corrects 10.20 to 10.00&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Sixteen assertions, including the boundary cases: no map means no change, a
0.1s jump does not trigger the skip, past the last range the video stays put.

## Three things I would tell my past self

1. **A tolerance window silently hides every error below it.** If your corrector
   has a threshold, ask what *small persistent* error it permits — ours was 0.2s
   of audible audio belonging to the wrong moment.
2. **Media elements are asynchronous.** Never derive a new position by reading
   an element you have just written to.
3. **Extract the pure logic and test it.** The interesting part of a media bug is
   usually the arithmetic around it, and that part needs no browser at all.

The app is open source (Tauri v2 + Rust on the desktop side, ONNX Runtime with
UVR-MDX-NET for the separation itself), and the extension is plain JavaScript:

- Project and source: https://github.com/SMSMy/HaramLite
- Site and guides: https://haramlite.com

If you have shipped a similar sync problem — subtitles, karaoke, dubbed audio,
anything where two media elements have to agree on where "now" is — I would like
to hear how you handled the drift window.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>browser</category>
      <category>javascript</category>
      <category>performance</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
