Skip to content

Conversation

@nicholas-gh
Copy link

@nicholas-gh nicholas-gh commented Jan 16, 2026

Example log:

2026-01-16 17:57:56.856 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Digitally Incorporated playlist returned 3 URLs
2026-01-16 17:57:56.856 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 1: http://prem2.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:56.856 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 2: http://prem4.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:56.856 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 3: http://prem1.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.036 WARNING (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Stream URL http://prem2.classicalradio.com:80/bach?<<PLAYER_KEY>> returned 403 (candidate 1/3), trying next
2026-01-16 17:57:57.054 DEBUG (MainThread) [aiosendspin.server.group] Sending server state to client ma_5ba8ksckpi
2026-01-16 17:57:57.054 DEBUG (MainThread) [aiosendspin.server.client.ma_5ba8ksckpi] Enqueueing message: ServerStateMessage
2026-01-16 17:57:57.221 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Selected stream URL 2/3: http://prem4.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.223 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Getting stream URL for classicalradio:bach
2026-01-16 17:57:57.242 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Digitally Incorporated playlist returned 3 URLs
2026-01-16 17:57:57.242 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 1: http://prem2.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.242 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 2: http://prem4.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.242 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 3: http://prem1.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.312 WARNING (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Stream URL http://prem2.classicalradio.com:80/bach?<<PLAYER_KEY>> returned 403 (candidate 1/3), trying next
2026-01-16 17:57:57.344 DEBUG (MainThread) [music_assistant.streams] stream aborted for digitally_incorporated://radio/classicalradio:21stcentury in 34.83 seconds - seconds streamed/buffered: 36.00
2026-01-16 17:57:57.483 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Selected stream URL 2/3: http://prem4.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:57.737 DEBUG (MainThread) [music_assistant.audio] retrieved streamdetails for library://radio/254 in 1217 milliseconds
2026-01-16 17:57:57.842 DEBUG (MainThread) [music_assistant.players] Handling command play_media for player Web (Safari on Mac) (by user j.smith)
2026-01-16 17:57:57.843 DEBUG (MainThread) [music_assistant.sendspin.ma_5ba8ksckpi] Received PLAY_MEDIA command on player Web (Safari on Mac) with uri http://192.168.1.50:8097/single/K2M5DnN3/ma_5ba8ksckpi/9118d0aa43af4ca99b8db1f462457791.flac
2026-01-16 17:57:57.844 DEBUG (MainThread) [music_assistant.providers.sendspin.timed_client_stream] New subscriber joining: buffer is empty, starting at current_position=0.000s
2026-01-16 17:57:57.844 DEBUG (MainThread) [aiosendspin.server.group] Starting play_media with play_start_time_us=None
2026-01-16 17:57:57.844 INFO (MainThread) [aiosendspin.server.stream] Player ma_5ba8ksckpi assigned to main channel
2026-01-16 17:57:57.845 DEBUG (MainThread) [aiosendspin.server.group] Sending stream start message to client ma_5ba8ksckpi
2026-01-16 17:57:57.845 DEBUG (MainThread) [aiosendspin.server.client.ma_5ba8ksckpi] Enqueueing message: StreamStartMessage
2026-01-16 17:57:57.846 DEBUG (MainThread) [aiosendspin.server.client.ma_5ba8ksckpi] Enqueueing message: GroupUpdateServerMessage
2026-01-16 17:57:57.846 DEBUG (MainThread) [aiosendspin.server.client.ma_5ba8ksckpi] Enqueueing message: ServerStateMessage
2026-01-16 17:57:57.852 DEBUG (MainThread) [music_assistant.sendspin.ma_5ba8ksckpi] Received GroupEvent: GroupStateChangedEvent(state=<PlaybackStateType.PLAYING: 'playing'>)
2026-01-16 17:57:57.853 DEBUG (MainThread) [music_assistant.sendspin.ma_5ba8ksckpi] Group state changed to: PlaybackStateType.PLAYING
2026-01-16 17:57:57.860 DEBUG (MainThread) [music_assistant.streams] Starting queue item stream for Bach (digitally_incorporated://radio/classicalradio:bach) - using buffer: False - using fade-in: False - using volume normalization: fixed_gain
2026-01-16 17:57:57.898 DEBUG (MainThread) [music_assistant.audio.media_stream] Started media stream for digitally_incorporated://radio/classicalradio:bach - using streamtype: icy - pcm format: f32le - ffmpeg PID: 49696
2026-01-16 17:57:57.909 DEBUG (MainThread) [music_assistant.audio] Start streaming radio with ICY metadata from url http://prem4.classicalradio.com:80/bach?<<PLAYER_KEY>>
2026-01-16 17:57:58.279 DEBUG (MainThread) [music_assistant.audio.media_stream] First chunk received after 0.38 seconds (codec detected: mp3)
2026-01-16 17:57:58.279 DEBUG (MainThread) [music_assistant.streams] First audio chunk received for Bach (digitally_incorporated://radio/classicalradio:bach) after 0.42 seconds

Patch written by Codex and then read by me and adjusted again with Codex.

…e giving up

I'm finding that prem2 (often the first URL in in the list) returns 403 'Too many clients'. Failing over to the next URL works and plays the stream.
@OzGav
Copy link
Contributor

OzGav commented Jan 16, 2026

@nicholas-gh lint is failing

@benklop for your review

@benklop
Copy link
Contributor

benklop commented Jan 17, 2026

It probably makes sense to try the next URL on any 4xx or 5xx response. there are many responses besides a valid 200 OK or 30x Redirect that should cause us to skip to the next URL.

@benklop
Copy link
Contributor

benklop commented Jan 17, 2026

Also, 403 makes sense if another stream is already playing in another client or something, they only allow one at a time. Actually trying to retrieve the stream to see if it works seems reasonable, but is going to be a tad problematic if you're switching to a new stream while already playing. I don't know if the old stream will have been disconnected / stopped playback yet at this point, or if that only happens once a new stream is returned.

Either way it makes a lot more sense to provide multiple URLs to the StreamDetails object we return since that seems to be supported.

Copy link
Contributor

@benklop benklop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've submitted a PR to the author, nicholas-gh#1, that should make this simpler and cleaner and still let us fall back when a stream doesn't work

Comment on lines +637 to 641
# Try each URL until one responds without 403 (forbidden)
candidate_urls = [str(url) for url in playlist if isinstance(url, str) and str(url).strip()]
if not candidate_urls:
msg = f"{self.domain}: No valid stream URLs received from Digitally Incorporated API"
raise MediaNotFoundError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this section says it's trying each URL to see if it returns a 403, but that's happening later. this just checks if it's a valid string, converts the string to a string, checks if it's not an empty string after being stripped, then returns the unstripped url converted to a tring again, for each candidate. this seems like a rather confusing way to just check the type of each candidate and make sure it has content.

continue

msg = f"{self.domain}: Unable to get working stream URL after {total_candidates} attempts"
raise MediaNotFoundError(msg)
Copy link
Contributor

@benklop benklop Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole thing seems a little over the top to me, there's a lot of extra stuff happening, conversions to string after determining we already have a string, and looping over the playlist multiple times.

I have nicholas-gh#1 as an alternative that should work and be simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@OzGav if you prefer I can submit that as a PR directly here, just thought it would be courteous to @nicholas-gh to do it this way.

@nicholas-gh
Copy link
Author

I've submitted a PR to the author, nicholas-gh#1, that should make this simpler and cleaner and still let us fall back when a stream doesn't work

Thank you both. I will try out nicholas-gh#1 since it seems I can reliably get a 403 to test with. I don't know if your project has a preference on if I make a new MR or change the commits in this one, so please close if that's the preference.

@nicholas-gh nicholas-gh marked this pull request as draft January 17, 2026 09:59
@nicholas-gh
Copy link
Author

I've realised that maybe the server returns 403 'too many clients' when it is too busy, not necessarily that we are (accidentally?) having too many clients connected [with the same player key].

@benklop
Copy link
Contributor

benklop commented Jan 18, 2026

I've realised that maybe the server returns 403 'too many clients' when it is too busy, not necessarily that we are (accidentally?) having too many clients connected [with the same player key].

That seems possible, but it shouldn't really be a 403 in that case. 4xx codes are CLIENT errors, and 5xx are server errors. 503 Unavailable is a common response with an overloaded server. DI.fm has been around long enough I would hope they know how to use the correct http codes.

@nicholas-gh
Copy link
Author

nicholas-gh commented Jan 18, 2026

I've realised that maybe the server returns 403 'too many clients' when it is too busy, not necessarily that we are (accidentally?) having too many clients connected [with the same player key].

That seems possible, but it shouldn't really be a 403 in that case. 4xx codes are CLIENT errors, and 5xx are server errors. 503 Unavailable is a common response with an overloaded server. DI.fm has been around long enough I would hope they know how to use the correct http codes.

True, yes.

Their support person sent me this message:

There is an ongoing issue with Prem2 at moment, and our Dev team is working to fix it.

So, I think we've got a small window to improve our MA handling, before they fix it and our test framework goes away 😆

@benklop
Copy link
Contributor

benklop commented Jan 18, 2026 via email

@nicholas-gh
Copy link
Author

nicholas-gh commented Jan 18, 2026

Have you been able to test it yet then?

Yes; nicholas-gh#1 (comment)

It doesn't work as-is (as some other code still relies on the _get_stream_url(), so an AttributeError happens) and a quick attempt to fix that causes some JSON-encoding recursion error which I didn't investigate yet; It might be Tuesday before I get chance.

@benklop
Copy link
Contributor

benklop commented Jan 18, 2026 via email

@benklop
Copy link
Contributor

benklop commented Jan 18, 2026 via email

@benklop
Copy link
Contributor

benklop commented Jan 18, 2026 via email

@nicholas-gh
Copy link
Author

nicholas-gh commented Jan 19, 2026

@benklop

server 8a5f2f1f9dbe60b30349da61af7d684ee21709dd
with models e5a9314dcf8a63eef5f860b2c4b63a152ebe35b1

Works in a couple of interactive tests/manual tests:

2026-01-19 09:08:01.264 DEBUG (MainThread) [music_assistant.metadata] Processing metadata lookup for library://radio/254
2026-01-19 09:08:01.267 DEBUG (MainThread) [music_assistant.player_queues] (pre)loading (next) item for queue Web (Browser on Desktop)...
2026-01-19 09:08:01.267 DEBUG (MainThread) [music_assistant.audio] Getting streamdetails for library://radio/254
2026-01-19 09:08:01.285 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Getting stream URL for classicalradio:bach
2026-01-19 09:08:01.531 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Filtered 3 valid stream URLs from playlist of 3 URLs
2026-01-19 09:08:01.531 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 1: StreamMirror(path='http://prem2.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.531 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 2: StreamMirror(path='http://prem4.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.531 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 3: StreamMirror(path='http://prem1.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.534 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Getting stream URL for classicalradio:bach
2026-01-19 09:08:01.534 DEBUG (MainThread) [aiosendspin.server.group] Sending server state to client <CLIENT_ID>
2026-01-19 09:08:01.535 DEBUG (MainThread) [aiosendspin.server.client.<CLIENT_ID>] Enqueueing message: ServerStateMessage
2026-01-19 09:08:01.554 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Filtered 3 valid stream URLs from playlist of 3 URLs
2026-01-19 09:08:01.554 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 1: StreamMirror(path='http://prem2.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.554 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 2: StreamMirror(path='http://prem4.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.554 DEBUG (MainThread) [music_assistant.digitally_incorporated] digitally_incorporated: Available stream URL 3: StreamMirror(path='http://prem1.classicalradio.com:80/bach?<STREAM_TOKEN>', priority=0)
2026-01-19 09:08:01.560 DEBUG (MainThread) [music_assistant.audio] retrieved streamdetails for library://radio/254 in 292 milliseconds
2026-01-19 09:08:01.663 DEBUG (MainThread) [music_assistant.players] Handling command play_media for player Web (Browser on Desktop) (by user <USER>)
2026-01-19 09:08:01.663 DEBUG (MainThread) [music_assistant.sendspin (Sendspin).<CLIENT_ID>] Received PLAY_MEDIA command on player Web (Browser on Desktop) with uri http://192.168.x.x:8097/single/<SESSION_ID>/<CLIENT_ID>/<MEDIA_TOKEN>.flac
2026-01-19 09:08:01.664 DEBUG (MainThread) [music_assistant.providers.sendspin.timed_client_stream] New subscriber joining: buffer is empty, starting at current_position=0.000s
2026-01-19 09:08:01.664 DEBUG (MainThread) [aiosendspin.server.group] Starting play_media with play_start_time_us=None
2026-01-19 09:08:01.664 INFO (MainThread) [aiosendspin.server.stream] Player <CLIENT_ID> assigned to main channel
2026-01-19 09:08:01.665 DEBUG (MainThread) [aiosendspin.server.group] Sending stream start message to client <CLIENT_ID>
2026-01-19 09:08:01.665 DEBUG (MainThread) [aiosendspin.server.client.<CLIENT_ID>] Enqueueing message: StreamStartMessage
2026-01-19 09:08:01.665 DEBUG (MainThread) [aiosendspin.server.client.<CLIENT_ID>] Enqueueing message: GroupUpdateServerMessage
2026-01-19 09:08:01.665 DEBUG (MainThread) [aiosendspin.server.client.<CLIENT_ID>] Enqueueing message: ServerStateMessage
2026-01-19 09:08:01.672 DEBUG (MainThread) [music_assistant.sendspin (Sendspin).<CLIENT_ID>] Received GroupEvent: GroupStateChangedEvent(state=<PlaybackStateType.PLAYING: 'playing'>)
2026-01-19 09:08:01.672 DEBUG (MainThread) [music_assistant.sendspin (Sendspin).<CLIENT_ID>] Group state changed to: PlaybackStateType.PLAYING
2026-01-19 09:08:01.681 DEBUG (MainThread) [music_assistant.streams] Starting queue item stream for Bach (digitally_incorporated://radio/classicalradio:bach) - using buffer: False - using fade-in: False - using volume normalization: fixed_gain
2026-01-19 09:08:01.681 DEBUG (MainThread) [music_assistant.audio.media_stream] media_stream path list len=3 first_type=StreamMirror
2026-01-19 09:08:01.681 DEBUG (MainThread) [music_assistant.audio] Using mirror stream list (count=3) for digitally_incorporated://radio/classicalradio:bach
2026-01-19 09:08:01.695 DEBUG (MainThread) [music_assistant.audio.media_stream] Started media stream for digitally_incorporated://radio/classicalradio:bach - using streamtype: http - pcm format: f32le - ffmpeg PID: <PID>
2026-01-19 09:08:01.700 DEBUG (MainThread) [music_assistant.audio] Mirror stream start for digitally_incorporated://radio/classicalradio:bach with 3 mirror(s)
2026-01-19 09:08:01.701 DEBUG (MainThread) [music_assistant.audio] Trying mirror http://prem2.classicalradio.com:80/bach?<STREAM_TOKEN> (priority=0) for digitally_incorporated://radio/classicalradio:bach
2026-01-19 09:08:01.701 DEBUG (MainThread) [music_assistant.audio] Start HTTP stream for digitally_incorporated://radio/classicalradio:bach (seek_position 0)
2026-01-19 09:08:01.892 WARNING (MainThread) [music_assistant.audio] Error streaming from mirror http://prem2.classicalradio.com:80/bach?<STREAM_TOKEN> (priority=0) for digitally_incorporated://radio/classicalradio:bach: 403, message='Too many clients connected'
Traceback (most recent call last):
  File "/Users/USERNAME/…/music_assistant/helpers/audio.py", line 1260, in get_mirror_stream
  File "/Users/USERNAME/…/music_assistant/helpers/audio.py", line 1029, in get_http_stream
  File "/Users/USERNAME/…/aiohttp/client_reqrep.py", line 636, in raise_for_status
aiohttp.client_exceptions.ClientResponseError: 403, message='Too many clients connected'
2026-01-19 09:08:01.894 DEBUG (MainThread) [music_assistant.audio] Trying mirror http://prem4.classicalradio.com:80/bach?<STREAM_TOKEN> (priority=0)
2026-01-19 09:08:01.894 DEBUG (MainThread) [music_assistant.audio] Start HTTP stream for digitally_incorporated://radio/classicalradio:bach (seek_position 0)
2026-01-19 09:08:02.432 DEBUG (MainThread) [music_assistant.audio.media_stream] First chunk received after 0.74 seconds (codec detected: mp3)
2026-01-19 09:08:02.432 DEBUG (MainThread) [music_assistant.streams] First audio chunk received for Bach (digitally_incorporated://radio/classicalradio:bach) after 0.75 seconds
2026-01-19 09:08:04.224 DEBUG (MainThread) [aiosendspin.server.group] Sending server state to client <CLIENT_ID>
2026-01-19 09:08:04.805 DEBUG (MainThread) [aiosendspin.server.group] Channel <CHANNEL_ID> prefill timeout after 3.0s, continuing with partial buffer
2026-01-19 09:08:04.808 INFO (MainThread) [aiosendspin.server.stream] Detected slow source, adjusting timing to prevent skipping.
2026-01-19 09:08:04.808 DEBUG (MainThread) [aiosendspin.server.stream] Adjusting timing: needs 1.487s headroom, have 1.013s buffer (adjusting 3.987s)

I suggest you make a new MR direct from your fork, and I close this MR (with a link to the new MR).

Many thanks for your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants