async def test_content_item_update_calls_active_listener(psm, protocol_mock, listener): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(msg) assert listener.call_count == 1 update_item = set_path(messages.create(pb.UPDATE_CONTENT_ITEM_MESSAGE)) item = update_item.inner().contentItems.add() await protocol_mock.inject(update_item) assert listener.call_count == 2 msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert listener.call_count == 3 now_playing = set_path(messages.create(pb.SET_NOW_PLAYING_PLAYER_MESSAGE)) await protocol_mock.inject(now_playing) assert listener.call_count == 4 await protocol_mock.inject(update_item) assert listener.call_count == 5
def volume_control(self, available): msg = messages.create(protobuf.VOLUME_CONTROL_AVAILABILITY_MESSAGE) msg.inner().volumeControlAvailable = available self._send(msg) msg = messages.create( protobuf.VOLUME_CONTROL_CAPABILITIES_DID_CHANGE_MESSAGE) msg.inner().capabilities.volumeControlAvailable = available msg.inner().outputDeviceUID = DEVICE_UID self._send(msg)
def handle_send_hid_event(self, message, inner): outstanding = self.state.outstanding_keypresses # These corresponds to the bytes mapping to pressed key (see # send_hid_event in pyatv/protocols/mrp/messages.py) start = inner.hidEventData[43:49] use_page, usage, down_press = struct.unpack(">HHH", start) if down_press == 1: outstanding[(use_page, usage)] = stub_sleep() self.send_to_client( messages.create(0, identifier=message.identifier)) elif down_press == 0: if (use_page, usage) in outstanding: pressed_key = _KEY_LOOKUP.get((use_page, usage)) if not pressed_key: raise Exception( f"unsupported key: use_page={use_page}, usage={usage}") if pressed_key == "select" and self.state.last_button_pressed == "home": self.state.powered_on = False self._send_device_info(update=True) time_diff = stub_sleep() - outstanding[(use_page, usage)] if time_diff > 0.5: self.state.last_button_action = const.InputAction.Hold elif self.state.last_button_pressed == pressed_key: # NB: Will report double tap for >= 3 clicks (fix when needed) self.state.last_button_action = const.InputAction.DoubleTap else: self.state.last_button_action = const.InputAction.SingleTap self.state.last_button_pressed = pressed_key del outstanding[(use_page, usage)] _LOGGER.debug("Pressed button: %s", self.state.last_button_pressed) self.send_to_client( messages.create(0, identifier=message.identifier)) # Special cases for some buttons if pressed_key == "volumeup" and not math.isclose( self.state.volume, 100.0): self.state.set_volume( min(self.state.volume + VOLUME_STEP, 100.0), DEVICE_UID) elif pressed_key == "volumedown" and not math.isclose( self.state.volume, 0.0): self.state.set_volume( max(self.state.volume - VOLUME_STEP, 0.0), DEVICE_UID) else: _LOGGER.error("Missing key down for %d,%d", use_page, usage) else: _LOGGER.error("Invalid key press state: %d", down_press)
async def test_set_now_playing_player_for_active_client(psm, protocol_mock, listener): msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) msg = set_path(messages.create(pb.SET_NOW_PLAYING_PLAYER_MESSAGE)) await protocol_mock.inject(msg) assert listener.call_count == 2 assert psm.playing.identifier == PLAYER_ID_1 assert psm.playing.display_name == PLAYER_NAME_1
async def test_content_item_update(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) msg = add_metadata_item(msg, identifier="id", title="item", playCount=123) await protocol_mock.inject(msg) msg = set_path(messages.create(pb.UPDATE_CONTENT_ITEM_MESSAGE)) item = msg.inner().contentItems.add() item.identifier = "id" item.metadata.title = "new title" item.metadata.playCount = 1111 await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.metadata_field("title") == "new title" assert player.metadata_field("playCount") == 1111
def update_client(self, display_name, identifier): msg = messages.create(protobuf.UPDATE_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = identifier if display_name is not None: client.displayName = display_name self._send(msg)
async def heartbeat_loop(protocol): """Periodically send heartbeat messages to device.""" _LOGGER.debug("Starting heartbeat loop") count = 0 attempts = 0 message = messages.create(protobuf.GENERIC_MESSAGE) while True: try: # Re-attempts are made with no initial delay to more quickly # recover a failed heartbeat (if possible) if attempts == 0: await asyncio.sleep(HEARTBEAT_INTERVAL) _LOGGER.debug("Sending periodic heartbeat %d", count) await protocol.send_and_receive(message) _LOGGER.debug("Got heartbeat %d", count) except asyncio.CancelledError: break except Exception: attempts += 1 if attempts > HEARTBEAT_RETRIES: _LOGGER.error("heartbeat %d failed after %d tries", count, attempts) protocol.connection.close() break _LOGGER.debug("heartbeat %d failed", count) else: attempts = 0 finally: count += 1 _LOGGER.debug("Stopping heartbeat loop at %d", count)
async def test_metadata_single_item(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) msg = add_metadata_item(msg, title="item") await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.metadata.title == "item"
async def test_get_metadata_field(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) msg = add_metadata_item(msg, title="item", playCount=123) await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.metadata_field("title") == "item" assert player.metadata_field("playCount") == 123
async def test_playback_state_playing_with_zero_playbac_rate(psm, protocol_mock): set_state = set_path(messages.create(pb.SET_STATE_MESSAGE)) set_state.inner().playbackState = pb.PlaybackState.Playing msg = add_metadata_item(set_state, playbackRate=0.0) await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.playback_state == pb.PlaybackState.Playing
async def test_set_now_playing_player_when_no_client(psm, protocol_mock, listener): msg = set_path(messages.create(pb.SET_NOW_PLAYING_PLAYER_MESSAGE)) await protocol_mock.inject(msg) assert listener.call_count == 0 assert not psm.playing.identifier assert not psm.playing.display_name
def handle_generic(self, message, inner): # Generic message is used by pyatv for heartbeats self.state.heartbeat_count += 1 _LOGGER.debug("Received heartbeat (total count: %d)", self.state.heartbeat_count) self.send_to_client( messages.create(protobuf.ProtocolMessage.UNKNOWN_MESSAGE, identifier=message.identifier))
async def test_default_player_when_only_client_set(psm, protocol_mock, listener): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(msg) msg = set_path( messages.create(pb.SET_STATE_MESSAGE), player_id=DEFAULT_PLAYER, player_name="Default Name", ) await protocol_mock.inject(msg) msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert psm.playing.identifier == DEFAULT_PLAYER assert psm.playing.display_name == "Default Name"
async def test_update_client(psm, protocol_mock, listener): msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert listener.call_count == 1 assert psm.client.display_name is None update = messages.create(pb.UPDATE_CLIENT_MESSAGE) client = update.inner().client client.bundleIdentifier = CLIENT_ID_1 client.displayName = CLIENT_NAME_1 await protocol_mock.inject(update) assert listener.call_count == 2 assert psm.client.display_name == CLIENT_NAME_1
def default_supported_commands(self, commands): msg = messages.create(protobuf.SET_DEFAULT_SUPPORTED_COMMANDS_MESSAGE) supported_commands = msg.inner().supportedCommands.supportedCommands for command in commands: item = supported_commands.add() item.command = command item.enabled = True msg.inner().playerPath.client.bundleIdentifier = PLAYER_IDENTIFIER self._send(msg)
async def test_get_command_info(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) info = msg.inner().supportedCommands.supportedCommands.add() info.command = pb.CommandInfo_pb2.Pause await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.command_info(pb.CommandInfo_pb2.Play) is None assert player.command_info(pb.CommandInfo_pb2.Pause) is not None
async def test_set_now_playing_client(psm, protocol_mock, listener): msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert listener.call_count == 1 assert psm.client.bundle_identifier == CLIENT_ID_1
async def test_remove_active_player(psm, protocol_mock, listener): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(msg) msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) msg = set_path(messages.create(pb.SET_NOW_PLAYING_PLAYER_MESSAGE)) await protocol_mock.inject(msg) assert psm.playing.identifier == PLAYER_ID_1 remove = set_path(messages.create(pb.REMOVE_PLAYER_MESSAGE)) await protocol_mock.inject(remove) assert listener.call_count == 4 assert not psm.playing.is_valid
async def test_remove_not_active_client(psm, protocol_mock, listener): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(msg) msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert listener.call_count == 2 assert psm.client.bundle_identifier == CLIENT_ID_1 remove = messages.create(pb.REMOVE_CLIENT_MESSAGE) client = remove.inner().client client.bundleIdentifier = CLIENT_ID_2 await protocol_mock.inject(remove) assert listener.call_count == 2 assert psm.client.bundle_identifier == CLIENT_ID_1
def set_active_player(self, identifier): if identifier is not None and identifier not in self.states: raise Exception(f"invalid player: {identifier}") self.active_player = identifier now_playing = messages.create(protobuf.SET_NOW_PLAYING_CLIENT_MESSAGE) client = now_playing.inner().client if identifier: client.bundleIdentifier = identifier self._send(now_playing)
def set_volume(self, volume, device_uid): if 0 <= volume <= 1: self.volume = volume msg = messages.create(protobuf.VOLUME_DID_CHANGE_MESSAGE) msg.inner().outputDeviceUID = device_uid msg.inner().volume = volume self._send(msg) else: _LOGGER.debug("Value %f out of range", volume)
async def test_get_client_and_player(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.identifier == PLAYER_ID_1 assert player.display_name == PLAYER_NAME_1 client = psm.get_client(msg.inner().playerPath.client) assert client.bundle_identifier == CLIENT_ID_1 assert client.display_name == CLIENT_NAME_1
def handle_client_updates_config(self, message, inner): for identifier, metadata in self.state.states.items(): self.send_to_client(_set_state_message(metadata, identifier)) # Trigger sending of SetNowPlayingClientMessage if self.state.active_player: self.state.set_active_player(self.state.active_player) if message.identifier is not None: self.send_to_client( messages.create(0, identifier=message.identifier))
async def test_set_state_calls_active_listener(psm, protocol_mock, listener): set_state = set_path(messages.create(pb.SET_STATE_MESSAGE)) await protocol_mock.inject(set_state) assert listener.call_count == 1 msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) assert listener.call_count == 2 now_playing = set_path(messages.create(pb.SET_NOW_PLAYING_PLAYER_MESSAGE)) await protocol_mock.inject(now_playing) assert listener.call_count == 3 await protocol_mock.inject(set_state) assert listener.call_count == 4
async def test_set_default_supported_commands(psm, protocol_mock, listener): msg = messages.create(pb.SET_DEFAULT_SUPPORTED_COMMANDS_MESSAGE) supported_commands = msg.inner().supportedCommands.supportedCommands command = supported_commands.add() command.command = pb.CommandInfo_pb2.Play msg.inner().playerPath.client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) msg = messages.create(pb.SET_NOW_PLAYING_CLIENT_MESSAGE) client = msg.inner().client client.bundleIdentifier = CLIENT_ID_1 await protocol_mock.inject(msg) # Default commands are set on client, so any player belonging to that client # should have the supported command player_path = pb.PlayerPath() player_path.client.bundleIdentifier = CLIENT_ID_1 player_path.player.identifier = PLAYER_ID_1 player = psm.get_player(player_path) assert player.command_info(pb.CommandInfo_pb2.Play) assert listener.call_count == 2
async def test_audio_volume_did_change(protocol_mock, audio, device_uid, volume, expected_volume): await volume_controls_changed(protocol_mock, DEVICE_UID, True) assert audio.is_available assert math.isclose(audio.volume, 0.0) message = messages.create(protobuf.VOLUME_DID_CHANGE_MESSAGE) message.inner().outputDeviceUID = device_uid message.inner().volume = volume await protocol_mock.inject(message) assert math.isclose(audio.volume, expected_volume)
async def test_metadata_item_identifier(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) msg = add_metadata_item(msg, identifier="id1", title="item1") await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.item_identifier == "id1" msg = add_metadata_item(msg, location=1, identifier="id2", title="item2") await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.item_identifier == "id2"
async def test_playback_state_without_rate(psm, protocol_mock): msg = set_path(messages.create(pb.SET_STATE_MESSAGE)) msg.inner().playbackState = pb.PlaybackState.Paused msg = add_metadata_item(msg) await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.playback_state is pb.PlaybackState.Paused msg.inner().playbackState = pb.PlaybackState.Playing await protocol_mock.inject(msg) player = psm.get_player(msg.inner().playerPath) assert player.playback_state is pb.PlaybackState.Playing
async def _do_press(keycode: Tuple[int, int], hold: bool): await protocol.send( messages.send_hid_event(keycode[0], keycode[1], True)) if hold: # Hardcoded hold time for one second await asyncio.sleep(1) await protocol.send( messages.send_hid_event(keycode[0], keycode[1], False)) # Send and receive a generic message as some kind of "flush" mechanism if flush: await protocol.send_and_receive( messages.create(protobuf.GENERIC_MESSAGE))
def handle_playback_queue_request(self, message, inner): state = self.state.get_player_state(self.state.active_player) setstate = messages.create(protobuf.SET_STATE_MESSAGE, identifier=message.identifier) artwork_data = self.state.states[self.state.active_player].artwork if artwork_data: queue = setstate.inner().playbackQueue queue.location = 0 item = queue.contentItems.add() item.artworkData = artwork_data item.artworkDataWidth = state.artwork_width or 456 item.artworkDataHeight = state.artwork_height or 789 self.send_to_client(setstate)