From: Marcel van der Veldt Date: Sun, 9 Jun 2019 22:12:02 +0000 (+0200) Subject: some fixes to crossfade streaming X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=cc04bc0b1f635f23465836afe3d4444a23560718;p=music-assistant-server.git some fixes to crossfade streaming --- diff --git a/music_assistant/database.py b/music_assistant/database.py index a529758c..5a571dd7 100755 --- a/music_assistant/database.py +++ b/music_assistant/database.py @@ -491,6 +491,13 @@ class Database(): LOGGER.info('added track %s (%s) to database: %s' %(track.name, track.provider_ids, track_id)) return track_id + async def update_track(self, track_id, column_key, column_value): + ''' update column of existing track ''' + async with aiosqlite.connect(self.dbfile, timeout=20) as db: + sql_query = 'UPDATE tracks SET %s=%s WHERE track_id=%s;' %(column_key, column_value, track_id) + await db.execute(sql_query) + await db.commit() + async def artist_tracks(self, artist_id, limit=1000000, offset=0, orderby='name') -> List[Track]: ''' get all library tracks for the given artist ''' artist_id = try_parse_int(artist_id) diff --git a/music_assistant/modules/http_streamer.py b/music_assistant/modules/http_streamer.py index 0811ae9a..8922e8f2 100755 --- a/music_assistant/modules/http_streamer.py +++ b/music_assistant/modules/http_streamer.py @@ -79,7 +79,7 @@ class HTTPStreamer(): await resp.write(chunk) queue.task_done() LOGGER.info("stream_track fininished for %s" % track_id) - except asyncio.CancelledError: + except (asyncio.CancelledError, asyncio.TimeoutError): cancelled.set() LOGGER.info("stream_track interrupted for %s" % track_id) raise asyncio.CancelledError() @@ -129,8 +129,8 @@ class HTTPStreamer(): async def stream_queue(self, http_request): ''' - streamm all tracks in queue from player with http - loads audiodata in memory so only recommended for high performance servers + stream all tracks in queue from player with http + loads large part of audiodata in memory so only recommended for high performance servers use case is enable crossfade support for chromecast devices ''' player_id = http_request.query.get('player_id') @@ -181,6 +181,7 @@ class HTTPStreamer(): queue_index = startindex last_fadeout_data = b'' + self.mass.event_loop.create_task(self.mass.player.player_queue_stream_move(player_id, queue_index, True)) while True: # get the (next) track in queue try: @@ -260,7 +261,7 @@ class HTTPStreamer(): 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)) + 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)) queue_index += 1 @@ -409,6 +410,19 @@ 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): os.remove(tmpfile) diff --git a/music_assistant/modules/player.py b/music_assistant/modules/player.py index b2ac73ad..a2e17251 100755 --- a/music_assistant/modules/player.py +++ b/music_assistant/modules/player.py @@ -394,11 +394,11 @@ 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): + 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) player = self._players[player_id] - return await self.providers[player.player_provider].player_queue_stream_move(player_id, new_index) + return await self.providers[player.player_provider].player_queue_stream_move(player_id, new_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 08af0b7d..18fa9d01 100644 --- a/music_assistant/modules/playerproviders/chromecast.py +++ b/music_assistant/modules/playerproviders/chromecast.py @@ -52,6 +52,7 @@ class ChromecastProvider(PlayerProvider): self._players = {} self._chromecasts = {} self._player_queue = {} + self._player_queue_startindex = {} self.supported_musicproviders = ['http'] asyncio.ensure_future(self.__discover_chromecasts()) @@ -96,7 +97,7 @@ class ChromecastProvider(PlayerProvider): self._chromecasts[player_id].media_controller.queue_prev() elif cmd == 'power' and cmd_args == 'off': self._players[player_id].powered = False - self._chromecasts[player_id].media_controller.stop() # power is not supported so send stop instead + self._chromecasts[player_id].quit_app() # power is not supported so send quit_app instead await self.mass.player.update_player(self._players[player_id]) elif cmd == 'power': self._players[player_id].powered = True @@ -157,14 +158,18 @@ 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): + 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 + if is_start: + self._player_queue_startindex[player_id] = new_index chromecast = self._chromecasts[player_id] - mediastatus = chromecast.media_controller.status - await self.__handle_player_state(chromecast, mediastatus=mediastatus) - LOGGER.info("player_queue_stream_move") + # 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) ### Provider specific (helper) methods ##### @@ -315,14 +320,21 @@ class ChromecastProvider(PlayerProvider): 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 - cur_queue_index = player.cur_queue_index - player.cur_item = self._player_queue[player_id][cur_queue_index] - cur_time = mediastatus.adjusted_current_time - while cur_time > player.cur_item.duration-10: - cur_queue_index -=1 - prev_track = self._player_queue[player_id][cur_queue_index] - cur_time -= prev_track.duration - player.cur_item_time = cur_time + cur_time_queue = mediastatus.adjusted_current_time + total_time = 0 + track_time = 0 + queue_index = self._player_queue_startindex[player_id] + queue_track = None + while True: + queue_track = self._player_queue[player_id][queue_index] + if cur_time_queue > (queue_track.duration + total_time): + total_time += queue_track.duration + queue_index += 1 + else: + track_time = cur_time_queue - total_time + break + player.cur_item = queue_track + player.cur_item_time = track_time await self.mass.player.update_player(player) async def __parse_track(self, mediastatus): @@ -407,7 +419,7 @@ class ChromecastProvider(PlayerProvider): self._player_queue[player_id] = [] chromecast.wait() - @run_periodic(3600) + @run_periodic(600) async def __discover_chromecasts(self): ''' discover chromecasts on the network ''' LOGGER.info('Running Chromecast discovery...')