Navidrome¶
The short version¶
There are two settings layers:
- The client app may request an audio format and/or a maximum bitrate.
- Navidrome can apply a matching per-player transcoding profile that selects or overrides the resulting stream codec and bitrate.
The final truth is in Navidrome’s stream log:
selectedFormat=...
selectedBitrate=...
format=...
bitRate=...
Those fields show what Navidrome actually served.
The roles¶
| Component | What it controls |
|---|---|
| Music library file | Original codec and bitrate, e.g. MP3 at 252 kbps |
| Client app | May request codec, maximum bitrate, both, or neither |
| Navidrome player profile | Server-side format/bitrate policy for the matching player |
| Navidrome/FFmpeg | Creates and serves the final stream |
How a stream is chosen¶
For each play request, Navidrome:
- Identifies the API client/player.
- Matches a saved player record when one exists.
- Determines whether the original file is allowed for that player/request.
- Applies an applicable per-player transcode profile.
- Streams either the original media or a transcoded output.
Typical successful transcode sequence:
Found matching player ... type=Arpeggi
Transcode decision: transcode ... targetBitrate=128 targetFormat=opus
Streaming file ... format=opus bitRate=128 transcoding=true
Client requests vs player profiles¶
A Navidrome per-player profile can override the format and bitrate asked for in a legacy Subsonic /rest/stream.view request.
Example: Amperfy can request:
format=mp3&maxBitRate=128
If Navidrome’s matching player profile is configured as Opus / 96 kbps, Navidrome can select and deliver:
selectedFormat=opus
selectedBitrate=96
format=opus
bitRate=96
The format query parameter alone is therefore not the final answer. Always inspect Navidrome’s selected and streamed values.
“Server choose codec” in a client¶
When a client is set to Server choose codec, it may omit format= but still request a maximum bitrate.
For example:
.../stream.view?...&maxBitRate=128
Navidrome can then use the matching player profile to choose the codec. “Server choose codec” means the client permits the server to decide; it does not guarantee that every codec selected by the server will play properly in that particular client.
Your tested clients¶
| Client/setup | Client request or behavior | Navidrome output from logs | Observed result |
|---|---|---|---|
| Amcfy, profile removed | Low-bitrate streaming behavior | MP3 / 128 kbps | Navidrome fallback result |
| Amcfy, player profile set | Transcode flow with server profile | Opus / 80 kbps | Profile honored |
| Substreamer | Explicit format=opus, maxBitRate=64 |
Opus / 64 kbps | Working |
| Amperfy, client MP3 + server MP3 | Requests MP3 / max 128 kbps | MP3 / 96 kbps | Working |
| Amperfy, server chooses codec + AAC server profile | No explicit format; max 128 kbps | AAC / 96 kbps | No audible playback in your test |
| Amperfy, server overridden to Opus | Client request overridden | Opus / 96 kbps | No playback in your test |
| Arpeggi | Explicit format=opus, maxBitRate=128 |
Opus / 128 kbps | Working; reports playing and later paused at 28 seconds |
| Navidrome Web UI in Firefox | No applicable transcode requirement | Original MP3 / 196 kbps | Direct play |
Arpeggi: confirmed configuration¶
Arpeggi is the clearest working Opus example in your logs.
The request sent to Navidrome is:
format=opus
maxBitRate=128
estimateContentLength=false
Navidrome responds by selecting:
targetFormat=opus
targetBitrate=128
selectedFormat=opus
selectedBitrate=128
The delivered stream is:
format=opus
bitRate=128
sampleRate=48000
channels=2
transcoding=true
Arpeggi then reports successful playback:
positionMs=1000 state=playing
and later:
positionMs=28000 state=paused
The first stream connection was cancelled after a short initial transfer, then a second connection completed with approximately 3.08 MB delivered. This is normal-looking iOS stream setup behavior rather than a server failure.
Recommended Arpeggi cellular profile¶
Arpeggi app: Opus, maximum 128 kbps
Navidrome player profile: Optional; do not override it with another codec unless tested
Because Arpeggi already explicitly asks for Opus / 128 kbps and successfully plays it, the simplest setup is to let Arpeggi control its own streaming request. If you create a Navidrome profile for it, use the same target:
Opus audio, 128 kbps
Do not set a lower server profile unless you intentionally want Navidrome to override Arpeggi’s requested quality.
Recommended practical settings¶
Configure each client according to tested compatibility.
| Client | Recommended cellular target | Notes |
|---|---|---|
| Arpeggi | Opus, 128 kbps | Confirmed working from your logs |
| Substreamer | Opus, 64 kbps | Confirmed working; raise to 96 kbps if you want more quality |
| Amcfy | Opus, 96–128 kbps if confirmed in playback | Server successfully delivered Opus; verify in app |
| Amperfy | MP3, 96–128 kbps | Keep app and Navidrome profile aligned on MP3 |
| Navidrome Web UI | Original stream | Avoid needless lossy re-encoding on a capable desktop connection |
Codec and bitrate¶
- Codec is the compression method: MP3, AAC, Opus, Vorbis, FLAC, etc.
- Bitrate is the amount of data sent each second: 64, 96, 128 kbps, etc.
- A lower bitrate uses less mobile data but can reduce quality.
- Re-encoding an already lossy file such as MP3 is a tradeoff for bandwidth; use it mainly for cellular streaming or constrained networks.
Useful cellular choices:
| Goal | Opus | MP3 | Approximate data per hour |
|---|---|---|---|
| Minimum data/background listening | 64 kbps | 96 kbps | 29 MB/h at 64 kbps; 43 MB/h at 96 kbps |
| Balanced mobile streaming | 96 kbps | 128 kbps | 43 MB/h at 96 kbps; 58 MB/h at 128 kbps |
| Better music quality | 128 kbps | 160–192 kbps | 58 MB/h at 128 kbps |
| Maximum quality | Original | Original | Depends on the source |
How to verify a client¶
- Start playback in the desired app.
- Watch the Navidrome container log:
docker logs -f navidrome 2>&1 | grep -E "Found matching player|Transcode decision|Streaming file|selectedFormat|selectedBitrate"
- Find the player Navidrome matched:
Found matching player ... client=... type=...
- Confirm the actual stream:
Streaming file ... format=... bitRate=... transcoding=true
Do not rely only on:
- The filename extension.
- “OGG” in an app UI—Ogg is a container and can hold Opus or Vorbis.
- The
format=URL parameter—Navidrome can override it. - HTTP status
200or206—those confirm delivery, not necessarily successful audio decoding.
Reading common log messages¶
| Log message | Meaning |
|---|---|
Transcode decision: transcode |
Navidrome will convert the source before streaming |
reasons=[audio bitrate not supported] |
Source bitrate exceeds the applicable bitrate limit |
Applying server-side transcoding override |
A Navidrome player profile is overriding the client request |
cached=true |
Navidrome reused a cached transcode rather than generating it again |
httpStatus=206 |
Partial-content/range response; normal for seeking or player probing |
broken pipe / context canceled |
Client closed a stream connection; commonly harmless during retries/prebuffering unless playback fails |
Last.fm error(9): Invalid session key |
Last.fm authentication expired; unrelated to audio playback |
Bottom line¶
For Arpeggi, use Opus at 128 kbps for cellular streaming. It explicitly requests that format, Navidrome serves it unchanged, and the playback reports confirm that it plays successfully.
More generally, use the codec/bitrate each individual client has demonstrated it can play, and verify every change through Navidrome’s selectedFormat, selectedBitrate, format, and bitRate log fields.