From dd98b50cd41a2895737231c1583dad0f647deed9 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 16 Jan 2026 18:01:20 +0000 Subject: [PATCH] When the DI webserver replies 403, try the next URL in the list before 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. --- .../digitally_incorporated/__init__.py | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/music_assistant/providers/digitally_incorporated/__init__.py b/music_assistant/providers/digitally_incorporated/__init__.py index 21166db21e..ecdf25dcdd 100644 --- a/music_assistant/providers/digitally_incorporated/__init__.py +++ b/music_assistant/providers/digitally_incorporated/__init__.py @@ -634,16 +634,49 @@ async def _get_stream_url(self, network_key: str, channel_key: str) -> str: for i, url in enumerate(playlist): self.logger.debug("%s: Available stream URL %d: %s", self.domain, i + 1, url) - # Use the first URL - Digitally Incorporated typically returns them in priority order - stream_url: str = str(playlist[0]) - self.logger.debug("%s: Selected stream URL: %s", self.domain, stream_url) - - # Validate the stream URL - if not stream_url or not isinstance(stream_url, str): - msg = f"{self.domain}: Invalid stream URL received: {stream_url}" + # 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) - return stream_url + total_candidates = len(candidate_urls) + for idx, stream_url in enumerate(candidate_urls, start=1): + try: + timeout = aiohttp.ClientTimeout(total=10) + async with self.mass.http_session.head( + stream_url, allow_redirects=True, timeout=timeout + ) as resp: + if resp.status == 403: + self.logger.warning( + "%s: Stream URL %s returned 403 (candidate %d/%d), trying next", + self.domain, + stream_url, + idx, + total_candidates, + ) + continue + resp.raise_for_status() + self.logger.debug( + "%s: Selected stream URL %d/%d: %s", + self.domain, + idx, + total_candidates, + stream_url, + ) + return stream_url + + except aiohttp.ClientError as err: + self.logger.debug( + "%s: Stream URL %s check failed (%s), trying next", + self.domain, + stream_url, + err, + ) + continue + + msg = f"{self.domain}: Unable to get working stream URL after {total_candidates} attempts" + raise MediaNotFoundError(msg) except (ProviderUnavailableError, MediaNotFoundError): # Re-raise provider/media errors as-is (they already have domain prefix)