From: Marcel van der Veldt Date: Sun, 1 Aug 2021 20:34:37 +0000 (+0200) Subject: finishing touches X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=c0d3462780f18d8edd82f7ef5e8987425e1e6d6e;p=music-assistant-server.git finishing touches --- diff --git a/music_assistant/constants.py b/music_assistant/constants.py index 31a11b9d..165d9d1c 100755 --- a/music_assistant/constants.py +++ b/music_assistant/constants.py @@ -1,6 +1,6 @@ """All constants for Music Assistant.""" -__version__ = "0.1.10" +__version__ = "0.2.0" REQUIRED_PYTHON_VER = "3.8" # configuration keys/attributes diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index dab87cd5..80283eb9 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -129,8 +129,6 @@ async def get_stream_details( item_id=queue_item.item_id, path=queue_item.uri, content_type=ContentType(queue_item.uri.split(".")[-1]), - sample_rate=44100, - bit_depth=16, ) else: # always request the full db track as there might be other qualities available @@ -171,12 +169,15 @@ async def get_stream_details( # set player_id on the streamdetails so we know what players stream streamdetails.player_id = player_id # get gain correct / replaygain - if not queue_item.name == "alert": + if queue_item.name == "alert": + loudness = 0 + gain_correct = 0 + else: loudness, gain_correct = await get_gain_correct( mass, player_id, streamdetails.item_id, streamdetails.provider ) - streamdetails.gain_correct = gain_correct - streamdetails.loudness = loudness + streamdetails.gain_correct = gain_correct + streamdetails.loudness = loudness # set streamdetails as attribute on the media_item # this way the app knows what content is playing queue_item.streamdetails = streamdetails diff --git a/music_assistant/helpers/process.py b/music_assistant/helpers/process.py index db211cd4..236499e3 100644 --- a/music_assistant/helpers/process.py +++ b/music_assistant/helpers/process.py @@ -13,8 +13,8 @@ from async_timeout import timeout LOGGER = logging.getLogger("AsyncProcess") -DEFAULT_CHUNKSIZE = 256000 -DEFAULT_TIMEOUT = 10 +DEFAULT_CHUNKSIZE = 512000 +DEFAULT_TIMEOUT = 120 class AsyncProcess: @@ -36,7 +36,7 @@ class AsyncProcess: self._args, stdin=asyncio.subprocess.PIPE if self._enable_write else None, stdout=asyncio.subprocess.PIPE, - limit=8000000, + limit=4000000, close_fds=True, ) else: @@ -44,7 +44,7 @@ class AsyncProcess: *self._args, stdin=asyncio.subprocess.PIPE if self._enable_write else None, stdout=asyncio.subprocess.PIPE, - limit=8000000, + limit=4000000, close_fds=True, ) return self @@ -76,8 +76,6 @@ class AsyncProcess: async def read(self, chunk_size: int = DEFAULT_CHUNKSIZE) -> bytes: """Read x bytes from the process stdout.""" - if self._proc.stdout.at_eof() or self._proc.returncode is not None: - return b"" try: async with timeout(DEFAULT_TIMEOUT): if chunk_size is None: diff --git a/music_assistant/managers/players.py b/music_assistant/managers/players.py index c7d9daff..36f180d0 100755 --- a/music_assistant/managers/players.py +++ b/music_assistant/managers/players.py @@ -6,6 +6,7 @@ import pathlib from typing import Dict, List, Optional, Set, Tuple, Union from music_assistant.constants import ( + CONF_CROSSFADE_DURATION, CONF_POWER_CONTROL, CONF_VOLUME_CONTROL, EVENT_PLAYER_ADDED, @@ -23,7 +24,6 @@ from music_assistant.models.player import ( ) from music_assistant.models.player_queue import PlayerQueue, QueueItem, QueueOption from music_assistant.models.provider import PlayerProvider, ProviderType -from music_assistant.models.streamdetails import ContentType, StreamDetails, StreamType POLL_INTERVAL = 30 @@ -397,9 +397,10 @@ class PlayerManager: self, player_id: str, url: str, - gain_adjust: int = 0, + volume: int = 0, force: bool = True, announce: bool = False, + duration: int = 10, ): """ Play alert (e.g. tts message) on selected player. @@ -408,14 +409,17 @@ class PlayerManager: :param player_id: player_id of the player to handle the command. :param url: Url to the sound effect/tts message that should be played. - :param gain_adjust: Adjust volume/gain of audio. + :param volume: Volume relative to current player's volume. :param force: Play alert even if player is currently powered off. :param announce: Prepend alert sound. + :param duration: Amount of time after which queue is restored, default 10 seconds. """ player = self.get_player(player_id) player_queue = self.get_player_queue(player_id) prev_state = player.calculated_state.state prev_power = player.calculated_state.powered + prev_volume = player.calculated_state.volume_level + prev_repeat = player_queue.repeat_enabled if not player.calculated_state.powered: if not force: LOGGER.debug( @@ -424,7 +428,25 @@ class PlayerManager: ) return await self.cmd_power_on(player_id) - + # snapshot the queue + prev_queue_items = player_queue.items + prev_queue_index = player_queue.cur_index + prev_queue_crossfade = self.mass.config.get_player_config( + player_queue.queue_id + )[CONF_CROSSFADE_DURATION] + + # pause playback + if prev_state == PlayerState.PLAYING: + await self.cmd_pause(player_queue.queue_id) + # disable crossfade and repeat if needed + if prev_queue_crossfade: + self.mass.config.player_settings[player_id][CONF_CROSSFADE_DURATION] = 0 + if prev_repeat: + await player_queue.set_repeat_enabled(False) + # set alert volume + if volume != 0: + await self.cmd_volume_set(player_id, prev_volume + volume) + # load alert items in player queue queue_items = [] if announce: alert_announce = ( @@ -437,15 +459,8 @@ class PlayerManager: item_id="alert_announce", provider="url", name="alert", - duration=2, - streamdetails=StreamDetails( - type=StreamType.URL, - provider="url", - item_id="alert_announce", - path=str(alert_announce), - content_type=ContentType(url.split(".")[-1]), - gain_correct=10, - ), + duration=3, + uri=str(alert_announce), ) queue_item.stream_url = "%s/queue/%s/%s" % ( self.mass.web.stream_url, @@ -458,44 +473,37 @@ class PlayerManager: item_id="alert_sound", provider="url", name="alert", - duration=10, - streamdetails=StreamDetails( - type=StreamType.URL, - provider="url", - item_id="alert_sound", - path=url, - content_type=ContentType(url.split(".")[-1]), - gain_correct=gain_adjust, - ), + duration=duration, + uri=url, ) - queue_item.stream_url = "%s/queue/%s/%s?alert=true" % ( + queue_item.stream_url = "%s/queue/%s/%s" % ( self.mass.web.stream_url, player_id, queue_item.queue_item_id, ) queue_items.append(queue_item) - await player_queue.insert(queue_items, 0) - - if prev_power and prev_state in [PlayerState.PLAYING, PlayerState.PAUSED]: - return + # load queue items + await player_queue.load(queue_items) + + async def restore_queue(): + # restore queue + if volume: + await self.cmd_volume_set(player_id, prev_volume) + if prev_queue_crossfade: + self.mass.config.player_settings[player_id][ + CONF_CROSSFADE_DURATION + ] = prev_queue_crossfade + await player_queue.set_repeat_enabled(prev_repeat) + # pylint: disable=protected-access + player_queue._items = prev_queue_items + player_queue._cur_index = prev_queue_index + if prev_power: + await player_queue.resume() + else: + await self.cmd_power_off(player_id) - # wait until playback completed - playback_started = False - count = 0 - while True: - if not playback_started and player_queue.state == PlayerState.PLAYING: - playback_started = True - elif playback_started and ( - player_queue.state != PlayerState.PLAYING - or (player_queue.cur_item and player_queue.cur_item.name != "alert") - ): - break - if count == 20: - break - count += 0.2 - await asyncio.sleep(0.2) - await self.cmd_power_off(player_id) + self.mass.loop.call_later(duration, create_task, restore_queue) @api_route("players/{player_id}/cmd/stop", method="PUT") async def cmd_stop(self, player_id: str) -> None: @@ -613,11 +621,8 @@ class PlayerManager: player = self.get_player(player_id) if not player: return - # send stop if player is playing - if player.active_queue == player_id and player.state in [ - PlayerState.PLAYING, - PlayerState.PAUSED, - ]: + # send stop if player is active queue + if player.active_queue == player_id and player.state != PlayerState.OFF: await self.cmd_stop(player_id) player_config = self.mass.config.player_settings[player.player_id] # turn off player diff --git a/music_assistant/models/player_queue.py b/music_assistant/models/player_queue.py index 9afcdec3..2c019b8e 100755 --- a/music_assistant/models/player_queue.py +++ b/music_assistant/models/player_queue.py @@ -370,7 +370,7 @@ class PlayerQueue: """Load (overwrite) queue with new items.""" for index, item in enumerate(queue_items): item.sort_index = index - if self._shuffle_enabled: + if self._shuffle_enabled and len(queue_items) > 2: queue_items = self.__shuffle_items(queue_items) self._items = queue_items if self.use_queue_stream: diff --git a/music_assistant/models/streamdetails.py b/music_assistant/models/streamdetails.py index f7452cca..e0cf0405 100644 --- a/music_assistant/models/streamdetails.py +++ b/music_assistant/models/streamdetails.py @@ -44,8 +44,8 @@ class StreamDetails(DataClassDictMixin): seconds_played: int = 0 gain_correct: float = 0 loudness: Optional[float] = None - sample_rate: int = 44100 - bit_depth: int = 16 + sample_rate: Optional[int] = None + bit_depth: Optional[int] = None media_type: MediaType = MediaType.TRACK def to_dict( diff --git a/music_assistant/providers/chromecast/player.py b/music_assistant/providers/chromecast/player.py index 874e4e4e..78614d98 100644 --- a/music_assistant/providers/chromecast/player.py +++ b/music_assistant/providers/chromecast/player.py @@ -49,7 +49,7 @@ class ChromecastPlayer(Player): self._available = False self._status_listener: Optional[CastStatusListener] = None self._is_speaker_group = False - self._throttler = Throttler(rate_limit=1, period=0.2) + self._throttler = Throttler(rate_limit=1, period=0.1) @property def player_id(self) -> str: @@ -339,7 +339,7 @@ class ChromecastPlayer(Player): async def cmd_queue_load(self, queue_items: List[QueueItem]) -> None: """Load (overwrite) queue with new items.""" player_queue = self.mass.players.get_player_queue(self.player_id) - cc_queue_items = self.__create_queue_items(queue_items[:25]) + cc_queue_items = self.__create_queue_items(queue_items[:50]) repeat_enabled = player_queue.use_queue_stream or player_queue.repeat_enabled queuedata = { "type": "QUEUE_LOAD", @@ -347,16 +347,16 @@ class ChromecastPlayer(Player): "shuffle": False, # handled by our queue controller "queueType": "PLAYLIST", "startIndex": 0, # Item index to play after this request or keep same item if undefined - "items": cc_queue_items, # only load 25 tracks at once or the socket will crash + "items": cc_queue_items, # only load 50 tracks at once or the socket will crash } await self.chromecast_command(self.__send_player_queue, queuedata) if len(queue_items) > 50: - await self.cmd_queue_append(queue_items[26:]) + await self.cmd_queue_append(queue_items[51:]) async def cmd_queue_append(self, queue_items: List[QueueItem]) -> None: """Append new items at the end of the queue.""" cc_queue_items = self.__create_queue_items(queue_items) - async for chunk in yield_chunks(cc_queue_items, 25): + async for chunk in yield_chunks(cc_queue_items, 50): queuedata = { "type": "QUEUE_INSERT", "insertBefore": None, @@ -374,7 +374,7 @@ class ChromecastPlayer(Player): return { "opt_itemId": queue_item.queue_item_id, "autoplay": True, - "preloadTime": 10, + "preloadTime": 0, "playbackDuration": int(queue_item.duration), "startTime": 0, "activeTrackIds": [], @@ -389,9 +389,7 @@ class ChromecastPlayer(Player): "streamType": "LIVE" if player_queue.use_queue_stream else "BUFFERED", "metadata": { "title": queue_item.name, - "artist": next(iter(queue_item.artists)).name - if queue_item.artists - else "", + "artist": "/".join(x.name for x in queue_item.artists), }, "duration": int(queue_item.duration), }, diff --git a/music_assistant/web/stream.py b/music_assistant/web/stream.py index 963eeac5..a3d617d0 100644 --- a/music_assistant/web/stream.py +++ b/music_assistant/web/stream.py @@ -98,7 +98,7 @@ async def stream_queue(request: Request): "Queue stream finished for: %s", player_queue.player.name, ) - return resp + return resp @routes.get("/stream/queue/{player_id}/{queue_item_id}") @@ -111,7 +111,7 @@ async def stream_single_queue_item(request: Request): player_queue = mass.players.get_player_queue(player_id) if not player_queue: raise HTTPNotFound(reason="invalid player_id") - if player_queue.use_queue_stream and not request.query.get("alert"): + if player_queue.use_queue_stream: # redirect request if player switched to queue streaming return await stream_queue(request) LOGGER.debug("Stream request for %s", player_queue.player.name)