def __init__( self, loop, session_manager: ClientSessionManager, config: conf.AppleTV, airplay: Stream, ) -> None: """Initialize a new Apple TV.""" super().__init__() self._session_manager = session_manager self._config = config self._dmap_service = config.get_service(Protocol.DMAP) assert self._dmap_service is not None daap_http = net.HttpSession( session_manager.session, f"http://{config.address}:{self._dmap_service.port}/", ) self._requester = DaapRequester(daap_http, self._dmap_service.credentials) self._apple_tv = BaseDmapAppleTV(self._requester) self._dmap_remote = DmapRemoteControl(self._apple_tv) self._dmap_metadata = DmapMetadata(config.identifier, self._apple_tv) self._dmap_power = DmapPower() self._dmap_push_updater = DmapPushUpdater(loop, self._apple_tv, self) self._dmap_features = DmapFeatures(config, self._apple_tv) self._airplay = airplay
def __init__( self, loop: asyncio.AbstractEventLoop, session_manager: ClientSessionManager, config: conf.AppleTV, airplay: Stream, ) -> None: """Initialize a new Apple TV.""" super().__init__() self._session_manager = session_manager self._config = config self._mrp_service = config.get_service(Protocol.MRP) assert self._mrp_service is not None self._connection = MrpConnection(config.address, self._mrp_service.port, loop, atv=self) self._srp = SRPAuthHandler() self._protocol = MrpProtocol(self._connection, self._srp, self._mrp_service) self._psm = PlayerStateManager(self._protocol, loop) self._mrp_remote = MrpRemoteControl(loop, self._psm, self._protocol) self._mrp_metadata = MrpMetadata(self._protocol, self._psm, config.identifier) self._mrp_power = MrpPower(loop, self._protocol, self._mrp_remote) self._mrp_push_updater = MrpPushUpdater(loop, self._mrp_metadata, self._psm) self._mrp_features = MrpFeatures(self._config, self._psm) self._airplay = airplay
def setup( loop: asyncio.AbstractEventLoop, config: conf.AppleTV, interfaces: Dict[Any, Relayer], device_listener: StateProducer, session_manager: ClientSessionManager, ) -> Optional[Tuple[Callable[[], Awaitable[None]], Callable[[], None], Set[FeatureName]]]: """Set up a new AirPlay service.""" service = config.get_service(Protocol.AirPlay) assert service is not None # TODO: Split up in connect/protocol and Stream implementation stream = AirPlayStream(config) interfaces[Features].register( AirPlayFeatures(cast(conf.AirPlayService, service)), Protocol.AirPlay) interfaces[Stream].register(stream, Protocol.AirPlay) async def _connect() -> None: pass def _close() -> None: stream.close() return _connect, _close, set([FeatureName.PlayUrl])
class PairFunctionalTest(AioHTTPTestCase): def setUp(self): AioHTTPTestCase.setUp(self) self.pairing = None self.service = AirPlayService('airplay_id', port=self.server.port) self.conf = AppleTV('127.0.0.1', 'Apple TV') self.conf.add_service(self.service) async def tearDownAsync(self): await self.pairing.close() await super().tearDownAsync() async def get_application(self, loop=None): self.fake_atv = FakeAirPlayDevice(self) self.usecase = AirPlayUseCases(self.fake_atv) return self.fake_atv.app async def do_pairing(self, pin=DEVICE_PIN): self.usecase.airplay_require_authentication() self.pairing = await pair(self.conf, Protocol.AirPlay, self.loop) self.assertTrue(self.pairing.device_provides_pin) await self.pairing.begin() if pin: self.pairing.pin(pin) self.assertFalse(self.pairing.has_paired) await self.pairing.finish() self.assertTrue(self.pairing.has_paired) self.assertEqual(self.service.credentials, DEVICE_CREDENTIALS) @unittest_run_loop async def test_pairing_exception_invalid_pin(self): with self.assertRaises(exceptions.PairingError): await self.do_pairing(9999) @unittest_run_loop async def test_pairing_exception_no_pin(self): with self.assertRaises(exceptions.PairingError): await self.do_pairing(None) @unittest_run_loop @patch('os.urandom') async def test_pairing_with_device_new_credentials(self, rand_func): rand_func.side_effect = predetermined_key await self.do_pairing() @unittest_run_loop async def test_pairing_with_device_existing_credentials(self): self.conf.get_service( Protocol.AirPlay).credentials = DEVICE_CREDENTIALS await self.do_pairing()
def __init__( self, config: conf.AppleTV, session_manager: ClientSessionManager, _ ) -> None: """Initialize a new MrpPairingHandler.""" super().__init__(session_manager, config.get_service(Protocol.AirPlay)) self.http: Optional[HttpConnection] = None self.address: str = str(config.address) self.pairing_procedure: Optional[AirPlayPairingProcedure] = None self.credentials: LegacyCredentials = self._setup_credentials() self.pin_code: Optional[str] = None self._has_paired: bool = False
def setup( # pylint: disable=too-many-locals loop: asyncio.AbstractEventLoop, config: conf.AppleTV, interfaces: Dict[Any, Relayer], device_listener: StateProducer, session_manager: ClientSessionManager, ) -> Optional[ Tuple[Callable[[], Awaitable[None]], Callable[[], None], Set[FeatureName]] ]: """Set up a new MRP service.""" service = config.get_service(Protocol.MRP) assert service is not None connection = MrpConnection(config.address, service.port, loop, atv=device_listener) protocol = MrpProtocol(connection, SRPAuthHandler(), service) psm = PlayerStateManager(protocol) remote_control = MrpRemoteControl(loop, psm, protocol) metadata = MrpMetadata(protocol, psm, config.identifier) push_updater = MrpPushUpdater(loop, metadata, psm) power = MrpPower(loop, protocol, remote_control) interfaces[RemoteControl].register(remote_control, Protocol.MRP) interfaces[Metadata].register(metadata, Protocol.MRP) interfaces[Power].register(power, Protocol.MRP) interfaces[PushUpdater].register(push_updater, Protocol.MRP) interfaces[Features].register(MrpFeatures(config, psm), Protocol.MRP) # Forward power events to the facade instance power.listener = interfaces[Power] async def _connect() -> None: await protocol.start() def _close() -> None: push_updater.stop() protocol.stop() # Features managed by this protocol features = set( [ FeatureName.Artwork, FeatureName.VolumeDown, FeatureName.VolumeUp, FeatureName.App, ] ) features.update(_FEATURES_SUPPORTED) features.update(_FEATURE_COMMAND_MAP.keys()) features.update(_FIELD_FEATURES.keys()) return _connect, _close, features
def setup( loop: asyncio.AbstractEventLoop, config: conf.AppleTV, interfaces: Dict[Any, Relayer], device_listener: StateProducer, session_manager: ClientSessionManager, ) -> Optional[ Tuple[Callable[[], Awaitable[None]], Callable[[], None], Set[FeatureName]] ]: """Set up a new DMAP service.""" service = config.get_service(Protocol.DMAP) assert service is not None daap_http = HttpSession( session_manager.session, f"http://{config.address}:{service.port}/", ) requester = DaapRequester(daap_http, service.credentials) apple_tv = BaseDmapAppleTV(requester) push_updater = DmapPushUpdater(loop, apple_tv, device_listener) metadata = DmapMetadata(config.identifier, apple_tv) interfaces[RemoteControl].register(DmapRemoteControl(apple_tv), Protocol.DMAP) interfaces[Metadata].register(metadata, Protocol.DMAP) interfaces[Power].register(DmapPower(), Protocol.DMAP) interfaces[PushUpdater].register(push_updater, Protocol.DMAP) interfaces[Features].register(DmapFeatures(config, apple_tv), Protocol.DMAP) async def _connect() -> None: await requester.login() def _close() -> None: push_updater.stop() device_listener.listener.connection_closed() # Features managed by this protocol features = set([FeatureName.VolumeDown, FeatureName.VolumeUp]) features.update(_AVAILABLE_FEATURES) features.update(_UNKNOWN_FEATURES) features.update(_FIELD_FEATURES.keys()) return _connect, _close, features
async def pair(config: conf.AppleTV, protocol: Protocol, loop: asyncio.AbstractEventLoop, session: aiohttp.ClientSession = None, **kwargs): """Pair a protocol for an Apple TV.""" service = config.get_service(protocol) if not service: raise exceptions.NoServiceError(f"no service available for {protocol}") handler = { Protocol.DMAP: DmapPairingHandler, Protocol.MRP: MrpPairingHandler, Protocol.AirPlay: AirPlayPairingHandler, Protocol.Companion: CompanionPairingHandler, }.get(protocol) if handler is None: raise RuntimeError(f"missing implementation for {protocol}") return handler(config, await http.create_session(session), loop, **kwargs)
async def pair(config: conf.AppleTV, protocol: Protocol, loop: asyncio.AbstractEventLoop, session: aiohttp.ClientSession = None, **kwargs): """Pair a protocol for an Apple TV.""" service = config.get_service(protocol) if not service: raise exceptions.NoServiceError("no service available for protocol " + str(protocol)) handler = { Protocol.DMAP: DmapPairingHandler, Protocol.MRP: MrpPairingHandler, Protocol.AirPlay: AirPlayPairingHandler, }.get(protocol) if handler is None: raise exceptions.UnsupportedProtocolError(str(protocol)) return handler(config, await net.create_session(session), loop, **kwargs)
def setup( loop: asyncio.AbstractEventLoop, config: conf.AppleTV, interfaces: Dict[Any, Relayer], device_listener: StateProducer, session_manager: ClientSessionManager, ) -> Optional[Tuple[Callable[[], Awaitable[None]], Callable[[], None], Set[FeatureName]]]: """Set up a new Companion service.""" service = config.get_service(Protocol.Companion) assert service is not None # Companion doesn't work without credentials, so don't setup if none exists if not service.credentials: return None api = CompanionAPI(config, loop) interfaces[Apps].register(CompanionApps(api), Protocol.Companion) interfaces[Features].register( CompanionFeatures(cast(conf.CompanionService, service)), Protocol.Companion) interfaces[Power].register(CompanionPower(api), Protocol.Companion) async def _connect() -> None: pass def _close() -> None: pass return ( _connect, _close, set([ FeatureName.AppList, FeatureName.LaunchApp, FeatureName.TurnOn, FeatureName.TurnOff, ]), )
def setup( loop: asyncio.AbstractEventLoop, config: conf.AppleTV, interfaces: Dict[Any, Relayer], device_listener: StateProducer, session_manager: ClientSessionManager, ) -> Optional[Tuple[Callable[[], Awaitable[None]], Callable[[], None], Set[FeatureName]]]: """Set up a new RAOP service.""" service = config.get_service(Protocol.RAOP) assert service is not None state_manager = RaopStateManager() metadata = RaopMetadata(state_manager) push_updater = RaopPushUpdater(metadata, loop) class RaopStateListener(RaopListener): """Listener for RAOP state changes.""" def playing(self, metadata: AudioMetadata) -> None: """Media started playing with metadata.""" state_manager.metadata = metadata self._trigger() def stopped(self) -> None: """Media stopped playing.""" state_manager.metadata = None self._trigger() @staticmethod def _trigger(): """Trigger push update.""" if push_updater.active: asyncio.ensure_future(push_updater.state_updated(), loop=loop) service = cast(conf.RaopService, service) raop_listener = RaopStateListener() interfaces[Stream].register( RaopStream(str(config.address), service, raop_listener, loop), Protocol.RAOP) interfaces[Features].register(RaopFeatures(state_manager), Protocol.RAOP) interfaces[PushUpdater].register(push_updater, Protocol.RAOP) interfaces[Metadata].register(metadata, Protocol.RAOP) async def _connect() -> None: pass def _close() -> None: pass return ( _connect, _close, set([ FeatureName.StreamFile, FeatureName.PushUpdates, FeatureName.Artist, FeatureName.Album, FeatureName.Title, ]), )