From a7b3452312e1961b1d8341b19f7ddcdb537e155e Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Wed, 24 Dec 2025 01:36:50 -0500 Subject: [PATCH] Implement audio master clock for A/V synchronization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Priority 3 fix from PLAYER_PERFORMANCE_ISSUES.md - addresses the root cause of A/V desync in player module. Changes: - Audio loop now tracks bytes written and updates master clock (audioTime) - Audio clock calculation: bytesWritten / (sampleRate × channels × bytesPerSample) - Video loop already syncs to audio master clock (from previous commit) - Master clock updates happen after each audio chunk write How it works: - Audio is the timing master, plays at natural rate - Video reads audio clock and adapts timing to stay in sync - If video >3 frames behind: drop frame and resync - If video >3 frames ahead: wait longer - Otherwise: adjust sleep duration for gradual sync Expected improvements: - Rock-solid A/V sync maintained over extended playback - No more drift between audio and video - Adaptive recovery from temporary slowdowns Combined with Priority 1 (larger audio buffers) and Priority 2 (FFmpeg volume control), this completes the core A/V sync architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- main.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/main.go b/main.go index f83e143..5989033 100644 --- a/main.go +++ b/main.go @@ -9414,6 +9414,7 @@ func (p *playSession) runAudio(offset float64) { // Larger chunks reduce read frequency and improve performance chunk := make([]byte, 16384) loggedFirst := false + bytesWritten := int64(0) // Track total audio bytes for master clock for { select { case <-p.stop: @@ -9434,6 +9435,13 @@ func (p *playSession) runAudio(offset float64) { // Volume is now handled by FFmpeg, just write directly // This eliminates per-sample processing overhead localPlayer.Write(chunk[:n]) + + // Update audio master clock for A/V sync + bytesWritten += int64(n) + // Calculate elapsed audio time: bytes / (sampleRate * channels * bytesPerSample) + elapsedTime := float64(bytesWritten) / float64(sampleRate*channels*bytesPerSample) + currentAudioTime := offset + elapsedTime + p.audioTime.Store(currentAudioTime) } if err != nil { if !errors.Is(err, io.EOF) {