fix chromecast
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 25 May 2019 22:50:51 +0000 (00:50 +0200)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 25 May 2019 22:50:51 +0000 (00:50 +0200)
music_assistant/modules/player.py
music_assistant/modules/playerproviders/chromecast.py

index f0b9627b3573818d4043b46b67ed5e37a94971f5..1324780d5b2e771ede30ca5379cd5d715f9a047b 100755 (executable)
@@ -41,14 +41,17 @@ class Player():
         if player_id not in self._players:
             return
         player = self._players[player_id]
+        prov_id = player.player_provider
+        prov = self.providers[prov_id]
+        LOGGER.info('received command %s for player %s' %(cmd, player.name))
         # handle some common workarounds
         if cmd in ['pause', 'play'] and cmd_args == 'toggle':
             cmd = 'pause' if player.state == PlayerState.Playing else 'play'
-        if cmd == 'power' and cmd_args == 'toggle':
+        if cmd == 'power' and (cmd_args == 'toggle' or not cmd_args):
             cmd_args = 'off' if player.powered else 'on'
-        if cmd == 'volume' and (cmd_args == 'up' or '+' in cmd_args):
+        if cmd == 'volume' and (cmd_args == 'up' or '+' in str(cmd_args)):
             cmd_args = player.volume_level + 2
-        elif cmd == 'volume' and (cmd_args == 'down' or '-' in cmd_args):
+        elif cmd == 'volume' and (cmd_args == 'down' or '-' in str(cmd_args)):
             cmd_args = player.volume_level - 2
         # redirect playlist related commands to parent player
         if player.group_parent and cmd not in ['power', 'volume', 'mute']:
@@ -57,15 +60,14 @@ class Player():
         await self.__player_command_hass_integration(player, cmd, cmd_args)
         # handle mute as power
         if cmd == 'power' and player.settings['mute_as_power']:
-            cmd = 'mute'
-            cmd_args = 'on' if cmd_args == 'off' else 'off' # invert logic (power ON is mute OFF)
-        # normal execution of command on player
-        prov_id = self._players[player_id].player_provider
-        prov = self.providers[prov_id]
-        await prov.player_command(player_id, cmd, cmd_args)
+            args = 'on' if cmd_args == 'off' else 'off' # invert logic (power ON is mute OFF)
+            await prov.player_command(player_id, 'mute', args)
         # handle play on power on
         if cmd == 'power' and cmd_args == 'on' and player.settings['play_power_on']:
-            await prov.player_command(player_id, 'play')
+            cmd = 'play'
+            cmd_args = None
+        # normal execution of command on player
+        await prov.player_command(player_id, cmd, cmd_args)
         # handle group volume/power for group players
         await self.__player_group_commands(player, cmd, cmd_args)
 
@@ -123,7 +125,8 @@ class Player():
                 player_childs = [item for item in self._players.values() if item.group_parent == group_player.player_id]
                 if not group_player.powered and cmd == 'power' and cmd_args == 'on':
                     # power on group player
-                    await self.player_command(group_player.player_id, 'power', 'on')
+                    self.mass.event_loop.create_task(
+                            self.player_command(group_player.player_id, 'power', 'on'))
                 elif group_player.powered and cmd == 'power' and cmd_args == 'off':
                     # check if the group player should still be turned on
                     new_powered = False
@@ -132,7 +135,8 @@ class Player():
                             new_powered = True
                             break
                     if not new_powered:
-                        await self.player_command(group_player.player_id, 'power', 'off')
+                        self.mass.event_loop.create_task(
+                                self.player_command(group_player.player_id, 'power', 'off'))
     
     async def remove_player(self, player_id):
         ''' handle a player remove '''
@@ -179,8 +183,6 @@ class Player():
             player_childs = [item for item in self._players.values() if item.group_parent == player_id]
             if player.settings['apply_group_volume']:
                 player_details.volume_level = await self.__get_group_volume(player_childs)
-            if player.settings['apply_group_power']:
-                player_details.powered = await self.__get_group_power(player_childs)
         # compare values to detect changes
         if player.cur_item and player_details.cur_item and player.cur_item.name != player_details.cur_item.name:
             player_changed = True
@@ -195,12 +197,14 @@ class Player():
         if player_changed:
             # player is added or updated!
             asyncio.ensure_future(self.mass.event('player updated', player))
-            # is groupplayer, trigger update of its childs
-            # for child in player_childs:
-            #     asyncio.create_task(self.trigger_update(child.player_id))
-            # # if child player in a group, trigger update of parent
-            # if player.group_parent:
-            #     asyncio.create_task(self.trigger_update(player.group_parent))
+            if player_details.is_group:
+                # is groupplayer, trigger update of its childs
+                player_childs = [item for item in self._players.values() if item.group_parent == player_id]
+                for child in player_childs:
+                    asyncio.create_task(self.trigger_update(child.player_id))
+            elif player.group_parent:
+                # if child player in a group, trigger update of parent
+                asyncio.create_task(self.trigger_update(player.group_parent))
 
     async def __update_player_hass_settings(self, player_details, player_settings):
         ''' handle home assistant integration on a player '''
@@ -242,6 +246,8 @@ class Player():
         ''' handle group volume '''
         group_power = False
         for child_player in player_childs:
+            print(child_player.name)
+            print(child_player.powered)
             if child_player.enabled and child_player.powered:
                 group_power = True
                 break
index 69008c84a31607716eff558abc1fee19446b1976..817b882ec359e07a910500907d3ada4f3cba9a2d 100644 (file)
@@ -25,6 +25,7 @@ from pychromecast.controllers.spotify import SpotifyController
 from pychromecast.controllers.media import MediaController
 import types
 import urllib
+import select
 
 def setup(mass):
     ''' setup the provider'''
@@ -52,7 +53,6 @@ class ChromecastProvider(PlayerProvider):
         self._chromecasts = {}
         self._player_queue = {}
         self.supported_musicproviders = ['http']
-        self.http_session = aiohttp.ClientSession(loop=mass.event_loop)
         asyncio.ensure_future(self.__discover_chromecasts())
         
 
@@ -61,12 +61,14 @@ class ChromecastProvider(PlayerProvider):
     async def player_command(self, player_id, cmd:str, cmd_args=None):
         ''' issue command on player (play, pause, next, previous, stop, power, volume, mute) '''
         if cmd == 'play':
+            self._players[player_id].powered = True
             if self._chromecasts[player_id].media_controller.status.player_is_playing:
                 pass
             elif self._chromecasts[player_id].media_controller.status.player_is_paused:
                 self._chromecasts[player_id].media_controller.play()
             else:
                 await self.__resume_queue(player_id)
+            await self.mass.player.update_player(self._players[player_id])
         elif cmd == 'pause':
             self._chromecasts[player_id].media_controller.pause()
         elif cmd == 'stop':
@@ -88,6 +90,7 @@ class ChromecastProvider(PlayerProvider):
             self._chromecasts[player_id].set_volume_muted(False)
         elif cmd == 'mute':
             self._chromecasts[player_id].set_volume_muted(True)
+        #self._chromecasts[player_id].wait()
 
     async def player_queue(self, player_id, offset=0, limit=50):
         ''' return the current items in the player's queue '''
@@ -142,8 +145,6 @@ class ChromecastProvider(PlayerProvider):
         ''' load queue on player with given queue items '''
         castplayer = self._chromecasts[player_id]
         player = self._players[player_id]
-        media_controller = castplayer.media_controller
-        receiver_ctrl = media_controller._socket_client.receiver_controller
         queue_items = await self.__create_queue_items(new_tracks[:50])
         queuedata = { 
                 "type": 'QUEUE_LOAD',
@@ -153,7 +154,7 @@ class ChromecastProvider(PlayerProvider):
                 "startIndex":    startindex,    # Item index to play after this request or keep same item if undefined
                 "items": queue_items # only load 50 tracks at once or the socket will crash
         }
-        await self.__send_player_queue(receiver_ctrl, media_controller, queuedata)
+        await self.__send_player_queue(castplayer, queuedata)
         if len(new_tracks) > 50:
             await self.__queue_insert(player_id, new_tracks[51:])
 
@@ -161,36 +162,31 @@ class ChromecastProvider(PlayerProvider):
         ''' insert item into the player queue '''
         castplayer = self._chromecasts[player_id]
         queue_items = await self.__create_queue_items(new_tracks)
-        media_controller = castplayer.media_controller
-        receiver_ctrl = media_controller._socket_client.receiver_controller
         for chunk in chunks(queue_items, 50):
             queuedata = { 
                         "type": 'QUEUE_INSERT',
                         "insertBefore":     insert_before,
                         "items":            chunk
                 }
-            await self.__send_player_queue(receiver_ctrl, media_controller, queuedata)
+            await self.__send_player_queue(castplayer, queuedata)
 
     async def __queue_update(self, player_id, queue_items_to_update):
         ''' update the cast player queue '''
         castplayer = self._chromecasts[player_id]
-        media_controller = castplayer.media_controller
-        receiver_ctrl = media_controller._socket_client.receiver_controller
         queuedata = { 
                     "type": 'QUEUE_UPDATE',
                     "items": queue_items_to_update
             }
-        await self.__send_player_queue(receiver_ctrl, media_controller, queuedata)
+        await self.__send_player_queue(castplayer, queuedata)
 
     async def __queue_remove(self, player_id, queue_item_ids):
         ''' remove items from the cast player queue '''
-        media_controller = self._chromecasts[player_id].media_controller
-        receiver_ctrl = media_controller._socket_client.receiver_controller
+        castplayer = self._chromecasts[player_id]
         queuedata = { 
                     "type": 'QUEUE_REMOVE',
                     "items": queue_item_ids
             }
-        await self.__send_player_queue(receiver_ctrl, media_controller, queuedata)
+        await self.__send_player_queue(castplayer, queuedata)
 
     async def __resume_queue(self, player_id):
         ''' resume queue play after power off '''
@@ -234,12 +230,15 @@ class ChromecastProvider(PlayerProvider):
             }
         }
         
-    async def __send_player_queue(self, receiver_ctrl, media_controller, queuedata):
+    async def __send_player_queue(self, castplayer, queuedata):
         '''send new data to the CC queue'''
+        media_controller = castplayer.media_controller
+        receiver_ctrl = media_controller._socket_client.receiver_controller
         def app_launched_callback():
                 """Plays media after chromecast has switched to requested app."""
                 queuedata['mediaSessionId'] = media_controller.status.media_session_id
                 media_controller.send_message(queuedata, inc_session_id=False)
+                #castplayer.wait()
         receiver_ctrl.launch_app(media_controller.app_id,
                                 callback_function=app_launched_callback)
 
@@ -253,16 +252,13 @@ class ChromecastProvider(PlayerProvider):
             player.muted = caststatus.volume_muted
             player.volume_level = caststatus.volume_level * 100
         if mediastatus:
-            # chromecast does not support power on/of so we use idle state instead
+            # chromecast does not support power on/of so we only set state
             if mediastatus.player_state in ['PLAYING', 'BUFFERING']:
                 player.state = PlayerState.Playing
-                player.powered = True
             elif mediastatus.player_state == 'PAUSED':
                 player.state = PlayerState.Paused
-                player.powered = not chromecast.is_idle
             else:
                 player.state = PlayerState.Stopped
-                player.powered = player.powered
             player.cur_item = await self.__parse_track(mediastatus)
             player.cur_item_time =  chromecast.media_controller.status.adjusted_current_time
         await self.mass.player.update_player(player)
@@ -303,53 +299,62 @@ class ChromecastProvider(PlayerProvider):
         if added_player:
             if added_player in self._players:
                 self._players[added_player].group_parent = str(mz._uuid)
+                self.mass.event_loop.create_task(self.mass.player.update_player(self._players[added_player]))
         elif removed_player:
             if removed_player in self._players:
                 self._players[removed_player].group_parent = None
+                self.mass.event_loop.create_task(self.mass.player.update_player(self._players[removed_player]))
         else:
             for member in mz.members:
                 if member in self._players:
                     self._players[member].group_parent = str(mz._uuid)
+                    self.mass.event_loop.create_task(self.mass.player.update_player(self._players[member]))
+
+    async def __chromecast_discovered(self, chromecast):
+        LOGGER.info("discovered chromecast: %s" % chromecast)
+        player_id = str(chromecast.uuid)
+        ip_change = False
+        if player_id in self._chromecasts and chromecast.uri != self._chromecasts[player_id].uri:
+            LOGGER.warning('Chromecast uri changed ?! - old: %s - new: %s' %(self._chromecasts[player_id].uri, chromecast.uri))
+            ip_change = True
+        if not player_id in self._players or ip_change:
+            player = MusicPlayer()
+            player.player_id = player_id
+            player.name = chromecast.name
+            player.player_provider = self.prov_id
+            # patch the receive message method for handling queue status updates
+            chromecast.queue = []
+            chromecast.media_controller.queue_items = []
+            chromecast.media_controller.queue_cur_id = None
+            chromecast.media_controller.receive_message = types.MethodType(receive_message, chromecast.media_controller)
+            listenerCast = StatusListener(chromecast, self.__handle_player_state, self.mass.event_loop)
+            chromecast.register_status_listener(listenerCast)
+            listenerMedia = StatusMediaListener(chromecast, self.__handle_player_state, self.mass.event_loop)
+            chromecast.media_controller.register_status_listener(listenerMedia)
+            if chromecast.cast_type == 'group':
+                player.is_group = True
+                mz = MultizoneController(chromecast.uuid)
+                mz.register_listener(MZListener(mz, self.__handle_group_members_update, self.mass.event_loop))
+                chromecast.register_handler(mz)
+                chromecast.register_connection_listener(MZConnListener(mz))
+            self._chromecasts[player_id] = chromecast
+            self._players[player_id] = player
+            if not player_id in self._player_queue:
+                # TODO: persistant storage of player queue ?
+                self._player_queue[player_id] = []
+            chromecast.wait()
 
     @run_periodic(600)
     async def __discover_chromecasts(self):
         ''' discover chromecasts on the network '''
-        LOGGER.info('Starting Chromecast discovery...')
-        bg_task = run_background_task(self.mass.bg_executor, pychromecast.get_chromecasts)
-        chromecasts = await asyncio.gather(bg_task)
-        for chromecast in chromecasts[0]:
-            player_id = str(chromecast.uuid)
-            ip_change = False
-            if player_id in self._chromecasts and chromecast.uri != self._chromecasts[player_id].uri:
-                LOGGER.warning('Chromecast uri changed ?! - old: %s - new: %s' %(self._chromecasts[player_id].uri, chromecast.uri))
-                ip_change = True
-            if not player_id in self._players or ip_change:
-                player = MusicPlayer()
-                player.player_id = player_id
-                player.name = chromecast.name
-                player.player_provider = self.prov_id
-                # patch the receive message method for handling queue status updates
-                chromecast.queue = []
-                chromecast.media_controller.queue_items = []
-                chromecast.media_controller.queue_cur_id = None
-                chromecast.media_controller.receive_message = types.MethodType(receive_message, chromecast.media_controller)
-                listenerCast = StatusListener(chromecast, self.__handle_player_state, self.mass.event_loop)
-                chromecast.register_status_listener(listenerCast)
-                listenerMedia = StatusMediaListener(chromecast, self.__handle_player_state, self.mass.event_loop)
-                chromecast.media_controller.register_status_listener(listenerMedia)
-                if chromecast.cast_type == 'group':
-                    player.is_group = True
-                    mz = MultizoneController(chromecast.uuid)
-                    mz.register_listener(MZListener(mz, self.__handle_group_members_update, self.mass.event_loop))
-                    chromecast.register_handler(mz)
-                    chromecast.register_connection_listener(MZConnListener(mz))
-                self._chromecasts[player_id] = chromecast
-                self._players[player_id] = player
-                if not player_id in self._player_queue:
-                    # TODO: persistant storage of player queue ?
-                    self._player_queue[player_id] = []
-                chromecast.wait()
-        LOGGER.info('Chromecast discovery done...')
+        LOGGER.info('Running Chromecast discovery...')
+        def callback(chromecast):
+            self.mass.event_loop.create_task(self.__chromecast_discovered(chromecast))
+        
+        stop_discovery = pychromecast.get_chromecasts(blocking=False, callback=callback)
+        await asyncio.sleep(10)
+        stop_discovery()
+        LOGGER.info('Finished Chromecast discovery...')
 
 def chunks(l, n):
     """Yield successive n-sized chunks from l."""