Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions music_assistant/providers/pandora/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,15 @@ async def _authenticate(self, username: str, password: str) -> None:
) from err

async def _api_request(
self, method: str, url: str, data: dict[str, Any] | None = None
self, method: str, url: str, data: dict[str, Any] | None = None, retry: bool = True
) -> dict[str, Any]:
"""Make an API request to Pandora."""
"""Make an API request to Pandora.

:param method: HTTP method (GET, POST, etc.)
:param url: API endpoint URL
:param data: Optional JSON data to send
:param retry: Whether to retry once on 401 authentication errors
"""
if not self._csrf_token or not self._auth_token:
raise LoginFailed("Not authenticated with Pandora")

Expand All @@ -160,7 +166,14 @@ async def _api_request(
) as response:
# Check status BEFORE parsing JSON
if response.status == 401:
raise LoginFailed("Pandora session expired")
if retry:
# Auth token expired, re-authenticate and retry once
username = str(self.config.get_value(CONF_USERNAME))
password = str(self.config.get_value(CONF_PASSWORD))
await self._authenticate(username, password)
return await self._api_request(method, url, data, retry=False)
raise LoginFailed("Pandora authentication failed after retry")

if response.status == 404:
raise MediaNotFoundError("Resource not found")
if response.status >= 500:
Expand Down Expand Up @@ -343,15 +356,15 @@ async def _handle_stream_request(self, request: web.Request) -> web.Response:
# Get or create session with LRU eviction
session = self._get_or_create_session(station_id)

# If we don't have this music track yet, fetch more fragments
while music_track_num >= len(session.track_map):
next_fragment_idx = len(session.fragments)
await self._get_fragment_data(session, next_fragment_idx)
try:
# If we don't have this music track yet, fetch more fragments
while music_track_num >= len(session.track_map):
next_fragment_idx = len(session.fragments)
await self._get_fragment_data(session, next_fragment_idx)

# Look up the actual fragment/track position
fragment_idx, track_idx = session.track_map[music_track_num]
# Look up the actual fragment/track position
fragment_idx, track_idx = session.track_map[music_track_num]

try:
# Ensure fragment is loaded
if fragment_idx >= len(session.fragments) or not session.fragments[fragment_idx]:
await self._get_fragment_data(session, fragment_idx)
Expand Down Expand Up @@ -383,6 +396,9 @@ async def _handle_stream_request(self, request: web.Request) -> web.Response:
except (MediaNotFoundError, InvalidDataError) as err:
self.logger.error("Stream error: %s", err)
return web.Response(status=404, text="Stream unavailable")
except ProviderUnavailableError as err:
self.logger.error("Pandora service unavailable: %s", err)
return web.Response(status=503, text="Service temporarily unavailable")

def _get_or_create_session(self, station_id: str) -> PandoraStationSession:
"""Get or create a session, with LRU eviction if needed."""
Expand Down