Fix ESP32 audio dropouts and improve streaming reliability
FLAC encoder now tracks frame numbers and reuses per-channel sample buffers to reduce allocations. Client dialer always frees the active slot on release so mDNS re-emissions can reconnect cleanly. Server send buffer uses drop-oldest instead of hard failure, with lateness tracking. Added engine stats logging (chunks/sec, kbps) and writer diagnostics. UI simplified to a single play/pause toggle that auto-starts playback on preset selection. Added --preset CLI flag for headless startup. Removed stale systemd service files. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -54,11 +54,17 @@ func TestClientDialerDedupesByInstance(t *testing.T) {
|
||||
var mu sync.Mutex
|
||||
var seen []string
|
||||
|
||||
// Block each dial until the test releases it, so concurrent
|
||||
// duplicate emissions for the same instance arrive while the
|
||||
// dial is in flight — which is what the dedupe protects against.
|
||||
gate := make(chan struct{})
|
||||
|
||||
dial := func(ctx context.Context, info *discovery.ClientInfo) error {
|
||||
atomic.AddInt32(&dialCalls, 1)
|
||||
mu.Lock()
|
||||
seen = append(seen, info.Instance)
|
||||
mu.Unlock()
|
||||
<-gate
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -72,39 +78,32 @@ func TestClientDialerDedupesByInstance(t *testing.T) {
|
||||
close(done)
|
||||
}()
|
||||
|
||||
// Emit the same instance 3 times and a different instance once
|
||||
// Emit the same instance 3 times and a different instance once,
|
||||
// all while dials are blocked in `gate`.
|
||||
in <- &discovery.ClientInfo{Instance: "a._sendspin._tcp.local.", Host: "1.1.1.1", Port: 8928, Path: "/sendspin"}
|
||||
in <- &discovery.ClientInfo{Instance: "a._sendspin._tcp.local.", Host: "1.1.1.1", Port: 8928, Path: "/sendspin"}
|
||||
in <- &discovery.ClientInfo{Instance: "b._sendspin._tcp.local.", Host: "1.1.1.2", Port: 8928, Path: "/sendspin"}
|
||||
in <- &discovery.ClientInfo{Instance: "a._sendspin._tcp.local.", Host: "1.1.1.1", Port: 8928, Path: "/sendspin"}
|
||||
|
||||
// Wait until at least 2 unique dials have happened
|
||||
deadline := time.After(500 * time.Millisecond)
|
||||
for {
|
||||
if atomic.LoadInt32(&dialCalls) >= 2 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-deadline:
|
||||
t.Fatalf("timed out waiting for dial calls, got %d", atomic.LoadInt32(&dialCalls))
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
waitForCalls(t, &dialCalls, 2, 500*time.Millisecond)
|
||||
|
||||
// Give any extra (incorrect) calls a chance to fire
|
||||
// Give any extra (incorrect) calls a chance to fire while dials
|
||||
// are still blocked.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
cancel()
|
||||
<-done
|
||||
|
||||
if got := atomic.LoadInt32(&dialCalls); got != 2 {
|
||||
t.Errorf("dialCalls = %d, want 2 (one per unique instance)", got)
|
||||
t.Errorf("dialCalls = %d, want 2 (one per unique instance while in flight)", got)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if len(seen) != 2 {
|
||||
mu.Unlock()
|
||||
t.Fatalf("seen = %v, want 2 entries", seen)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
close(gate)
|
||||
cancel()
|
||||
<-done
|
||||
}
|
||||
|
||||
func waitForCalls(t *testing.T, counter *int32, want int32, timeout time.Duration) {
|
||||
|
||||
Reference in New Issue
Block a user