From f97bf87911bcb599d4d7bb77bdcc3d8e768d0512 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Mon, 10 Jun 2019 11:00:54 +0200 Subject: [PATCH] some more improvements --- music_assistant/modules/http_streamer.py | 40 ++++++++----------- music_assistant/modules/player.py | 7 ++-- .../modules/playerproviders/chromecast.py | 40 +++++++++---------- 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/music_assistant/modules/http_streamer.py b/music_assistant/modules/http_streamer.py index 8922e8f2..09e64066 100755 --- a/music_assistant/modules/http_streamer.py +++ b/music_assistant/modules/http_streamer.py @@ -134,7 +134,6 @@ class HTTPStreamer(): use case is enable crossfade support for chromecast devices ''' player_id = http_request.query.get('player_id') - startindex = int(http_request.query.get('startindex',0)) cancelled = threading.Event() resp = web.StreamResponse(status=200, reason='OK', @@ -146,7 +145,7 @@ class HTTPStreamer(): cancelled = threading.Event() run_async_background_task( self.mass.bg_executor, - self.__stream_queue, player_id, startindex, queue, cancelled) + self.__stream_queue, player_id, queue, cancelled) try: while True: chunk = await queue.get() @@ -161,7 +160,7 @@ class HTTPStreamer(): raise asyncio.CancelledError() return resp - async def __stream_queue(self, player_id, startindex, buffer, cancelled): + async def __stream_queue(self, player_id, buffer, cancelled): ''' start streaming all queue tracks ''' sample_rate = self.mass.config['player_settings'][player_id]['max_sample_rate'] fade_length = self.mass.config['player_settings'][player_id]["crossfade_duration"] @@ -179,9 +178,10 @@ class HTTPStreamer(): await buffer.put(b'') # indicate EOF asyncio.create_task(fill_buffer()) - queue_index = startindex + player = await self.mass.player.player(player_id) + queue_index = player.cur_queue_index last_fadeout_data = b'' - self.mass.event_loop.create_task(self.mass.player.player_queue_stream_move(player_id, queue_index, True)) + self.mass.event_loop.create_task(self.mass.player.player_queue_stream_update(player_id, queue_index, True)) while True: # get the (next) track in queue try: @@ -199,13 +199,14 @@ class HTTPStreamer(): fade_in_part = b'' cur_chunk = 0 prev_chunk = None - + bytes_written = 0 async for is_last_chunk, chunk in self.__get_raw_audio(track_id, provider, sample_rate, fade_bytes): cur_chunk += 1 if cur_chunk == 1 and not last_fadeout_data: # fade-in part but this is the first track so just pass it to the final file sox_proc.stdin.write(chunk) await sox_proc.stdin.drain() + bytes_written += len(chunk) elif cur_chunk == 1 and last_fadeout_data: # create fade-out part args = 'sox --ignore-length -t %s - -t %s - reverse fade t %s reverse' % (pcm_args, pcm_args, fade_length) @@ -233,6 +234,7 @@ class HTTPStreamer(): # write the crossfade part to the sox player sox_proc.stdin.write(crossfade_part) await sox_proc.stdin.drain() + bytes_written += len(crossfade_part) fadeinfile.close() fadeoutfile.close() del crossfade_part @@ -244,6 +246,7 @@ class HTTPStreamer(): last_fadeout_data = last_part[-fade_bytes:] bytes_remaining = last_part[:-fade_bytes] sox_proc.stdin.write(bytes_remaining) + bytes_written += len(bytes_remaining) await sox_proc.stdin.drain() else: # middle part of the track @@ -251,6 +254,7 @@ class HTTPStreamer(): if prev_chunk: sox_proc.stdin.write(prev_chunk) await sox_proc.stdin.drain() + bytes_written += len(prev_chunk) prev_chunk = chunk else: prev_chunk = chunk @@ -259,12 +263,15 @@ class HTTPStreamer(): # and it helps a bit in the quest to follow where we are in the queue while buffer.qsize() > 1 and not cancelled.is_set(): await asyncio.sleep(1) - if cur_chunk == 1: - # report start stream of current queue index - self.mass.event_loop.create_task(self.mass.player.player_queue_stream_move(player_id, queue_index, False)) # end of the track reached LOGGER.info("Finished Streaming queue track: %s - %s" % (track_id, queue_track.name)) + # update actual duration to the queue for more accurate now playing info + accurate_duration = bytes_written / int(sample_rate * 4 * 2) + queue_track.duration = accurate_duration + self.mass.player.providers[player.player_provider]._player_queue[player_id][queue_index] = queue_track + # move to next queue index queue_index += 1 + self.mass.event_loop.create_task(self.mass.player.player_queue_stream_update(player_id, queue_index, False)) # break out the loop if the http session is cancelled if cancelled.is_set(): break @@ -410,23 +417,10 @@ class HTTPStreamer(): cmd = 'sox -t %s %s -t flac -C5 %s silence 1 0.1 1%% reverse silence 1 0.1 1%% reverse' %(content_type, tmpfile, cachefile) process = await asyncio.create_subprocess_shell(cmd) await process.wait() - # retrieve accurate track duration - cmd = 'soxi -d "%s"' %(cachefile) - process = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.PIPE) - stdout, stderr = await process.communicate() - durationstr = stdout.decode().split()[0] - hours = int(durationstr.split(":")[0]) - minutes = int(durationstr.split(":")[1]) - seconds = float(durationstr.split(":")[0]) - total_duration = (hours*60*60) + (minutes*60) + seconds - LOGGER.info("track duration for track %s is %s" %(track_id, total_duration)) - item_id = await self.mass.db.get_database_id(provider, track_id, MediaType.Track) - await self.mass.db.update_track(item_id, "duration", total_duration) # always clean up temp file - while os.path.isfile(tmpfile): + if os.path.isfile(tmpfile): os.remove(tmpfile) - await asyncio.sleep(0.5) LOGGER.info('Fininished analyzing file %s' % tmpfile) async def __get_track_gain_correct(self, track_id, provider): diff --git a/music_assistant/modules/player.py b/music_assistant/modules/player.py index a2e17251..add13064 100755 --- a/music_assistant/modules/player.py +++ b/music_assistant/modules/player.py @@ -394,11 +394,10 @@ class Player(): ''' get current index of the player's queue ''' return self._players[player_id].cur_queue_index - async def player_queue_stream_move(self, player_id, new_index, is_start): - ''' called by our queue streamer when it's loading a new track ''' - new_index = int(new_index) + async def player_queue_stream_update(self, player_id, cur_index, is_start=False): + ''' called by our queue streamer when it started playing the queue from position x ''' player = self._players[player_id] - return await self.providers[player.player_provider].player_queue_stream_move(player_id, new_index, is_start) + return await self.providers[player.player_provider].player_queue_stream_update(player_id, cur_index, is_start) def load_providers(self): ''' dynamically load providers ''' diff --git a/music_assistant/modules/playerproviders/chromecast.py b/music_assistant/modules/playerproviders/chromecast.py index 18fa9d01..9f168d97 100644 --- a/music_assistant/modules/playerproviders/chromecast.py +++ b/music_assistant/modules/playerproviders/chromecast.py @@ -87,12 +87,14 @@ class ChromecastProvider(PlayerProvider): elif cmd == 'next': enable_crossfade = self.mass.config['player_settings'][player_id]["crossfade_duration"] > 0 if enable_crossfade: - await self.__play_stream_queue(player_id, self._players[player_id].cur_queue_index+1) + self._players[player_id].cur_queue_index+=1 + await self.__play_stream_queue(player_id, self._players[player_id].cur_queue_index) else: self._chromecasts[player_id].media_controller.queue_next() elif cmd == 'previous': if enable_crossfade: - await self.__play_stream_queue(player_id, self._players[player_id].cur_queue_index-1) + self._players[player_id].cur_queue_index-=1 + await self.__play_stream_queue(player_id, self._players[player_id].cur_queue_index) else: self._chromecasts[player_id].media_controller.queue_prev() elif cmd == 'power' and cmd_args == 'off': @@ -120,7 +122,7 @@ class ChromecastProvider(PlayerProvider): play media on a player ''' castplayer = self._chromecasts[player_id] - cur_queue_index = await self.__get_cur_queue_index(player_id) + cur_queue_index = self._players[player_id].cur_queue_index enable_crossfade = self.mass.config['player_settings'][player_id]["crossfade_duration"] > 0 if queue_opt == 'replace' or not self._player_queue[player_id]: @@ -158,29 +160,24 @@ class ChromecastProvider(PlayerProvider): if not enable_crossfade: await self.__queue_insert(player_id, media_items) - async def player_queue_stream_move(self, player_id, new_index, is_start): - ''' called by the queue streamer when it's loading a new track ''' - self._players[player_id].cur_queue_index = new_index - # trigger update + async def player_queue_stream_update(self, player_id, cur_index, is_start=False): + ''' called by our queue streamer when it started playing a track in the queue at index X ''' if is_start: - self._player_queue_startindex[player_id] = new_index - chromecast = self._chromecasts[player_id] - # fire update a few times as we can't predict the precaching exactly - for i in range(0, 5): - mediastatus = chromecast.media_controller.status - await self.__handle_player_state(chromecast, mediastatus=mediastatus) - await asyncio.sleep(5) - + self._player_queue_startindex[player_id] = cur_index + # schedule update a few times as we don't know how much time is prebuffered + for i in range(0, 20): + castplayer = self._chromecasts[player_id] + status = castplayer.media_controller.status + await self.__handle_player_state(castplayer, mediastatus=status) + await asyncio.sleep(2) + ### Provider specific (helper) methods ##### - async def __get_cur_queue_index(self, player_id): + async def __get_cur_queue_index(self, player_id, current_uri): ''' retrieve index of current item in the player queue ''' - enable_crossfade = self.mass.config['player_settings'][player_id]["crossfade_duration"] > 0 - if enable_crossfade: - return self._players[player_id].cur_queue_index cur_index = 0 for index, track in enumerate(self._player_queue[player_id]): - if track.uri == self._chromecasts[player_id].media_controller.status.content_id: + if track.uri == current_uri: cur_index = index break return cur_index @@ -316,7 +313,7 @@ class ChromecastProvider(PlayerProvider): if not 'stream_queue' in mediastatus.content_id: player.cur_item = await self.__parse_track(mediastatus) player.cur_item_time = mediastatus.adjusted_current_time - player.cur_queue_index = await self.__get_cur_queue_index(player_id) + player.cur_queue_index = await self.__get_cur_queue_index(player_id, mediastatus.content_id) else: # try to work out the current time # player is playing a constant stream of the queue so we need to do this the hard way @@ -335,6 +332,7 @@ class ChromecastProvider(PlayerProvider): break player.cur_item = queue_track player.cur_item_time = track_time + player.cur_queue_index = queue_index await self.mass.player.update_player(player) async def __parse_track(self, mediastatus): -- 2.34.1