title="Music Assistant" if flow_mode else queue_item.name,
image_url=MASS_LOGO_ONLINE,
duration=duration,
- queue_id=queue_item.queue_id,
+ source_id=queue_item.queue_id,
queue_item_id=queue_item.queue_item_id,
)
if not flow_mode and queue_item.media_item:
if not player.current_media:
return None
# prefer queue_id and queue_item_id within the current media
- if player.current_media.queue_id == queue_id and player.current_media.queue_item_id:
+ if player.current_media.source_id == queue_id and player.current_media.queue_item_id:
return player.current_media.queue_item_id
# special case for sonos players
if (
return (
'<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/">'
- f'<item id="{media.queue_item_id or xmlescape(media.uri)}" restricted="true" parentID="{media.queue_id or ""}">'
+ f'<item id="{media.queue_item_id or xmlescape(media.uri)}" restricted="true" parentID="{media.source_id or ""}">'
f"<dc:title>{escape_metadata(media.title or media.uri)}</dc:title>"
f"<dc:creator>{escape_metadata(media.artist or '')}</dc:creator>"
f"<upnp:album>{escape_metadata(media.album or '')}</upnp:album>"
album: str | None = None,
image_url: str | None = None,
duration: int | None = None,
- queue_id: str | None = None,
+ source_id: str | None = None,
queue_item_id: str | None = None,
custom_data: dict[str, Any] | None = None,
clear_all: bool = False,
self._attr_current_media.image_url = image_url
if duration:
self._attr_current_media.duration = duration
- if queue_id:
- self._attr_current_media.queue_id = queue_id
+ if source_id:
+ self._attr_current_media.source_id = source_id
if queue_item_id:
self._attr_current_media.queue_item_id = queue_item_id
if custom_data:
if sync_leader := self.sync_leader:
await sync_leader.play_media(media)
self._attr_current_media = media
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self.update_state()
else:
raise RuntimeError("an empty group cannot play media, consider adding members first")
# set the active source for the player to the media queue
# this accounts for syncgroups and linked players (e.g. sonos)
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self._attr_current_media = media
# select audio source
# because this could have been a group
player_id=media.custom_data["player_id"],
)
- elif media.queue_id and media.queue_id.startswith(UGP_PREFIX):
+ elif media.source_id and media.source_id.startswith(UGP_PREFIX):
# special case: UGP stream
- ugp_player = cast("UniversalGroupPlayer", self.mass.players.get(media.queue_id))
+ ugp_player = cast("UniversalGroupPlayer", self.mass.players.get(media.source_id))
ugp_stream = ugp_player.stream
assert ugp_stream is not None # for type checker
input_format = ugp_stream.base_pcm_format
audio_source = ugp_stream.subscribe_raw()
- elif media.queue_id and media.queue_item_id:
+ elif media.source_id and media.queue_item_id:
# regular queue (flow) stream request
input_format = AIRPLAY_FLOW_PCM_FORMAT
- queue = self.mass.player_queues.get(media.queue_id)
+ queue = self.mass.player_queues.get(media.source_id)
assert queue
- start_queue_item = self.mass.player_queues.get_item(media.queue_id, media.queue_item_id)
+ start_queue_item = self.mass.player_queues.get_item(
+ media.source_id, media.queue_item_id
+ )
assert start_queue_item
audio_source = self.mass.streams.get_queue_flow_stream(
queue=queue,
# Optimistically update state
self._attr_current_media = media
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self._attr_elapsed_time = 0
self._attr_elapsed_time_last_updated = time.time()
self.update_state()
url = f"builtin_player/flow/{self.player_id}.mp3"
self._attr_current_media = media
self._attr_playback_state = PlaybackState.PLAYING
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self.update_state()
self.mass.signal_event(
EventType.BUILTIN_PLAYER,
if queue is None or media is None:
raise web.HTTPNotFound(reason="No active queue or media found!")
- if media.queue_id is None:
+ if media.source_id is None:
raise web.HTTPError # TODO: better error
- queue_item = self.mass.player_queues.get_item(media.queue_id, media.queue_item_id)
+ queue_item = self.mass.player_queues.get_item(media.source_id, media.queue_item_id)
if queue_item is None:
raise web.HTTPError # TODO: better error
# Optimistically update state
self._attr_current_media = media
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self._attr_elapsed_time = 0
self._attr_elapsed_time_last_updated = time.time()
self._attr_playback_state = PlaybackState.PLAYING
# get stream or create new one
stream_name = self._get_stream_name(SnapCastStreamType.MUSIC)
- stream = await self._get_or_create_stream(stream_name, media.queue_id or self.player_id)
+ stream = await self._get_or_create_stream(stream_name, media.source_id or self.player_id)
# if no announcement is playing we activate the stream now, otherwise it
# will be activated by play_announcement when the announcement is over.
await snap_group.set_stream(stream.identifier)
self._attr_current_media = media
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
# select audio source
if media.media_type == MediaType.PLUGIN_SOURCE:
output_format=DEFAULT_SNAPCAST_FORMAT,
player_id=self.player_id,
)
- elif media.queue_id and media.queue_id.startswith(UGP_PREFIX):
+ elif media.source_id and media.source_id.startswith(UGP_PREFIX):
# special case: UGP stream
- ugp_player = cast("UniversalGroupPlayer", self.mass.players.get(media.queue_id))
+ ugp_player = cast("UniversalGroupPlayer", self.mass.players.get(media.source_id))
ugp_stream = ugp_player.stream
assert ugp_stream is not None # for type checker
input_format = ugp_stream.base_pcm_format
audio_source = ugp_stream.subscribe_raw()
- elif media.queue_id and media.queue_item_id:
+ elif media.source_id and media.queue_item_id:
# regular queue (flow) stream request
input_format = DEFAULT_SNAPCAST_PCM_FORMAT
- queue = self.mass.player_queues.get(media.queue_id)
- start_queue_item = self.mass.player_queues.get_item(media.queue_id, media.queue_item_id)
+ queue = self.mass.player_queues.get(media.source_id)
+ start_queue_item = self.mass.player_queues.get_item(
+ media.source_id, media.queue_item_id
+ )
assert queue is not None # for type checking
assert start_queue_item is not None # for type checking
audio_source = self.mass.streams.get_queue_flow_stream(
if media.media_type in (
MediaType.PLUGIN_SOURCE,
MediaType.FLOW_STREAM,
- ) or media.queue_id.startswith(UGP_PREFIX):
+ ) or media.source_id.startswith(UGP_PREFIX):
# flow stream or plugin source playback
# always use the legacy (UPNP) playback method for this
await self._play_media_legacy(media)
_update_state()
return
- if media.queue_id:
+ if media.source_id and media.queue_item_id:
# Regular Queue item playback
# create a sonos cloud queue and load it
cloud_queue_url = f"{self.mass.streams.base_url}/sonos_queue/v2.3/"
- mass_queue = self.mass.player_queues.get(media.queue_id)
+ mass_queue = self.mass.player_queues.get(media.source_id)
await self.client.player.group.play_cloud_queue(
cloud_queue_url,
- http_authorization=media.queue_id,
+ http_authorization=media.source_id,
item_id=media.queue_item_id,
queue_version=str(int(mass_queue.items_last_updated)),
)
- self.mass.call_later(5, self.sync_play_modes, media.queue_id)
+ self.mass.call_later(5, self.sync_play_modes, media.source_id)
_update_state()
return
image_url=track_image_url,
)
if active_service == MusicService.MUSIC_ASSISTANT:
- current_media.queue_id = self._attr_active_source
+ current_media.source_id = self._attr_active_source
current_media.queue_item_id = current_item["id"]
# radio stream info
if container and container.get("name") and active_group.playback_metadata.get("streamInfo"):
player_id = self.player_id
if (
airplay_player.playback_state == PlaybackState.PLAYING
- and airplay_player.active_source == media.queue_id
+ and airplay_player.active_source == media.source_id
):
# if the airplay player is already playing,
# the stream will be reused so no need to do the whole grouping thing below
# because this could have been a group
player_id=media.custom_data["player_id"],
)
- elif media.queue_id.startswith("ugp_"):
+ elif media.source_id.startswith("ugp_"):
# special case: UGP stream
- ugp_player: UniversalGroupPlayer = self.mass.players.get(media.queue_id)
+ ugp_player: UniversalGroupPlayer = self.mass.players.get(media.source_id)
ugp_stream = ugp_player.stream
# Filter is later applied in MultiClientStream
audio_source = ugp_stream.get_stream(master_audio_format, filter_params=None)
- elif media.queue_id and media.queue_item_id:
+ elif media.source_id and media.queue_item_id:
# regular queue stream request
audio_source = self.mass.streams.get_queue_flow_stream(
- queue=self.mass.player_queues.get(media.queue_id),
+ queue=self.mass.player_queues.get(media.source_id),
start_queue_item=self.mass.player_queues.get_item(
- media.queue_id, media.queue_item_id
+ media.source_id, media.queue_item_id
),
pcm_format=master_audio_format,
)
"artist": media.artist,
"image_url": media.image_url,
"duration": media.duration,
- "queue_id": media.queue_id,
+ "queue_id": media.source_id,
"queue_item_id": media.queue_item_id,
}
- if queue := self.mass.player_queues.get(media.queue_id):
+ if queue := self.mass.player_queues.get(media.source_id):
self.extra_data["playlist repeat"] = REPEATMODE_MAP[queue.repeat_mode]
self.extra_data["playlist shuffle"] = int(queue.shuffle_enabled)
await slimplayer.play_url(
output_format=UGP_FORMAT,
player_id=media.custom_data["player_id"],
)
- elif media.queue_id and media.queue_item_id:
+ elif media.source_id and media.queue_item_id:
# regular queue stream request
- queue = self.mass.player_queues.get(media.queue_id)
- queue_item = self.mass.player_queues.get_item(media.queue_id, media.queue_item_id)
+ queue = self.mass.player_queues.get(media.source_id)
+ queue_item = self.mass.player_queues.get_item(media.source_id, media.queue_item_id)
if not queue or not queue_item:
# this should not happen, but guard just in case
- raise RuntimeError(f"Invalid queue(item): {media.queue_id}, {media.queue_item_id}")
+ raise RuntimeError(f"Invalid queue(item): {media.source_id}, {media.queue_item_id}")
audio_source = self.mass.streams.get_queue_flow_stream(
queue=queue,
start_queue_item=queue_item,
self._attr_elapsed_time = 0
self._attr_elapsed_time_last_updated = time() - 1
self._attr_playback_state = PlaybackState.PLAYING
- self._attr_active_source = media.queue_id
+ self._attr_active_source = media.source_id
self.update_state()
# forward to downstream play_media commands
uri=f"{base_url}?player_id={member.player_id}",
media_type=MediaType.FLOW_STREAM,
title=self.display_name,
- queue_id=self.player_id,
+ source_id=self.player_id,
)
)
)
uri=f"{base_url}?player_id={player_id}",
media_type=MediaType.FLOW_STREAM,
title=self.display_name,
- queue_id=child_player.player_id,
+ source_id=child_player.player_id,
),
)
# handle removals
"ifaddr==0.2.0",
"mashumaro==3.16",
"music-assistant-frontend==2.15.4",
- "music-assistant-models==1.1.53",
+ "music-assistant-models==1.1.55",
"mutagen==1.47.0",
"orjson==3.11.3",
"pillow==11.3.0",
lyricsgenius==3.6.5
mashumaro==3.16
music-assistant-frontend==2.15.4
-music-assistant-models==1.1.53
+music-assistant-models==1.1.55
mutagen==1.47.0
orjson==3.11.3
pillow==11.3.0
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/70b7288088b42d318f75dbcc41fd0091/Images/Primary?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/32ed6a0091733dcff57eae67010f3d4b/Images/Primary?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
]),
'label': None,
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/dd954bbf54398e247d803186d3585b79/Images/Backdrop/0?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/da9c458e425584680765ddc3a89cbc0c/Images/Primary?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
]),
'label': None,
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/54918f75ee8f6c8b8dc5efd680644f29/Images/Primary?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'http://localhost:1234/Items/fb12a77f49616a9fc56a6fab2b94174c/Images/Primary?api_key=ACCESS_TOKEN',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': list([
dict({
'path': 'al-ad0f112b6dcf83de5e9cae85d07f0d35_640a93a8',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': list([
dict({
'path': 'al-ad0f112b6dcf83de5e9cae85d07f0d35_640a93a8',
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': list([
dict({
'path': 'al-ad0f112b6dcf83de5e9cae85d07f0d35_640a93a8',
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': list([
dict({
'path': 'al-ad0f112b6dcf83de5e9cae85d07f0d35_640a93a8',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'https://demo.org/image.jpg',
'description': 'Empty biography',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'https://demo.org/image.jpg',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'https://demo.org/image.jpg',
'description': 'Empty biography',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'https://demo.org/image.jpg',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'ar-100000002',
'description': 'Empty biography',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'ar-100000002',
'description': 'The history of The History of Rome...Why the Western Empire Fell when it did...Some thoughts on the future...Thank you, goodnight.',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'A weekly podcast tracing the rise, decline and fall of the Roman Empire. Now complete!',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'The history of The History of Rome...Why the Western Empire Fell when it did...Some thoughts on the future...Thank you, goodnight.',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'A weekly podcast tracing the rise, decline and fall of the Roman Empire. Now complete!',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'The history of The History of Rome...Why the Western Empire Fell when it did...Some thoughts on the future...Thank you, goodnight.',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'A weekly podcast tracing the rise, decline and fall of the Roman Empire. Now complete!',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'al-2250',
'description': 'A weekly podcast tracing the rise, decline and fall of the Roman Empire. Now complete!',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': 'A weekly podcast tracing the rise, decline and fall of the Roman Empire. Now complete!',
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': list([
dict({
'path': 'pd-5',
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'description': None,
'explicit': None,
'genres': None,
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,
'East coast',
'Hip-Hop',
]),
+ 'grouping': None,
'images': None,
'label': None,
'languages': None,