Cache + Transcoding - Emptydownloaded file

Issue description:

I have a navidrome server installed on Synology NAS.
I would like to have symphonium stream original files when playing and storing an opus 320 kb for offline rolling cache with automatic rules.
I did setup automatic rules or even when I do it manually I get retries and there is always error “Empty Downloaded File”.
The logs from Navidrome show

time=“2026-03-17T22:24:54+01:00” level=info msg=“Streaming file” artist=Metallica bitRate=320 cached=false format=opus originalBitRate=1012 originalFormat=flac requestId=szynka2/MKD1lxh5E8-000048 title=“The House Jack Built” transcoding=true user=marek
time=“2026-03-17T22:25:04+01:00” level=info msg=“Streaming file” artist=Metallica bitRate=320 cached=false format=opus originalBitRate=1012 originalFormat=flac requestId=szynka2/MKD1lxh5E8-000051 title=“The House Jack Built” transcoding=true user=marek

I will use the app to share the logs.

If in the quality of offline I select original I do get files. For faveorites I have set original quality and those do get downloaded and put in permanent cache

Logs:

Upload description: yeld78

Additional information:

Symfonium settings for cache quality is 320 kbps, cache location internal storage.
Phone : Galaxy S23 Ultra, OneUI 8, Android 16

Reproduction steps:

Setup Navidrome transcoding setting for opus audiot to 320.
In symphonium go to a a track select the 3 dots and click add to cache.
I the next screen select Transcoding 320 kbps, select add to rolling cache.
Click add, go to settings manage offline files and look at the download queue and see the track is trying to be downloaded and after couple of tries it will show error "Empty Downloaded File (Hex number)/true

Media provider:

Subsonic

Screenshots:

2026-03-17 22:25:54.910 Verbose DownloaderService  Debug (DB53409B6F7805A87B9291BDA0A4948B) [16 ms] responseHeadersEnd: Response{protocol=http/1.1, code=200, message=OK, url=http://192.168.1.20:4533/rest/stream.view?id=p5NCxIevYl98iVqmBM6ske&maxBitRate=320&format=opus&u=REDACTED&t=REDACTED&s=REDACTED&v=1.13.0&c=Symfonium&f=json}
2026-03-17 22:25:54.910 Verbose DownloaderService  Debug (DB53409B6F7805A87B9291BDA0A4948B) [16 ms] responseBodyStart
2026-03-17 22:25:54.911 Verbose DownloaderService  Debug (DB53409B6F7805A87B9291BDA0A4948B) [16 ms] responseBodyEnd: byteCount=0

The server does return 0 byte content with 200 code that’s strange.

@deluan Anything you can think of that would trigger that ? Or what can they do to see more server side ?

After this answer I checked navidrome and I looks like ffmpeg is missing libopus.

I will be focusing on navidrome now. Thx for pointing me in the right direction.

Edit : After switching to ffmpeg7 path in navidrome transcoding setting instead of using synology’s system ffmpeg everything seems to work as expected

I did some changes to better report errors (logs and http status) on situations like this: fix(server): improve transcoding failure diagnostics and error responses by deluan · Pull Request #5227 · navidrome/navidrome · GitHub

1 Like

Thanks but please :

  • Subsonic endpoints (stream, download, getTranscodeStream) now return a Subsonic error response when transcoding produces 0 bytes or io.Copy fails

Don’t do that, while I do have security for 0 bytes, if you return json data as the media data I have no way to know, nor ffmpeg or Chromecast or everything.

That part of Subsonic API was always absurd and LMS now properly returns http errors for an endpoint that is not consumed by Subsonic clients but audio renderers.

If you can’t return an http error, 0 is better for Symfonium to not cache invalid data without user errors and making users life harder.

From my perspective - if I had access to logs from symfonium or the error on the file would say no data received from server then I would look in navidrome and would not ask here. On the other hand if navidrome would report the error in the info logs I would also figure this out without external help. Just my 2 cents.

Thx for your efforts and help

  1. You have access to the logs it’s in the docs :wink:
  2. The error message was pretty clear, it’s getting a 0 byte file, not sure what more it could have shown.

The description in the PR is wrong (I’ll fix it). The actual implementation returns Subsonic errors for the old endpoints (stream and downlaod), but proper http errors in getTranscodeStream.

That’s what I did in the PR, proper showing errors in the logs when this situation happen

Yeah but that’s what it should not do. An app that downloads a media to a file, will not read the content of the file to see if there’s some json in it or the actual media content.

As I said that part of the API was dumb the endpoint is consumed by things that expect raw content and can’t analyse json message with 200 code.

While Symfonium will use the new endpoints so won’t be impacted, any other client trying to offline cache the media with transcoding will cache the json and never know there’s an error.

I agree it is dumb, but that’s what is documented. If we want to change the behaviour, I’m all for that, but we should document it in OpenSubsonic?

Well, this was always like that (return 200 + json/xml on errors), so I’m not changing anything.

We can suggest it’s better, but we can’t really enforce that anyway.

And yes it’s what most already do, but since you are touching it, it’s the perfect timing to not do it anymore :slight_smile:

Debugging this user would have been insanely more complicated if you did return the json it would not have been detected, just random playback errors after trying to play the json file.

Returning 0 bytes and no error message was always wrong. That’s what I’m trying to fix here.

With my changes, it will return a response with generic error 0 and “transcoding failed: empty output” message.
And the actual issue with ffmpeg will be shown in Navidrome logs.

IMO, not really: you’d see the “transcoding failed: empty output” and you’d just ask them to check the server’s log, right? So I’d argue this is better for debugging :slight_smile:

Anyway, I don’t feel comfortable changing this without documenting it. It would also affect GetCoverArt, GetAvatar and a few other endpoints that should return raw data. This should be discussed with the larger OpenSubsonic group.

That’s the whole point :slight_smile: No it would not be seen at all.

When you download something and the endpoint returns 200 you take the bytes and write them to the target file. You do not read a couple of byte in memory, see if it’s a json or not and then write your memory cache then resume the download to the target file. So what all clients do is caching a media file thinking it’s ok while it’s just a small json data.

Same when you you cast remote device can say they can’t connect to endpoints, but they can’t say hey I get json, they will just fail with random can’t play error.

Anyway yes the logs on Navidrome will help, and do not change if you do not think it’s useful, but no returning a json does not help debugging client side at all.

Good point! I reverted it to returning status 200 with 0 bytes as it was before, while keeping the new logs in place. In GetTranscodeStream it returns a proper 500 error.

Anyway, I still think it is worth to open this discussion in the OS forum.

EDIT: Should we amend the transcoding extension to say that servers that implement it should always return a transcodeParams in getTranscodeDecision, and clients should always use getTranscodeStream? This would avoid reliance on the broken stream endpoint for new clients (and old ones that are willing to modernize)

We can discuss on OS yes, but as said it’s hard to enforce that late, adding an extension would not bring much to the table either. So if a simple should in the docs works for you then why not, but I don’t think we can get more without endless complicated discussions :slight_smile:

For the transcoding endpoint that would be strange in many ways to send an obscure transcodeParam to a transcode endpoint to get a non transcoded stream. Makes it hard to what will happen in the end.

Maybe we can add a parameter to say don’t actually transcode (similar to the format=raw concept of the OG), no idea for naming right now but you get the idea.