From: Marcel van der Veldt Date: Sat, 25 May 2019 22:50:51 +0000 (+0200) Subject: fix chromecast X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=f27a4f4ce39e413ebfa82cbf1eccc11736c66d66;p=music-assistant-server.git fix chromecast --- diff --git a/music_assistant/modules/player.py b/music_assistant/modules/player.py index f0b9627b..1324780d 100755 --- a/music_assistant/modules/player.py +++ b/music_assistant/modules/player.py @@ -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 diff --git a/music_assistant/modules/playerproviders/chromecast.py b/music_assistant/modules/playerproviders/chromecast.py index 69008c84..817b882e 100644 --- a/music_assistant/modules/playerproviders/chromecast.py +++ b/music_assistant/modules/playerproviders/chromecast.py @@ -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."""