Skip to content

feat: localav audio backend (FFmpeg + miniaudio)#894

Draft
dweymouth wants to merge 2 commits intomainfrom
feat/mpv-replacement
Draft

feat: localav audio backend (FFmpeg + miniaudio)#894
dweymouth wants to merge 2 commits intomainfrom
feat/mpv-replacement

Conversation

@dweymouth
Copy link
Copy Markdown
Owner

@dweymouth dweymouth commented Apr 1, 2026

Summary

Replaces libmpv as the local audio engine with a bespoke stack built on FFmpeg (libav*) and miniaudio. Gated behind the localav build tag so mpv remains the default until this is production-ready.

Build:

go build -tags "migrated_fynedo localav" ./...

Why

The primary motivation is direct PCM sample access to enable additional visualizers such as ProjectM — libmpv's abstraction layer prevents tapping decoded frames — as well as other new audio features such as crossfade playback that aren't supported by MPV. A lower-level pipeline also removes the large libmpv native binary dependency.

Stack

Layer Library
Demux / HTTP streaming libavformat
Decode libavcodec
EQ / ReplayGain / metering libavfilter
Audio output + device enum miniaudio 0.11.21 (vendored single-header)

Features

  • Gapless playback — next decoder is pre-opened and swapped in before the ring buffer drains, so there is no audible gap at track boundaries
  • SPSC lock-free ring buffer between the decode goroutine and the miniaudio callback
  • 15-band parametric EQ via avfilter (same filter-string format as mpv backend)
  • ReplayGain from file tags via avfilter volume node
  • Audio device enumeration and exclusive mode (CoreAudio / WASAPI / ALSA)
  • Peak/RMS metering via astats avfilter
  • Seek safety — decode goroutine stopped synchronously before filter-graph rebuild
  • PCM sample hook point ready for a future ProjectM visualizer

Commit structure

  1. Refactor — promote shared Equalizer, AudioDevice, MediaInfo, and LocalPlayer interface out of the mpv package into backend/player, so both backends can satisfy the same contract without importing each other
  2. feat — the new backend/player/localav/ backend + build-tagged app-init files

What's not done yet

  • ICY radio metadata (stub no-ops; mpv backend still handles radio)
  • Windows / Linux build validation (only tested on macOS + Homebrew FFmpeg)
  • Flatpak / AppImage packaging adjustments

Test plan

  • go build -tags "migrated_fynedo localav" ./... compiles cleanly
  • go test -tags "migrated_fynedo localav" ./backend/player/localav/... passes
  • Play FLAC, MP3, AAC, Opus, OGG — confirm audio output
  • Gapless transition between tracks — no audible gap
  • Seek mid-track — position jumps correctly, no desync
  • Toggle EQ bands — audio changes without crash
  • Set ReplayGain to track mode — loudness difference audible
  • Open audio device selector — devices list, switching works
  • Enable peak meter in Now Playing — values update during playback
  • Default mpv build (go build -tags migrated_fynedo ./...) still compiles and plays

🤖 Generated with Claude Code

dweymouth and others added 2 commits March 31, 2026 18:28
Define LocalPlayer interface and shared AudioDevice/MediaInfo types in
backend/player so both mpv and a future alternative backend can implement
the same contract without importing each other.

- backend/player/equalizer.go: Equalizer interface + ISO10/15BandEqualizer
  moved from backend/player/mpv; mpv/equalizer.go is now type aliases
- backend/player/localplayer.go: LocalPlayer interface, AudioDevice, MediaInfo
- backend/player/mpv/player.go: implements player.LocalPlayer
- backend/app.go: LocalPlayer field is now player.LocalPlayer interface;
  initMPV/setupMPV renamed to initLocalPlayer/setupLocalPlayer (dispatch
  via build-tagged files)
- backend/app_player_mpv.go: //go:build !localav — extracts initLocalPlayer
  for the mpv backend
- backend/playbackengine.go, ui/**:  update type assertions and imports to
  use player.* types instead of mpv.*

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New alternative local player backend gated behind the `localav` build tag.
Build with: go build -tags "migrated_fynedo localav" ./...

Stack:
- libavformat/libavcodec/libavfilter for demux, decode, and DSP
- miniaudio (vendored single-header 0.11.21) for audio output and
  device enumeration via CoreAudio/WASAPI/ALSA

Key features:
- Gapless playback via double-slot decoder: next track is pre-opened and
  swapped in immediately when the current decoder hits EOF, before the
  ring buffer drains, eliminating audible gaps
- SPSC lock-free ring buffer between decode goroutine and miniaudio callback
- 15-band parametric EQ via avfilter graph (same filter string as mpv backend)
- ReplayGain from file tags via avfilter volume node
- Audio device enumeration and selection including exclusive mode
- Peak/RMS metering via astats avfilter
- Seek safety: decode goroutine is stopped synchronously before
  av_player_seek rebuilds the filter graph (prevents SIGSEGV race)
- Direct PCM sample access hook point for future ProjectM visualizer

Files:
- backend/player/localav/av_player.{h,c} — C engine
- backend/player/localav/player.go — CGo bindings, implements LocalPlayer
- backend/player/localav/miniaudio.h — vendored miniaudio 0.11.21
- backend/player/localav/cgo_{darwin,linux,windows}.go — platform CGo flags
- backend/app_player_localav.go — //go:build localav app init

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant