some fixes to crossfade streaming
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 9 Jun 2019 22:12:02 +0000 (00:12 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sun, 9 Jun 2019 22:12:02 +0000 (00:12 +0200)
music_assistant/database.py
music_assistant/modules/http_streamer.py
music_assistant/modules/player.py
music_assistant/modules/playerproviders/chromecast.py

index a529758c7c059f100b50ac1f60132f60c9241028..5a571dd74b2eb936ea957e5bcbeece0d003b4897 100755 (executable)
@@ -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)
index 0811ae9a9d40f7df4c0630c07eed1dff5b96e8c2..8922e8f2accb70b234a4a4ceebb716b429315982 100755 (executable)
@@ -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)
index b2ac73adf71c3c30ea61df46816040350e75ccba..a2e1725167949ddbbeccde32c033cdbd9b778799 100755 (executable)
@@ -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 '''
index 08af0b7d38dac62876b273f18d4a0b189661db1d..18fa9d0144b35d7ef30f775498ddc7b243de0d9c 100644 (file)
@@ -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...')