HLS Audio and Video Drift After an Ad Break or Encoder Restart
Trace HLS lip-sync drift across ad insertion and encoder restarts by comparing timestamps, discontinuity sequences, renditions, and browser playback evidence.
When speech no longer matches a speaker's mouth after an ad break, channel change, or encoder restart, the visible symptom does not identify the fault. A playlist boundary may be wrong, audio and video timestamps may diverge, one rendition may be misaligned, or the player may be rendering a different track than the one being inspected.
The reliable approach is to compare the same timeline checkpoints across the video and audio media playlists, then correlate those checkpoints with parsed timestamps and playback events. Test only streams you own or are authorized to inspect. Remove signed URLs, keys, cookies, tokens, viewer IDs, and private hostnames from logs.
Verification method — September 26, 2026: We ran a deterministic Node.js 24.12.0 fixture using synthetic video/audio presentation timestamps and discontinuity sequence numbers. It confirmed a stable 40–60 ms offset across a splice, detected a synthetic audio-only 500 ms offset increase, and distinguished that case from a shared timestamp reset. This verifies only fixture arithmetic and checkpoint matching. It does not parse transport streams or fMP4, run an encoder, play an ad, test a browser/player/device, or establish a universal acceptable lip-sync threshold. The fixture is scripts/test-hls-av-sync-fixture.cjs.
First decide what “drift” means
Capture whether the error is constant, cumulative, or introduced at one boundary. Note which audio rendition is selected and whether the video quality changed at the same time.
| Symptom | First hypothesis to test | Evidence to compare |
|---|---|---|
| Same offset before and after a splice | A pre-existing encode or output-path offset | Paired audio/video timestamps well before the boundary |
| Sudden offset change at an ad or restart | Bad splice timestamp mapping or an unmarked timeline reset | Last content segment and first inserted/restarted segments |
| Offset grows throughout a long program | Clock-rate or timestamp progression mismatch | Several checkpoints over time, not only segment starts |
| Only one quality level drifts | Rendition-specific timing or mux issue | Matching content time across every video playlist |
| Only one language track drifts | Alternate-audio packaging or timestamp alignment | Selected audio playlist against the same video rendition |
| Offset changes when the browser seeks or switches tracks | Player buffering/switch path may be involved | Parsed media timestamps plus player events and current track IDs |
Do not adjust audio delay until you know which pattern you have. A constant correction can hide a splice defect and make other segments wrong.
Compare matching checkpoints, not segment numbers
Media sequence numbers identify playlist positions; they do not guarantee that audio and video segments with the same number cover the same instant. Align observations by program date-time when present and unambiguous, content/ad boundary, segment presentation interval, and discontinuity sequence. Record the playlist snapshot and fetch time because live playlists move.
For each checkpoint, note:
- Video rendition and audio rendition/group selected.
- Segment URI identity in a sanitized form, media sequence, discontinuity sequence, duration, and program date-time if available.
- First and last presentation timestamps from an authorized media analyzer.
- Whether the playlist contains
EXT-X-DISCONTINUITYat that boundary. - The first content segment after the ad and the first segment after an encoder restart.
- Any timestamp wrap, reset, gap, overlap, or track change reported by the demuxer.
Keep raw media local and access-controlled. A diagnostic report usually needs timestamp values and segment labels, not the media or a reusable URL.
Audit discontinuities across every rendition
RFC 8216 defines EXT-X-DISCONTINUITY-SEQUENCE so clients can synchronize renditions whose playlists contain discontinuities. Apple’s authoring guidance says encoding-continuity breaks need a discontinuity tag and that variants and renditions must mark discontinuities at the same points in time. A tag in only one playlist can leave switching ambiguous even when each individual playlist appears plausible.
At each splice or restart, compare the relevant video playlists and each selectable audio playlist. Verify the discontinuity count and boundary refer to the same program instant; do not merely compare line numbers. If a sliding live playlist has removed earlier discontinuities, confirm its sequence base advances consistently. Where EXT-X-PROGRAM-DATE-TIME is used, verify that the mapping is not ambiguous across the boundary.
Also inspect the media timeline itself. A playlist tag communicates a boundary; it does not repair an audio timestamp that jumps by half a second or a video track that continues on a different clock. For fMP4, inspect decode-time continuity as well as presentation timestamps. For MPEG-TS, inspect timestamp progression and continuity around the splice.
Trace the first bad boundary through the player
For an MSE-based hls.js path, capture the library version, level and audio-track changes, fragment loading/parsing events, errors, and LEVEL_PTS_UPDATED data where exposed by the installed version. Correlate those events with video.currentTime, video.audioTracks where supported, and the rendered symptom. Native HLS implementations may not expose the same internals; use their available diagnostics and label the missing evidence.
Do not infer demuxed PTS directly from wall-clock time. Keep monotonic event timing separate from media timestamps. A page can also resume after background throttling with stale buffers or an expired live window; if the issue only occurs after a lock or tab switch, compare against the background recovery guide. For playback stalls during the same investigation, see HLS buffering diagnosis.
Use a controlled reproduction matrix
On a stream and insertion workflow you are authorized to test, replay the same segment set and change one factor at a time:
| Case | What it isolates |
|---|---|
| No ad, continuous encoder | Baseline timestamp relationship |
| Ad inserted with the same encoder clock | Splice mapping without a clock reset |
| Encoder restart without an ad | Restart/discontinuity handling |
| Ad plus encoder restart | Interaction between splice and timestamp reset |
| Each video rendition with default audio | Variant alignment |
| Each alternate audio track with one fixed video rendition | Audio rendition alignment |
| Seek across the boundary and switch tracks on both sides | Player transition behavior |
Save playlist snapshots and approved analyzer output at each transition. Compare the last clean pre-boundary interval, the first interval after it, and several intervals farther ahead. If the offset jumps once and then stays constant, investigate the splice mapping. If it keeps growing, investigate clock progression. If only one rendition differs, fix packaging for that rendition rather than applying a global player delay.
Choose the fix at the layer that owns the mismatch
- Boundary absent or inconsistent: correct playlist generation and discontinuity sequence across all affected variants and renditions.
- Timestamps jump only in inserted media: correct the packager or ad-stitcher timestamp mapping; validate both sides of the splice.
- One track drifts progressively: inspect its encoder clock, sample timing, and mux; do not compensate every rendition.
- Only a player switch reproduces it: reduce the case to a minimal authorized fixture, capture exact library/browser versions and events, then investigate buffering/remux behavior.
- Only a live resume reproduces it: refresh the playlist and establish the current seekable window before restoring position; do not reuse a stale absolute
currentTimeblindly.
Retest the entire window before and after the change, all supported renditions, track switches, and seek paths. Keep any intentional offset documented at the correct output stage, not hidden in a viewer-side permanent delay.
Report evidence without overclaiming
A useful report states the selected tracks, boundary type, playlist sequence values, timestamp offset before and after, whether the offset then grows, the player path and versions, and the exact reproduction case. Distinguish a synthetic fixture, a static playlist review, an analyzer measurement, and physical playback. They answer different questions.
Primary references
- RFC 8216: HTTP Live Streaming
- Apple: HLS authoring specification for Apple devices
- Apple: HLS authoring specification appendixes
- hls.js events API
- hls.js: event definitions
- MDN: HTMLMediaElement currentTime
- W3C: Media Source Extensions
Start at the first boundary where paired audio/video timing changes. Matching playlist discontinuities, rendition-wide timing, parsed timestamps, and player events together will show whether the owner is the packager, ad stitcher, encoder, player transition, or live-resume path.