Skip to content

Commit

Permalink
Bugfixes and improvements to (universal) player groups (music-assista…
Browse files Browse the repository at this point in the history
  • Loading branch information
marcelveldt authored Apr 4, 2024
1 parent 20a8520 commit a47ac7e
Show file tree
Hide file tree
Showing 23 changed files with 946 additions and 1,082 deletions.
2 changes: 1 addition & 1 deletion music_assistant/common/helpers/uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def parse_uri(uri: str) -> tuple[MediaType, str, str]:
media_type_str = uri.split("/")[3]
media_type = MediaType(media_type_str)
item_id = uri.split("/")[4].split("?")[0]
elif uri.startswith(("http://", "https://")):
elif uri.startswith(("http://", "https://", "rtsp://", "rtmp://")):
# Translate a plain URL to the URL provider
provider_instance_id_or_domain = "url"
media_type = MediaType.UNKNOWN
Expand Down
2 changes: 1 addition & 1 deletion music_assistant/common/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class MediaType(StrEnum):
RADIO = "radio"
FOLDER = "folder"
ANNOUNCEMENT = "announcement"
FLOW_STREAM = "flow_stream"
UNKNOWN = "unknown"

@classmethod
Expand All @@ -35,7 +36,6 @@ def ALL(self) -> tuple[MediaType, ...]: # noqa: N802
MediaType.TRACK,
MediaType.PLAYLIST,
MediaType.RADIO,
MediaType.ANNOUNCEMENT,
)


Expand Down
24 changes: 23 additions & 1 deletion music_assistant/common/models/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from mashumaro import DataClassDictMixin

from .enums import PlayerFeature, PlayerState, PlayerType
from .enums import MediaType, PlayerFeature, PlayerState, PlayerType


@dataclass(frozen=True)
Expand All @@ -20,6 +20,22 @@ class DeviceInfo(DataClassDictMixin):
manufacturer: str = "Unknown Manufacturer"


@dataclass
class PlayerMedia(DataClassDictMixin):
"""Metadata of Media loading/loaded into a player."""

uri: str # uri or other identifier of the loaded media
media_type: MediaType = MediaType.UNKNOWN
title: str | None = None # optional
artist: str | None = None # optional
album: str | None = None # optional
image_url: str | None = None # optional
duration: int | None = None # optional
queue_id: str | None = None # only present for requests from queue controller
queue_item_id: str | None = None # only present for requests from queue controller
custom_data: dict | None = None # optional


@dataclass
class Player(DataClassDictMixin):
"""Representation of a Player within Music Assistant."""
Expand Down Expand Up @@ -58,8 +74,14 @@ class Player(DataClassDictMixin):

# current_item_id: return item_id/uri of the current active/loaded item on the player
# this may be a MA queue_item_id, url, uri or some provider specific string
# deprecated: use current_media instead
current_item_id: str | None = None

# current_media: return current active/loaded item on the player
# this may be a MA queue item, url, uri or some provider specific string
# includes metadata if supported by the provider/player
current_media: PlayerMedia | None = None

# can_sync_with: return tuple of player_ids that can be synced to/with this player
# usually this is just a list of all player_ids within the playerprovider
can_sync_with: tuple[str, ...] = field(default=())
Expand Down
1 change: 0 additions & 1 deletion music_assistant/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,3 @@
)
SYNCGROUP_PREFIX: Final[str] = "syncgroup_"
VERBOSE_LOG_LEVEL: Final[int] = 5
UGP_PREFIX: Final[str] = "ugp_"
42 changes: 36 additions & 6 deletions music_assistant/server/controllers/player_queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
QueueEmpty,
)
from music_assistant.common.models.media_items import MediaItemType, media_from_dict
from music_assistant.common.models.player import PlayerMedia
from music_assistant.common.models.player_queue import PlayerQueue
from music_assistant.common.models.queue_item import QueueItem
from music_assistant.constants import FALLBACK_DURATION
from music_assistant.constants import CONF_FLOW_MODE, FALLBACK_DURATION, MASS_LOGO_ONLINE
from music_assistant.server.helpers.api import api_command
from music_assistant.server.helpers.audio import get_stream_details
from music_assistant.server.models.core_controller import CoreController
Expand Down Expand Up @@ -199,8 +200,11 @@ def get_active_queue(self, player_id: str) -> PlayerQueue:
"""Return the current active/synced queue for a player."""
if player := self.mass.players.get(player_id):
# account for player that is synced (sync child)
if player.synced_to:
if player.synced_to and player.synced_to != player.player_id:
return self.get_active_queue(player.synced_to)
# handle active group player
if player.active_group and player.active_group != player.player_id:
return self.get_active_queue(player.active_group)
# active_source may be filled with other queue id
if player.active_source != player_id and (
queue := self.get_active_queue(player.active_source)
Expand Down Expand Up @@ -671,14 +675,18 @@ async def play_index(
queue.current_index = index
queue.index_in_buffer = index
queue.flow_mode_start_index = index
queue.flow_mode = False # reset
queue.flow_mode = self.mass.config.get_raw_player_config_value(
queue_id, CONF_FLOW_MODE, False
)
# get streamdetails - do this here to catch unavailable items early
queue_item.streamdetails = await get_stream_details(
self.mass, queue_item, seek_position=seek_position, fade_in=fade_in
)
# send play_media request to player
await self.mass.players.play_media(
player_id=queue_id,
queue_item=queue_item,
# transform into PlayerMedia to send to the actual player implementation
media=self.player_media_from_queue_item(queue_item, queue.flow_mode),
)

# Interaction with player
Expand Down Expand Up @@ -953,6 +961,27 @@ def index_by_id(self, queue_id: str, queue_item_id: str) -> int | None:
return index
return None

def player_media_from_queue_item(self, queue_item: QueueItem, flow_mode: bool) -> PlayerMedia:
"""Parse PlayerMedia from QueueItem."""
media = PlayerMedia(
uri=self.mass.streams.resolve_stream_url(queue_item, flow_mode=flow_mode),
media_type=MediaType.FLOW_STREAM if flow_mode else queue_item.media_type,
title="Music Assistant" if flow_mode else queue_item.name,
image_url=MASS_LOGO_ONLINE,
duration=queue_item.duration,
queue_id=queue_item.queue_id,
queue_item_id=queue_item.queue_item_id,
)
if not flow_mode and queue_item.media_item:
media.title = queue_item.media_item.name
media.artist = getattr(queue_item.media_item, "artist_str", "")
media.album = (
album.name if (album := getattr(queue_item.media_item, "album", None)) else ""
)
if queue_item.image:
media.image_url = self.mass.metadata.get_image_url(queue_item.image)
return media

def _get_next_index(
self, queue_id: str, cur_index: int | None, is_skip: bool = False
) -> int | None:
Expand Down Expand Up @@ -1028,8 +1057,9 @@ async def _enqueue_next(index: int, supports_enqueue: bool = False) -> None:
with suppress(QueueEmpty):
next_item = await self.preload_next_item(queue.queue_id, index)
if supports_enqueue:
await self.mass.players.enqueue_next_queue_item(
player_id=player.player_id, queue_item=next_item
await self.mass.players.enqueue_next_media(
player_id=player.player_id,
media=self.player_media_from_queue_item(next_item, queue.flow_mode),
)
return
await self.play_index(queue.queue_id, next_item.queue_item_id)
Expand Down
Loading

0 comments on commit a47ac7e

Please sign in to comment.