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:
2026-05-16 14:50:30 +02:00
parent d72e439fd9
commit 6e22834c5c
12 changed files with 158 additions and 82 deletions

View File

@@ -128,27 +128,23 @@ func (d *clientDialer) claim(instance string) bool {
}
// release updates the instance slot after a dial attempt completes.
// On success (dialErr == nil) the instance stays latched in the active
// set — we never re-dial a successfully-connected instance. On error
// it frees the slot and schedules an exponentially-backed-off cooldown
// before the next retry.
// The slot is always freed — dial() only returns after handleConnection
// has returned, so the connection is already dead by the time we get
// here, and a future mDNS re-emission should be free to redial. On
// error it also schedules an exponentially-backed-off cooldown before
// the next retry.
func (d *clientDialer) release(instance string, dialErr error) {
d.mu.Lock()
defer d.mu.Unlock()
delete(d.active, instance)
if dialErr == nil {
// Latch: keep active[instance] = true so future discovery
// events for this instance are silently ignored. The server
// already handled the connection; re-dialing would create a
// duplicate session.
delete(d.failures, instance)
delete(d.cooldown, instance)
return
}
// Dial failed — release the slot so the instance can be retried
// after the backoff period.
delete(d.active, instance)
d.failures[instance]++
backoff := d.baseBackoff * time.Duration(1<<(d.failures[instance]-1))
if backoff > d.maxBackoff || backoff <= 0 {