From: Marcel van der Veldt Date: Wed, 15 Jun 2022 12:17:51 +0000 (+0200) Subject: Small improvements to the announce/alert feature for TTS (#366) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=71addc9479118c97ebd4333aa7484debe7ce8e01;p=music-assistant-server.git Small improvements to the announce/alert feature for TTS (#366) * small improvements to alert stream * remove redundant code --- diff --git a/music_assistant/controllers/streams.py b/music_assistant/controllers/streams.py index 9e0e1544..72c21bbc 100644 --- a/music_assistant/controllers/streams.py +++ b/music_assistant/controllers/streams.py @@ -169,6 +169,7 @@ class StreamsController: seek_position: int, fade_in: bool, output_format: ContentType, + is_alert: bool, ) -> QueueStream: """Start running a queue stream.""" # cleanup stale previous queue tasks @@ -184,7 +185,12 @@ class StreamsController: streamdetails = await get_stream_details(self.mass, first_item, queue.queue_id) # work out pcm details - if queue.settings.crossfade_mode == CrossFadeMode.ALWAYS: + if is_alert: + pcm_sample_rate = 41000 + pcm_bit_depth = 16 + pcm_channels = 2 + pcm_resample = True + elif queue.settings.crossfade_mode == CrossFadeMode.ALWAYS: pcm_sample_rate = min(96000, queue.max_sample_rate) pcm_bit_depth = 24 pcm_channels = 2 @@ -212,6 +218,7 @@ class StreamsController: pcm_bit_depth=pcm_bit_depth, pcm_channels=pcm_channels, pcm_resample=pcm_resample, + is_alert=is_alert, autostart=True, ) self.mass.create_task(self.cleanup_stale) @@ -244,6 +251,7 @@ class QueueStream: pcm_channels: int = 2, pcm_floating_point: bool = False, pcm_resample: bool = False, + is_alert: bool = False, autostart: bool = False, ): """Init QueueStreamJob instance.""" @@ -259,6 +267,7 @@ class QueueStream: self.pcm_channels = pcm_channels self.pcm_floating_point = pcm_floating_point self.pcm_resample = pcm_resample + self.is_alert = is_alert self.url = queue.mass.streams.get_stream_url(stream_id, output_format) self.mass = queue.mass @@ -356,11 +365,11 @@ class QueueStream: self.seconds_streamed += len(audio_chunk) / sample_size del audio_chunk # allow clients to only buffer max ~30 seconds ahead - seconds_allowed = int(time() - self.streaming_started) + 30 + seconds_allowed = int(time() - self.streaming_started) diff = self.seconds_streamed - seconds_allowed - if diff > 1: + if diff > 30: self.logger.debug( - "Player is buffering %s seconds ahead, slowing it down", + "Player is buffering %s seconds ahead, slowing it down a bit", diff, ) await asyncio.sleep(10) @@ -467,9 +476,6 @@ class QueueStream: ) continue - if queue_track.name == "alert": - self.pcm_resample = True - # check the PCM samplerate/bitrate if not self.pcm_resample and streamdetails.bit_depth > self.pcm_bit_depth: self.signal_next = True @@ -525,7 +531,7 @@ class QueueStream: queue_track.streamdetails.seconds_skipped = seek_position fade_in_part = b"" cur_chunk = 0 - prev_chunk = None + prev_chunk = b"" bytes_written = 0 # handle incoming audio chunks async for is_last_chunk, chunk in get_media_stream( @@ -543,11 +549,6 @@ class QueueStream: if len(chunk) == 0 and bytes_written == 0 and is_last_chunk: # stream error: got empy first chunk ?! self.logger.warning("Stream error on %s", queue_track.uri) - elif cur_chunk == 1 and is_last_chunk: - # audio only has one single chunk (alert?) - bytes_written += len(chunk) - yield chunk - del chunk elif cur_chunk == 1 and last_fadeout_data: prev_chunk = chunk del chunk @@ -597,7 +598,7 @@ class QueueStream: bytes_written += len(remaining_bytes) del remaining_bytes del chunk - prev_chunk = None # needed to prevent this chunk being sent again + prev_chunk = b"" # needed to prevent this chunk being sent again # HANDLE LAST PART OF TRACK elif prev_chunk and is_last_chunk: # last chunk received so create the last_part @@ -631,6 +632,10 @@ class QueueStream: del last_part del remaining_bytes del chunk + elif is_last_chunk: + # there is only one chunk (e.g. alert sound) + yield chunk + del chunk # MIDDLE PARTS OF TRACK else: # middle part of the track diff --git a/music_assistant/models/player_queue.py b/music_assistant/models/player_queue.py index 65c9575c..a06304a7 100644 --- a/music_assistant/models/player_queue.py +++ b/music_assistant/models/player_queue.py @@ -9,7 +9,13 @@ from asyncio import Task, TimerHandle from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union -from music_assistant.models.enums import EventType, MediaType, QueueOption, RepeatMode +from music_assistant.models.enums import ( + ContentType, + EventType, + MediaType, + QueueOption, + RepeatMode, +) from music_assistant.models.errors import MediaNotFoundError, MusicAssistantError from music_assistant.models.event import MassEvent @@ -40,6 +46,8 @@ class QueueSnapShot: items: List[QueueItem] index: Optional[int] position: int + repeat: RepeatMode + shuffle: bool class PlayerQueue: @@ -271,9 +279,7 @@ class PlayerQueue: announce: Prepend the (TTS) alert with a small announce sound. gain_correct: Adjust the gain of the alert sound (in dB). """ - if self._snapshot: - self.logger.debug("Ignore play_alert: already in progress") - # return + assert not self._snapshot, "Alert already in progress" # create snapshot await self.snapshot_create() @@ -300,11 +306,13 @@ class PlayerQueue: else: raise MediaNotFoundError(f"Invalid uri: {uri}") from err - # load queue with alert sound(s) - await self.load(queue_items) + # start queue with alert sound(s) + self._items = queue_items + self._settings.repeat_mode = RepeatMode.OFF + self._settings.shuffle_enabled = False + await self.queue_stream_start(0, 0, False, is_alert=True) - # wait for the alert to finish playing - await self.stream.done.wait() + # wait for the player to finish playing alert_done = asyncio.Event() def handle_event(evt: MassEvent): @@ -315,7 +323,10 @@ class PlayerQueue: handle_event, EventType.QUEUE_UPDATED, self.queue_id ) try: + await asyncio.wait_for(self.stream.done.wait(), 30) await asyncio.wait_for(alert_done.wait(), 30) + except asyncio.TimeoutError: + self.logger.warning("Timeout while playing alert") finally: unsub() # restore queue @@ -397,12 +408,15 @@ class PlayerQueue: async def snapshot_create(self) -> None: """Create snapshot of current Queue state.""" + self.logger.debug("Creating snapshot...") self._snapshot = QueueSnapShot( powered=self.player.powered, state=self.player.state, items=self._items, index=self._current_index, position=self._current_item_elapsed_time, + repeat=self._settings.repeat_mode, + shuffle=self._settings.shuffle_enabled, ) async def snapshot_restore(self) -> None: @@ -411,6 +425,8 @@ class PlayerQueue: # clear queue first await self.clear() # restore queue + self._settings.repeat_mode = self._snapshot.repeat + self._settings.shuffle_enabled = self._snapshot.shuffle await self.update(self._snapshot.items) self._current_index = self._snapshot.index self._current_item_elapsed_time = self._snapshot.position @@ -421,6 +437,7 @@ class PlayerQueue: if not self._snapshot.powered: await self.player.power(False) # reset snapshot once restored + self.logger.debug("Restored snapshot...") self._snapshot = None async def play_index( @@ -625,10 +642,19 @@ class PlayerQueue: ) async def queue_stream_start( - self, start_index: int, seek_position: int, fade_in: bool, passive: bool = False + self, + start_index: int, + seek_position: int, + fade_in: bool, + is_alert: bool = False, + passive: bool = False, ) -> QueueStream: """Start the queue stream runner.""" - output_format = self._settings.stream_type + if is_alert and ContentType.MP3 in self.player.supported_content_types: + # force MP3 for alert messages + output_format = ContentType.MP3 + else: + output_format = self._settings.stream_type if self.player.use_multi_stream: # if multi stream is enabled, all child players should receive the same audio stream expected_clients = len(get_child_players(self.player, True)) @@ -646,6 +672,7 @@ class PlayerQueue: seek_position=seek_position, fade_in=fade_in, output_format=output_format, + is_alert=is_alert, ) self._stream_id = stream.stream_id # execute the play command on the player(s)