Fix playlist playback: use MA enqueue and search resolution

The play_playlist_on_speaker function was sending text search queries
to raw Cast entities which can't resolve them. Now uses enqueue: "replace"
for the first track and "add" for subsequent tracks. Added 1s delay between
requests so MA can process each Apple Music search. Increased HTTP timeout
to 30s for search latency.

The caller must pass a Music Assistant entity (_2 suffix) for text-based
search to work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 11:48:33 -06:00
parent 2c6ba345b1
commit 448be52f4a

View File

@@ -1,3 +1,4 @@
import asyncio
import logging import logging
import httpx import httpx
@@ -13,7 +14,7 @@ async def _ha_request(method: str, path: str, **kwargs) -> dict:
"Authorization": f"Bearer {settings.ha_token}", "Authorization": f"Bearer {settings.ha_token}",
"Content-Type": "application/json", "Content-Type": "application/json",
} }
async with httpx.AsyncClient(timeout=10) as client: async with httpx.AsyncClient(timeout=30) as client:
resp = await client.request( resp = await client.request(
method, f"{settings.ha_url}{path}", headers=headers, **kwargs method, f"{settings.ha_url}{path}", headers=headers, **kwargs
) )
@@ -81,37 +82,31 @@ async def play_playlist_on_speaker(
tracks: list[dict], tracks: list[dict],
speaker_entity: str, speaker_entity: str,
) -> None: ) -> None:
"""Play a list of tracks on a speaker. Each track dict has 'artist' and 'title'. """Play a list of tracks on a speaker via Music Assistant.
Enqueues tracks via Music Assistant. Each track dict has 'artist' and 'title'. The speaker_entity MUST be a
Music Assistant entity (the _2 suffix ones, e.g. media_player.living_room_speaker_2)
so that text search queries are resolved via Apple Music.
""" """
if not tracks: if not tracks:
return return
for i, track in enumerate(tracks): for i, track in enumerate(tracks):
search_query = f"{track['artist']} {track['title']}"
try: try:
if i == 0: await _ha_request(
# Play first track "POST",
await _ha_request( "/api/services/media_player/play_media",
"POST", json={
"/api/services/media_player/play_media", "entity_id": speaker_entity,
json={ "media_content_id": search_query,
"entity_id": speaker_entity, "media_content_type": "music",
"media_content_id": f"{track['artist']} - {track['title']}", "enqueue": "replace" if i == 0 else "add",
"media_content_type": "music", },
}, )
) logger.info("Enqueued [%d/%d]: %s", i + 1, len(tracks), search_query)
else: # Brief pause between requests so MA can process each search
# Enqueue subsequent tracks if i < len(tracks) - 1:
await _ha_request( await asyncio.sleep(1)
"POST",
"/api/services/media_player/play_media",
json={
"entity_id": speaker_entity,
"media_content_id": f"{track['artist']} - {track['title']}",
"media_content_type": "music",
"enqueue": "add",
},
)
except Exception: except Exception:
logger.exception("Failed to enqueue %s - %s", track["artist"], track["title"]) logger.exception("Failed to enqueue %s - %s", track["artist"], track["title"])