@bharper/atv-js 0.2.6 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +89 -9
- package/dist/index.js.map +1 -1
- package/dist/mdns.d.ts.map +1 -1
- package/dist/mdns.js +96 -11
- package/dist/mdns.js.map +1 -1
- package/examples/print-device-json.js +22 -0
- package/package.json +2 -3
- package/pyatv/.codecov.yml +38 -0
- package/pyatv/.github/FUNDING.yml +3 -0
- package/pyatv/.github/ISSUE_TEMPLATE/bug_report.yml +80 -0
- package/pyatv/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/pyatv/.github/ISSUE_TEMPLATE/feature_request.yml +22 -0
- package/pyatv/.github/ISSUE_TEMPLATE/implementation-proposal.yml +29 -0
- package/pyatv/.github/ISSUE_TEMPLATE/investigation.yml +16 -0
- package/pyatv/.github/ISSUE_TEMPLATE/minor-change.yml +10 -0
- package/pyatv/.github/ISSUE_TEMPLATE/question-or-idea.yml +11 -0
- package/pyatv/.github/dependabot.yml +26 -0
- package/pyatv/.github/workflows/codeql-analysis.yml +71 -0
- package/pyatv/.github/workflows/release.yml +160 -0
- package/pyatv/.github/workflows/tests.yml +104 -0
- package/pyatv/.gitpod.yml +23 -0
- package/pyatv/CHANGES.md +3708 -0
- package/pyatv/CODE_OF_CONDUCT.md +76 -0
- package/pyatv/CONTRIBUTING.md +72 -0
- package/pyatv/CONTRIBUTORS.md +3 -0
- package/pyatv/Dockerfile +15 -0
- package/pyatv/LICENSE.md +9 -0
- package/pyatv/MANIFEST.in +14 -0
- package/pyatv/README.md +111 -0
- package/pyatv/base_versions.txt +13 -0
- package/pyatv/chickn.yaml +75 -0
- package/pyatv/docs/404.html +24 -0
- package/pyatv/docs/CNAME +1 -0
- package/pyatv/docs/Gemfile +31 -0
- package/pyatv/docs/_config.yml +121 -0
- package/pyatv/docs/_includes/api +10 -0
- package/pyatv/docs/_includes/atvremote_scan +32 -0
- package/pyatv/docs/_includes/code +6 -0
- package/pyatv/docs/_includes/issue +14 -0
- package/pyatv/docs/_includes/pypi +5 -0
- package/pyatv/docs/_layouts/template.html +109 -0
- package/pyatv/docs/api/pyatv.conf.html +312 -0
- package/pyatv/docs/api/pyatv.const.html +974 -0
- package/pyatv/docs/api/pyatv.convert.html +106 -0
- package/pyatv/docs/api/pyatv.exceptions.html +489 -0
- package/pyatv/docs/api/pyatv.helpers.html +102 -0
- package/pyatv/docs/api/pyatv.html +120 -0
- package/pyatv/docs/api/pyatv.interface.html +2369 -0
- package/pyatv/docs/api/pyatv.settings.html +484 -0
- package/pyatv/docs/api/pyatv.storage.file_storage.html +102 -0
- package/pyatv/docs/api/pyatv.storage.html +186 -0
- package/pyatv/docs/api/pyatv.storage.memory_storage.html +83 -0
- package/pyatv/docs/assets/css/custom.css +19 -0
- package/pyatv/docs/assets/css/hljs.css +1 -0
- package/pyatv/docs/assets/css/normalize.css +349 -0
- package/pyatv/docs/assets/css/pdoc.css +287 -0
- package/pyatv/docs/assets/css/sanitize.css +566 -0
- package/pyatv/docs/assets/css/style.scss +9 -0
- package/pyatv/docs/assets/img/logo.svg +63 -0
- package/pyatv/docs/assets/js/highlight.9.12.0.min.js +3 -0
- package/pyatv/docs/assets/js/mermaid.8.9.2.min.js +32 -0
- package/pyatv/docs/assets/js/mermaid.min.js.map +1 -0
- package/pyatv/docs/development/apps.md +81 -0
- package/pyatv/docs/development/audio.md +42 -0
- package/pyatv/docs/development/control.md +56 -0
- package/pyatv/docs/development/development.md +15 -0
- package/pyatv/docs/development/device_info.md +36 -0
- package/pyatv/docs/development/examples.md +44 -0
- package/pyatv/docs/development/features.md +70 -0
- package/pyatv/docs/development/keyboard.md +51 -0
- package/pyatv/docs/development/listeners.md +144 -0
- package/pyatv/docs/development/logging.md +55 -0
- package/pyatv/docs/development/metadata.md +115 -0
- package/pyatv/docs/development/power_management.md +53 -0
- package/pyatv/docs/development/scan_pair_and_connect.md +331 -0
- package/pyatv/docs/development/services.md +9 -0
- package/pyatv/docs/development/storage.md +259 -0
- package/pyatv/docs/development/stream.md +241 -0
- package/pyatv/docs/development/testing.md +9 -0
- package/pyatv/docs/documentation/atvlog.md +64 -0
- package/pyatv/docs/documentation/atvproxy.md +244 -0
- package/pyatv/docs/documentation/atvremote.md +639 -0
- package/pyatv/docs/documentation/atvscript.md +275 -0
- package/pyatv/docs/documentation/concepts.md +168 -0
- package/pyatv/docs/documentation/documentation.md +130 -0
- package/pyatv/docs/documentation/getting_started.md +248 -0
- package/pyatv/docs/documentation/protocols.md +1959 -0
- package/pyatv/docs/documentation/supported_features.md +246 -0
- package/pyatv/docs/documentation/tutorial.md +1062 -0
- package/pyatv/docs/documentation/workspace.code-workspace +7 -0
- package/pyatv/docs/favicon.ico +0 -0
- package/pyatv/docs/index.md +109 -0
- package/pyatv/docs/internals/design.md +354 -0
- package/pyatv/docs/internals/documentation.md +84 -0
- package/pyatv/docs/internals/interfaces.md +95 -0
- package/pyatv/docs/internals/internals.md +157 -0
- package/pyatv/docs/internals/submit_pr.md +56 -0
- package/pyatv/docs/internals/testing.md +176 -0
- package/pyatv/docs/internals/tools.md +574 -0
- package/pyatv/docs/pdoc_templates/config.mako +46 -0
- package/pyatv/docs/pdoc_templates/html.mako +454 -0
- package/pyatv/docs/support/acknowledgements.md +87 -0
- package/pyatv/docs/support/faq.md +214 -0
- package/pyatv/docs/support/migration.md +138 -0
- package/pyatv/docs/support/scanning_issues.md +110 -0
- package/pyatv/docs/support/support.md +18 -0
- package/pyatv/docs/support/troubleshooting.md +83 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFadeMessage.proto +13 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFadeMessage_pb2.pyi +37 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFadeResponseMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFadeResponseMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFormatSettingsMessage.proto +5 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/AudioFormatSettingsMessage_pb2.pyi +27 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ClientUpdatesConfigMessage.proto +16 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ClientUpdatesConfigMessage_pb2.pyi +44 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CommandInfo.proto +117 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CommandInfo_pb2.pyi +325 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CommandOptions.proto +36 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CommandOptions_pb2.pyi +115 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/Common.proto +79 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/Common_pb2.pyi +228 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ConfigureConnectionMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ConfigureConnectionMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ContentItem.proto +27 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ContentItemMetadata.proto +213 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ContentItemMetadata_pb2.pyi +630 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ContentItem_pb2.pyi +94 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CryptoPairingMessage.proto +15 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/CryptoPairingMessage_pb2.pyi +46 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/DeviceInfoMessage.proto +69 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/DeviceInfoMessage_pb2.pyi +226 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GenericMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GenericMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetKeyboardSessionMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetKeyboardSessionMessage_pb2.pyi +26 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetRemoteTextInputSessionMessage.proto +10 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetRemoteTextInputSessionMessage_pb2.pyi +26 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetVolumeMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetVolumeMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetVolumeResultMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/GetVolumeResultMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/KeyboardMessage.proto +88 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/KeyboardMessage_pb2.pyi +261 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/LanguageOption.proto +9 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/LanguageOption_pb2.pyi +42 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ModifyOutputContextRequestMessage.proto +23 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ModifyOutputContextRequestMessage_pb2.pyi +86 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NotificationMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NotificationMessage_pb2.pyi +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingClient.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingClient_pb2.pyi +49 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingInfo.proto +24 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingInfo_pb2.pyi +79 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingPlayer.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/NowPlayingPlayer_pb2.pyi +45 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/Origin.proto +17 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/OriginClientPropertiesMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/OriginClientPropertiesMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/Origin_pb2.pyi +63 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueue.proto +15 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueCapabilities.proto +7 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueCapabilities_pb2.pyi +33 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueContext.proto +5 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueContext_pb2.pyi +27 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueRequestMessage.proto +29 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueueRequestMessage_pb2.pyi +87 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlaybackQueue_pb2.pyi +53 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlayerClientPropertiesMessage.proto +13 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlayerClientPropertiesMessage_pb2.pyi +37 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlayerPath.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/PlayerPath_pb2.pyi +39 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ProtocolMessage.proto +171 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/ProtocolMessage_pb2.pyi +377 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterForGameControllerEventsMessage.proto +18 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterForGameControllerEventsMessage_pb2.pyi +54 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterHIDDeviceMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterHIDDeviceMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterHIDDeviceResultMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterHIDDeviceResultMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterVoiceInputDeviceMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterVoiceInputDeviceMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterVoiceInputDeviceResponseMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RegisterVoiceInputDeviceResponseMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoteTextInputMessage.proto +13 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoteTextInputMessage_pb2.pyi +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveClientMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveClientMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveEndpointsMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveEndpointsMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveOutputDevicesMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemoveOutputDevicesMessage_pb2.pyi +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemovePlayerMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/RemovePlayerMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendButtonEventMessage.proto +13 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendButtonEventMessage_pb2.pyi +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendCommandMessage.proto +16 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendCommandMessage_pb2.pyi +43 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendCommandResultMessage.proto +100 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendCommandResultMessage_pb2.pyi +286 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendHIDEventMessage.proto +41 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendHIDEventMessage_pb2.pyi +63 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendPackedVirtualTouchEventMessage.proto +24 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendPackedVirtualTouchEventMessage_pb2.pyi +64 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendVoiceInputMessage.proto +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SendVoiceInputMessage_pb2.pyi +134 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetArtworkMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetArtworkMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetConnectionStateMessage.proto +18 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetConnectionStateMessage_pb2.pyi +54 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetDefaultSupportedCommandsMessage.proto +28 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetDefaultSupportedCommandsMessage_pb2.pyi +74 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetDiscoveryModeMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetDiscoveryModeMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetHiliteModeMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetHiliteModeMessage_pb2.pyi +32 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetNowPlayingClientMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetNowPlayingClientMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetNowPlayingPlayerMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetNowPlayingPlayerMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetRecordingStateMessage.proto +17 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetRecordingStateMessage_pb2.pyi +54 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetStateMessage.proto +27 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetStateMessage_pb2.pyi +72 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetVolumeMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SetVolumeMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SupportedCommands.proto +7 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/SupportedCommands_pb2.pyi +30 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TextInputMessage.proto +23 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TextInputMessage_pb2.pyi +76 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionKey.proto +6 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionKey_pb2.pyi +30 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionMessage.proto +15 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionMessage_pb2.pyi +42 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionPacket.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionPacket_pb2.pyi +41 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionPackets.proto +7 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/TransactionPackets_pb2.pyi +30 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateClientMessage.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateClientMessage_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateContentItemArtworkMessage.proto +14 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateContentItemArtworkMessage_pb2.pyi +41 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateContentItemMessage.proto +14 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateContentItemMessage_pb2.pyi +41 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateEndPointsMessage.proto +25 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateEndPointsMessage_pb2.pyi +74 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateOutputDeviceMessage.proto +88 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdateOutputDeviceMessage_pb2.pyi +277 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdatePlayerPath.proto +12 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/UpdatePlayerPath_pb2.pyi +34 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VirtualTouchDeviceDescriptorMessage.proto +8 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VirtualTouchDeviceDescriptorMessage_pb2.pyi +36 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VoiceInputDeviceDescriptorMessage.proto +8 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VoiceInputDeviceDescriptorMessage_pb2.pyi +35 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeControlAvailabilityMessage.proto +23 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeControlAvailabilityMessage_pb2.pyi +71 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeControlCapabilitiesDidChangeMessage.proto +14 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeControlCapabilitiesDidChangeMessage_pb2.pyi +40 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeDidChangeMessage.proto +13 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/VolumeDidChangeMessage_pb2.pyi +38 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/WakeDeviceMessage.proto +11 -0
- package/pyatv/pyatv/protocols/mrp/protobuf/WakeDeviceMessage_pb2.pyi +26 -0
- package/pyatv/pyatv/py.typed +0 -0
- package/pyatv/pylintrc +49 -0
- package/pyatv/pyproject.toml +74 -0
- package/pyatv/requirements/requirements.txt +14 -0
- package/pyatv/requirements/requirements_docs.txt +2 -0
- package/pyatv/requirements/requirements_test.txt +20 -0
- package/pyatv/scripts/build_docs.sh +17 -0
- package/pyatv/scripts/setup_dev_env.sh +83 -0
- package/pyatv/setup.cfg +14 -0
- package/pyatv/tests/data/README +23 -0
- package/pyatv/tests/data/audio_10_frames.wav +0 -0
- package/pyatv/tests/data/audio_1_packet_metadata.wav +0 -0
- package/pyatv/tests/data/audio_3_packets.wav +0 -0
- package/pyatv/tests/data/only_metadata.wav +0 -0
- package/pyatv/tests/data/only_title.wav +0 -0
- package/pyatv/tests/data/static_3sec.ogg +0 -0
- package/pyatv/tests/data/testfile.txt +1 -0
- package/pyatv/tests/support/pyatv.code-workspace +14 -0
- package/src/index.ts +122 -8
- package/src/mdns.ts +64 -11
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: pyatv
|
|
4
|
+
---
|
|
5
|
+
# :tv: Main Page
|
|
6
|
+
|
|
7
|
+
This is an asyncio python library for interacting with Apple TV and AirPlay devices. It mainly
|
|
8
|
+
targets Apple TVs (all generations), but also support audio streaming via AirPlay to receivers like the HomePod,
|
|
9
|
+
AirPort Express and third-party speakers. It can act as remote control to the Music app/iTunes in macOS.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
[](https://codecov.io/gh/postlund/pyatv)
|
|
13
|
+
[](https://github.com/psf/black)
|
|
14
|
+
[](https://badge.fury.io/py/pyatv)
|
|
15
|
+
[](https://lgtm.com/projects/g/postlund/pyatv/context:python)
|
|
16
|
+
[](https://gitpod.io/#https://github.com/postlund/pyatv)
|
|
17
|
+
[](https://pepy.tech/project/pyatv)
|
|
18
|
+
[](https://pypi.python.org/pypi/pyatv/)
|
|
19
|
+
[](https://opensource.org/licenses/MIT)
|
|
20
|
+
|
|
21
|
+
# :satisfied: Features
|
|
22
|
+
|
|
23
|
+
Here is a short summary of supported features:
|
|
24
|
+
|
|
25
|
+
* Automatic device discovery with Zeroconf
|
|
26
|
+
* Device information, e.g. hardware model and operating system version
|
|
27
|
+
* Currently playing metadata, artwork and push updates
|
|
28
|
+
* Remote, navigation and volume control commands
|
|
29
|
+
* Basic support for streaming video and audio with AirPlay
|
|
30
|
+
* Listing installed apps, launching apps and currently playing app
|
|
31
|
+
* Power management, e.g. turn on or off
|
|
32
|
+
* Supports Apple TV (all of them), AirPort Express, HomePod, macOS music app and most AirPlay v1 receivers
|
|
33
|
+
* Persistent storage of credentials and settings, e.g. to file or custom built storage
|
|
34
|
+
|
|
35
|
+
A complete list of supported features and limitations is available
|
|
36
|
+
[here](documentation/supported_features).
|
|
37
|
+
|
|
38
|
+
There are also few utility scripts bundled with pyatv that makes it easy to try the library
|
|
39
|
+
out. Check out [atvremote](documentation/atvremote), [atvproxy](documentation/atvproxy),
|
|
40
|
+
[atvscript](documentation/atvscript) and [atvlog](documentation/atvlog).
|
|
41
|
+
|
|
42
|
+
# :eyes: Where to start?
|
|
43
|
+
|
|
44
|
+
To get going, install with `pip`:
|
|
45
|
+
|
|
46
|
+
<div class="center_box" style="margin-bottom: 2em">
|
|
47
|
+
<p style="margin: 0px">:tada: <a href="https://pypi.org/project/pyatv">pip install pyatv :tada:</a></p>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
Head over to [Getting started](documentation/getting-started) to see what you can do! There's
|
|
51
|
+
also a [Tutorial](documentation/tutorial) if you want to get going faster!
|
|
52
|
+
|
|
53
|
+
As pyatv is a library, it is mainly aimed for developers creating applications that can interact
|
|
54
|
+
with Apple TVs. However, pyatv ships with a few powerful command lines tools you can use to
|
|
55
|
+
try the library without writing any code.
|
|
56
|
+
|
|
57
|
+
If you need help or have questions, check out the [Support](support) page instead.
|
|
58
|
+
|
|
59
|
+
In case you are upgrading from an earlier version of pyatv, make sure to check out the migration
|
|
60
|
+
guide [here](support/migration) that will help you port your existing code.
|
|
61
|
+
|
|
62
|
+
# :cloud: In other the news...
|
|
63
|
+
|
|
64
|
+
As pyatv depends solely on private and reverse engineered protocols, things sometimes break because
|
|
65
|
+
Apple changes something. Or because of other reasons. This section covers the major things that you
|
|
66
|
+
need to be aware of.
|
|
67
|
+
|
|
68
|
+
* As of tvOS 15, the Media Remote Protocol (MRP) is tunneled over AirPlay 2. Support for this was
|
|
69
|
+
introduced in version 0.9.0 of pyatv, so be sure to use a later version than that.
|
|
70
|
+
* Support for {% include api i="interface.Stream.play_url" %} on tvOS was restored in version 0.13.3.
|
|
71
|
+
* It is possible to control the Music app running on a Mac, but macOS 11.4 seems to not work.
|
|
72
|
+
* If you have problems with {% include pypi package="miniaudio" %} on ARM (e.g. Rasperry Pi), try
|
|
73
|
+
re-installing {% include pypi package="miniaudio" %} and building it from source. See [here](support/faq#when-using-pyatv-on-a-raspberry-pi-eg-running-atvremote-i-get-illegal-instruction-how-do-i-fix-that)
|
|
74
|
+
for details.
|
|
75
|
+
* Other general issues can be found in the [FAQ](support/faq).
|
|
76
|
+
|
|
77
|
+
# :trophy: Who uses pyatv?
|
|
78
|
+
|
|
79
|
+
Here are a few projects known to use pyatv:
|
|
80
|
+
|
|
81
|
+
* [Home Assistant](https://home-assistant.io) - The Apple TV integration is powered by pyatv
|
|
82
|
+
* [node-pyatv](https://github.com/sebbo2002/node-pyatv) - Node.Js binding built using pyatv
|
|
83
|
+
* [pyatv-mqtt-bridge](https://github.com/sebbo2002/pyatv-mqtt-bridge) - MQTT Bridge allows you to remote control your Apple TV using the MQTT protocol (built using node-pyatv)
|
|
84
|
+
* [homebridge-appletv-enhanced](https://github.com/maxileith/homebridge-appletv-enhanced#homebridge-appletv-enhanced) - Homebridge plugin that is providing functionality that should be native to HomeKit
|
|
85
|
+
* [Indigo Domotics Plugin](https://github.com/kw123/appleTV) - Plugin to Indigo Domotics
|
|
86
|
+
* [iSponsorBlockTV](https://github.com/dmunozv04/iSponsorBlockTV) - Skip sponsor segments in YouTube videos playing on an Apple TV
|
|
87
|
+
* [homebridge-homepod-radio](https://github.com/petro-kushchak/homebridge-homepod-radio) - Homebridge accessory for streaming radio to Homepod mini
|
|
88
|
+
* [node-red-contrib-apple-tv-x](https://github.com/twocolors/node-red-contrib-apple-tv-x) - Apple TV control from inside Node-RED
|
|
89
|
+
* [c4-pyatv-remote](https://github.com/13mralex/c4-pyatv-remote) - Control4 remote control integration
|
|
90
|
+
* [atv-desktop-remote](https://github.com/bsharper/atv-desktop-remote) - Desktop remote control for Apple TV
|
|
91
|
+
|
|
92
|
+
If you are maintaining a project using pyatv, feel free to add it to the list (open a PR
|
|
93
|
+
or [issue](https://github.com/postlund/pyatv/issues/new?assignees=&labels=question,documentation&template=question-or-idea.md&title=Add+my+pyatv+project+to+list&assignees=postlund)).
|
|
94
|
+
You don't need to provide a URL if you don't want, just a short description of the use case
|
|
95
|
+
is fine too!
|
|
96
|
+
|
|
97
|
+
# :office: License
|
|
98
|
+
|
|
99
|
+
This library is licensed under the
|
|
100
|
+
[MIT license](https://github.com/postlund/pyatv/blob/master/LICENSE.md).
|
|
101
|
+
|
|
102
|
+
# :person_with_blond_hair: Who is making this?
|
|
103
|
+
|
|
104
|
+
I, Pierre Ståhl, is the lead developer and maintainer of this library. It is a hobby
|
|
105
|
+
project that I put a few hours in every now and then to maintain. If you find it useful,
|
|
106
|
+
please consider to sponsor me! :heart:
|
|
107
|
+
|
|
108
|
+
Of course, this is an open source project which means I couldn't do it all by myself.
|
|
109
|
+
I have created dedicated page for [acknowledgements](support/acknowledgements)!
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: Design
|
|
4
|
+
permalink: /internals/design
|
|
5
|
+
link_group: internals
|
|
6
|
+
---
|
|
7
|
+
# :construction: Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# Design
|
|
13
|
+
|
|
14
|
+
This page gives a brief introduction to the architecture and design of pyatv.
|
|
15
|
+
|
|
16
|
+
*NB: This page is far from complete and under development. Please let me know if you
|
|
17
|
+
want any part of pyatv explained further, so I can add it here.*
|
|
18
|
+
|
|
19
|
+
# General Topics
|
|
20
|
+
|
|
21
|
+
This section contains a few topics covering general design concepts, not going into any
|
|
22
|
+
protocol specific details.
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
A *configuration* is a general description of a device and is represented by an instance of
|
|
27
|
+
{% include api i="conf.AppleTV" %}. The easiest and best way of obtaining a configuration is
|
|
28
|
+
{% include api i="pyatv.scan" %}, as a list of configurations are returned. Both when pairing
|
|
29
|
+
and connecting, a configuration is needed.
|
|
30
|
+
|
|
31
|
+
The configuration instance stores general information about the device, like IP address,
|
|
32
|
+
name and various other properties used by the device info interface. It also stores a list
|
|
33
|
+
of services associated with the device, i.e. the protocols it supports. A simple overview:
|
|
34
|
+
|
|
35
|
+
<code class="diagram">
|
|
36
|
+
classDiagram
|
|
37
|
+
class AppleTV
|
|
38
|
+
AppleTV : str address
|
|
39
|
+
AppleTV : int port
|
|
40
|
+
AppleTV : ...
|
|
41
|
+
class AirPlayService
|
|
42
|
+
AirPlayService : str identifier
|
|
43
|
+
AirPlayService : int port
|
|
44
|
+
AirPlayService : ...
|
|
45
|
+
class CompanionService
|
|
46
|
+
CompanionService : int port
|
|
47
|
+
CompanionService : ...
|
|
48
|
+
class DmapService
|
|
49
|
+
DmapService : str identifier
|
|
50
|
+
DmapService : int port
|
|
51
|
+
DmapService : ...
|
|
52
|
+
class MrpService
|
|
53
|
+
MrpService : str identifier
|
|
54
|
+
MrpService : int port
|
|
55
|
+
MrpService : ...
|
|
56
|
+
class RaopService
|
|
57
|
+
RaopService : str identifier
|
|
58
|
+
RaopService : int port
|
|
59
|
+
RaopService : ...
|
|
60
|
+
AppleTV --* AirPlayService
|
|
61
|
+
AppleTV --* CompanionService
|
|
62
|
+
AppleTV --* DmapService
|
|
63
|
+
AppleTV --* MrpService
|
|
64
|
+
AppleTV --* RaopService
|
|
65
|
+
</code>
|
|
66
|
+
|
|
67
|
+
## Scanning
|
|
68
|
+
|
|
69
|
+
Scanning can be performed in one of two ways: unicast or multicast. The different methods are
|
|
70
|
+
implemented as separate scanners in {% include code file="support/scan.py" %}:
|
|
71
|
+
|
|
72
|
+
<code class="diagram">
|
|
73
|
+
graph TD
|
|
74
|
+
A[User] -->|hosts=X| B(pyatv.scan)
|
|
75
|
+
B -->|X=None| C[MulticastMdnsScanner]
|
|
76
|
+
B -->|X=10.0.0.2,10.0.0.3| D[UnicastMdnsScanner]
|
|
77
|
+
</code>
|
|
78
|
+
|
|
79
|
+
**UnicastMdnsScanner:** Sends zeroconf requests directly to one or more hosts as specified via the *hosts* argument.
|
|
80
|
+
|
|
81
|
+
**MulticastMdnsScanner:** Uses multicast and sends requests to all hosts on the network.
|
|
82
|
+
|
|
83
|
+
Each request contains a list of all the services that pyatv are interested in, i.e. services
|
|
84
|
+
used by the implemented protocols. Each protocol must implement a `scan` method returning
|
|
85
|
+
which Zeroconf services it needs as well as handlers that are called when a service is found.
|
|
86
|
+
An example from Companion looks like this:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
def companion_service_handler(
|
|
90
|
+
mdns_service: mdns.Service, response: mdns.Response
|
|
91
|
+
) -> ScanHandlerReturn:
|
|
92
|
+
"""Parse and return a new Companion service."""
|
|
93
|
+
service = conf.CompanionService(
|
|
94
|
+
mdns_service.port,
|
|
95
|
+
properties=mdns_service.properties,
|
|
96
|
+
)
|
|
97
|
+
return mdns_service.name, service
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def scan() -> Mapping[str, ScanHandler]:
|
|
101
|
+
"""Return handlers used for scanning."""
|
|
102
|
+
return {"_companion-link._tcp.local": companion_service_handler}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Whenever a service with type `_companion-link._tcp.local` is found, the function/handler
|
|
106
|
+
`companion_service_handler` is called. Device name and a {% include api i="interface.BaseService" %}
|
|
107
|
+
representing the service is returned and added to the final device configuration.
|
|
108
|
+
|
|
109
|
+
Both unicast (which is a pyatv specific term) and multicast scanning uses a homegrown
|
|
110
|
+
implementation of Zeroconf instead of relying on a third party. One exception however
|
|
111
|
+
is when publishing new services on the network. In that case
|
|
112
|
+
[python-zeroconf](https://github.com/jstasiak/python-zeroconf) is used. Prior to version
|
|
113
|
+
0.7.0 of pyatv, python-zerconf would also be used for scanning but some limitations in
|
|
114
|
+
the library drove a new implementation. Namely these:
|
|
115
|
+
|
|
116
|
+
* No other library seems to support sending requests to a specific host, but only allow
|
|
117
|
+
multicast. Unicast was added as an alternative method of scanning for people experiencing
|
|
118
|
+
problems with multicast. It's a very special case but works quite well.
|
|
119
|
+
* Not possible to request more than one service at the time in a request. One device in
|
|
120
|
+
pyatv generally depend on service data from more than one service (e.g. MRP and AirPlay).
|
|
121
|
+
Using python-zeroconf, pyatv needed to subscribe to each service independently and await
|
|
122
|
+
all responses. It's not really possible to know when to stop waiting as one cannot know
|
|
123
|
+
how many services to wait for. The implementation in pyatv supports requesting all the
|
|
124
|
+
relevant services at the same time, yielding one response with all service data.
|
|
125
|
+
It means less traffic, more accurate scanning and less waiting.
|
|
126
|
+
* Response messages contains a special entry (`_device-info._tcp.local`) which isn't a pure
|
|
127
|
+
service, so it's not possible to subscribe to. It contains the device model, used under
|
|
128
|
+
some circumstances to derive hardware model. Other libraries does not allow access
|
|
129
|
+
to this entry, basically ignoring it, so device info would be lost.
|
|
130
|
+
|
|
131
|
+
The Zeroconf implementation is in {% include code file="support/mdns.py" %} and some helper
|
|
132
|
+
routines for DNS in {% include code file="support/dns.py" %}.
|
|
133
|
+
|
|
134
|
+
## Connecting
|
|
135
|
+
|
|
136
|
+
When connecting to a device, an instance of {% include api i="interface.AppleTV" %} is created.
|
|
137
|
+
This section describes the basics of how that is done.
|
|
138
|
+
|
|
139
|
+
### Set up of a protocol instance
|
|
140
|
+
|
|
141
|
+
When connecting to a device, each service is used to set up a new protocol. This will
|
|
142
|
+
create instances of all interface implementations, register them with it's corresponding
|
|
143
|
+
relayer (see next section) and connect (if needed by the protocol). Each protocol must
|
|
144
|
+
implement a `setup` method and add that to the list in
|
|
145
|
+
{% include code file="__init__.py" %} for this to work.
|
|
146
|
+
|
|
147
|
+
A simple example of a setup method looks like this:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
def setup(
|
|
151
|
+
loop: asyncio.AbstractEventLoop,
|
|
152
|
+
config: conf.AppleTV,
|
|
153
|
+
interfaces: Dict[Any, Relayer],
|
|
154
|
+
device_listener: StateProducer,
|
|
155
|
+
session_manager: ClientSessionManager,
|
|
156
|
+
) -> Generator[SetupData, None, None]:
|
|
157
|
+
# Service information for current protocol
|
|
158
|
+
service = config.get_service(Protocol.XXX)
|
|
159
|
+
|
|
160
|
+
# Create any protocol specific things here
|
|
161
|
+
protocol = DummyProtocol()
|
|
162
|
+
|
|
163
|
+
# Register interfaces with corresponding relayers
|
|
164
|
+
interfaces = {
|
|
165
|
+
RemoteControl: DummyRemoteControl(protocol)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
# Called for all protocols _after_ setup has been called for all protocols
|
|
169
|
+
async def _connect() -> bool:
|
|
170
|
+
await protocol.start()
|
|
171
|
+
return True # Connect succeeded
|
|
172
|
+
|
|
173
|
+
# Called when closing the device connection
|
|
174
|
+
def _close() -> Set[asyncio.Task]:
|
|
175
|
+
protocol.stop()
|
|
176
|
+
return set() # Tasks thas has not yet finished
|
|
177
|
+
|
|
178
|
+
# Yield connect handler, close handler and a set with _all_ features supported
|
|
179
|
+
# by the protocol.
|
|
180
|
+
yield SetupData(Protocol.XXX, _connect, _close, interfaces, set([FeatureName.Play]))
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The `_connect` and `close` methods will be called by the facade object when connecting
|
|
184
|
+
or disconnecting. For the feature interface to work properly, each protocol must yield
|
|
185
|
+
which features they support. This is used internally in the features implementation in
|
|
186
|
+
the facade to know if a protocol implements a certain feature or not.
|
|
187
|
+
|
|
188
|
+
A protocol can yield as many protocol implementations as they want, even protocols of
|
|
189
|
+
a different kind. This is to support the use case where one protocol is tunneled over
|
|
190
|
+
another protocol, for instance how MRP is carried over a stream in AirPlay 2.
|
|
191
|
+
|
|
192
|
+
### Relaying
|
|
193
|
+
|
|
194
|
+
The general idea of the {% include code file="support/relayer.py" %} module is to allow
|
|
195
|
+
protocols to only implement parts of an interface as well as allowing multiple protocols
|
|
196
|
+
to implement the same interfaces, but still provide meaningful output to the user. This
|
|
197
|
+
is accomplished in two ways:
|
|
198
|
+
|
|
199
|
+
* There's a built-in priority amongst protocols. If several protocols implement the same
|
|
200
|
+
functionality, the implementation of the protocol with highest priority is picked. The
|
|
201
|
+
general priority is MRP, DMAP, Companion, AirPlay and RAOP.
|
|
202
|
+
* The relayer verifies if a protocol has actually provided an implementation of a
|
|
203
|
+
particular member before relaying, ignoring it otherwise.
|
|
204
|
+
|
|
205
|
+
One relayer is responsible for one interface only. This means that several realyers must
|
|
206
|
+
be used to support all the interfaces in pyatv. The facade implementation described below
|
|
207
|
+
keeps track of all those relayers.
|
|
208
|
+
|
|
209
|
+
A typical example of a relayer instance might look like this:
|
|
210
|
+
|
|
211
|
+
<code class="diagram">
|
|
212
|
+
classDiagram
|
|
213
|
+
class Relayer
|
|
214
|
+
Relayer : relay(target, priority)
|
|
215
|
+
class MrpPower
|
|
216
|
+
MrpPower : PowerState power_state
|
|
217
|
+
MrpPower : turn_on(await_new_state)
|
|
218
|
+
MrpPower : turn_off(await_new_state)
|
|
219
|
+
class CompanionPower
|
|
220
|
+
CompanionPower : turn_on(await_new_state)
|
|
221
|
+
CompanionPower : turn_off(await_new_state)
|
|
222
|
+
Relayer --> MrpPower
|
|
223
|
+
Relayer --> CompanionPower
|
|
224
|
+
</code>
|
|
225
|
+
|
|
226
|
+
Here, only `MrpPower` implements {% include api i="interface.Power.power_state" %},
|
|
227
|
+
making the relayer always return the value from the MRP implementation. The remaining
|
|
228
|
+
methods are implemented by both instances, leaving the choice to priority. A relayer
|
|
229
|
+
always has a pre-defined priority list from when it was created (general priority list
|
|
230
|
+
mentioned above), but it's also possible to override the internal priority list when
|
|
231
|
+
calling the `relay` method. This makes it possible to deal with special cases, where
|
|
232
|
+
one protocol with lower priority provides a better implementation than one with
|
|
233
|
+
higher priority. The power interface is one such example, where the Companion
|
|
234
|
+
implementation is better than MRP (even though MRP has higher general priority than
|
|
235
|
+
Companion).
|
|
236
|
+
|
|
237
|
+
### Facade
|
|
238
|
+
The "facade" implements {% include api i="interface.AppleTV" %} as well as all
|
|
239
|
+
interfaces belonging to it. One relayer is allocated per interface and protocols
|
|
240
|
+
register instances of interfaces they implement during the setup phase (when
|
|
241
|
+
connecting). An example with some of the interfaces looks like this:
|
|
242
|
+
|
|
243
|
+
<code class="diagram">
|
|
244
|
+
graph TD
|
|
245
|
+
AppleTV -->|interface.AppleTV| FacadeAppleTV
|
|
246
|
+
FacadeAppleTV -->|interface.Power|PowerRelayer[Relayer]
|
|
247
|
+
FacadeAppleTV -->|interface.Audio|AudioRelayer[Relayer]
|
|
248
|
+
FacadeAppleTV -->|interface.Apps|AppsRelayer[Relayer]
|
|
249
|
+
FacadeAppleTV -->|interface.Remotecontrol|RCRelayer[Relayer]
|
|
250
|
+
PowerRelayer --> CompanionPower
|
|
251
|
+
PowerRelayer --> MrpPower
|
|
252
|
+
AudioRelayer --> RaopAudio
|
|
253
|
+
AppsRelayer --> CompanionApps
|
|
254
|
+
RCRelayer --> DmapRemoteControl
|
|
255
|
+
RCRelayer --> MrpRemoteControl
|
|
256
|
+
RCRelayer --> RaopRemoteControl
|
|
257
|
+
</code>
|
|
258
|
+
|
|
259
|
+
From a user point of view, all interaction occurs with the facade object which
|
|
260
|
+
relays calls to the most appropriate protocol instance. A typical interface
|
|
261
|
+
implementation looks like this:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
class FacadeApps(Relayer, interface.Apps):
|
|
265
|
+
|
|
266
|
+
def __init__(self):
|
|
267
|
+
super().__init__(interface.Apps, DEFAULT_PRIORITIES)
|
|
268
|
+
|
|
269
|
+
async def app_list(self) -> List[interface.App]:
|
|
270
|
+
return await self.relay("app_list")()
|
|
271
|
+
|
|
272
|
+
async def launch_app(self, bundle_id: str) -> None:
|
|
273
|
+
await self.relay("launch_app")(bundle_id)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Calls are relayed and potentially returning a value. As described in the relayer
|
|
277
|
+
section above, the priority rule is generally used to determine which instance is called.
|
|
278
|
+
But the facade can side-step this rule and implement it's own logic when deemed necessary.
|
|
279
|
+
One example is the power implementation (short version):
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
class FacadePower(Relayer, interface.Power, interface.PowerListener):
|
|
283
|
+
OVERRIDE_PRIORITIES = [
|
|
284
|
+
Protocol.Companion,
|
|
285
|
+
Protocol.MRP,
|
|
286
|
+
Protocol.DMAP,
|
|
287
|
+
Protocol.AirPlay,
|
|
288
|
+
Protocol.RAOP,
|
|
289
|
+
]
|
|
290
|
+
|
|
291
|
+
...
|
|
292
|
+
|
|
293
|
+
async def turn_on(self, await_new_state: bool = False) -> None:
|
|
294
|
+
await self.relay("turn_on", priority=self.OVERRIDE_PRIORITIES)(
|
|
295
|
+
await_new_state=await_new_state
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Here, the priority list is overridden for {% include api i="interface.Power.turn_on" %}
|
|
301
|
+
(and {% include api i="interface.Power.turn_off" %}). Another example is the audio
|
|
302
|
+
interface:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
class FacadeAudio(Relayer, interface.Audio):
|
|
306
|
+
def __init__(self):
|
|
307
|
+
super().__init__(interface.Audio, DEFAULT_PRIORITIES)
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def volume(self) -> float:
|
|
311
|
+
volume = self.relay("volume")
|
|
312
|
+
if 0.0 <= volume <= 100.0:
|
|
313
|
+
return volume
|
|
314
|
+
raise exceptions.ProtocolError(f"volume {volume} is out of range")
|
|
315
|
+
|
|
316
|
+
async def set_volume(self, level: float) -> None:
|
|
317
|
+
if 0.0 <= level <= 100.0:
|
|
318
|
+
await self.relay("set_volume")(level)
|
|
319
|
+
else:
|
|
320
|
+
raise exceptions.ProtocolError(f"volume {level} is out of range")
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
In this case, the facade will guard that an invalid audio level is passed over to the
|
|
324
|
+
protocol implementation (i.e. must not be checked there) as well as the value
|
|
325
|
+
returned from the protocol.
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
### General sequence of connecting
|
|
329
|
+
Here's a very rough diagram of what happens during a connect call:
|
|
330
|
+
|
|
331
|
+
<code class="diagram">
|
|
332
|
+
sequenceDiagram
|
|
333
|
+
autonumber
|
|
334
|
+
participant User
|
|
335
|
+
participant pyatv
|
|
336
|
+
participant Protocol
|
|
337
|
+
participant Facade
|
|
338
|
+
User ->> pyatv: connect(config)
|
|
339
|
+
loop For each service in config
|
|
340
|
+
pyatv ->> Protocol: setup
|
|
341
|
+
Protocol ->> pyatv: protocol, connect, close, interfaces, feature list
|
|
342
|
+
pyatv ->> Facade: register interfaces
|
|
343
|
+
end
|
|
344
|
+
pyatv ->> Facade: connect
|
|
345
|
+
loop For each protocol
|
|
346
|
+
Facade ->> Protocol: connect
|
|
347
|
+
end
|
|
348
|
+
pyatv ->> User: facade instance
|
|
349
|
+
note over User: Use returned instance
|
|
350
|
+
User ->> Facade: close
|
|
351
|
+
loop For each protocol
|
|
352
|
+
Facade ->> Protocol: close
|
|
353
|
+
end
|
|
354
|
+
</code>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: Documentation
|
|
4
|
+
permalink: /internals/documentation
|
|
5
|
+
link_group: internals
|
|
6
|
+
---
|
|
7
|
+
# :books: Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# Documentation
|
|
13
|
+
|
|
14
|
+
This section describes how to work with the pyatv documentation, i.e. the site you are currently
|
|
15
|
+
reading.
|
|
16
|
+
|
|
17
|
+
# Building the Documentation
|
|
18
|
+
|
|
19
|
+
To preview changes you make before pushing them to GitHub, run this script (only tested on Linux):
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
./scripts/build_doc.sh
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This will spawn Jekyll in preview mode and serve the documentation at [http://localhost:4000](http://localhost:4000). Changes are picked up and re-generated automatically. Note that Docker is required to run this script.
|
|
26
|
+
|
|
27
|
+
If you make changes to `_config.yml`, you need to restart the script for them to take effect. If you make changes to indirect dependencies, e.g. `page_links:` in `_config.yml`, it will not be enough to restart the script. You will either have to make a dummy modification to your file (for instance use the `touch <file>` command) or remove `_site` prior to starting the script. This is because the information under `page_links:` might be used by your page but Jekyll doesn't know that, so it won't re-generate that page and serve the cached version instead.
|
|
28
|
+
|
|
29
|
+
# Updating Documentation
|
|
30
|
+
|
|
31
|
+
All documentation is written in markdown. One resource is [this one](https://guides.github.com/features/mastering-markdown/). The index page is `docs/index.md` and other pages are located in sub-directories, e.g. `docs/development`. You can just go ahead and make simple changes like spelling errors and clarifications directly and push a PR. If you want to add new pages, continue reading.
|
|
32
|
+
|
|
33
|
+
## Navigation
|
|
34
|
+
|
|
35
|
+
At the top of the site there are a few buttons, e.g. `Development` and `Support`. These buttons are generated by `top_pages:` in `_config.yml`. To add a new button, just extend this list. There are already too many buttons, so please try placing your changes on a new subpage instead.
|
|
36
|
+
|
|
37
|
+
Subpage navigation links are shown below the buttons but above the actual content. Each section, e.g. `Development` can have its own subpages and are defined under `page_links:`. A basic example:
|
|
38
|
+
|
|
39
|
+
```yml
|
|
40
|
+
top_pages:
|
|
41
|
+
- link: /support/
|
|
42
|
+
title: Support
|
|
43
|
+
|
|
44
|
+
page_links:
|
|
45
|
+
support:
|
|
46
|
+
- link: /support/
|
|
47
|
+
title: Start
|
|
48
|
+
- link: /support/migration/
|
|
49
|
+
title: Migration
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The key used under `top_pages:` should be the same key that is used under `page_links:`, i.e. `support` in this case (due to link being `/support/`).
|
|
53
|
+
|
|
54
|
+
When creating the actual page, you need to make sure that `permalink` and `link_group` is correctly set, otherwise the navigation won't work. Here is an example based on the configuration above:
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
---
|
|
58
|
+
layout: template
|
|
59
|
+
title: Migration
|
|
60
|
+
permalink: /support/migration/
|
|
61
|
+
link_group: support
|
|
62
|
+
---
|
|
63
|
+
# Migration
|
|
64
|
+
|
|
65
|
+
...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Since `permalink` is used to say how the page is accessed, location of the source file doesn't really matter. But try to keep the structure that is already in place as it makes it easier to find pages.
|
|
69
|
+
|
|
70
|
+
## Interfaces
|
|
71
|
+
|
|
72
|
+
In general each interface should have its own page under `Development`. So when adding a new interface (or extending an existing one), make sure you add a new subpage for that interface. Start by adding it to `page_links:` in `_config.yml` and then create a new file for it under `docs/development`. You can look at for instance `device_info.md` for inspiration.
|
|
73
|
+
|
|
74
|
+
## API Reference
|
|
75
|
+
|
|
76
|
+
Unfortunately there are no API reference libraries for Jekyll (at least not that works with GitHub Pages). So it cannot be automatically generated by jekyll. As a workaround, the API reference is manually updated and checked into the repository. The API reference is located under `docs/api` and can be updated by calling:
|
|
77
|
+
|
|
78
|
+
```shell
|
|
79
|
+
$ ./scripts/api.py generate
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Calling with `verify` instead will check if it is already up-to-date. This check is done by `chickn` and it will fail the build in case something has changed. So if you forget to update the API, you will be notified of this (your PR will not be possible to merge).
|
|
83
|
+
|
|
84
|
+
The API reference is generated using [pdoc3](https://pdoc3.github.io/pdoc/). To make it work with the rest of the site, the default HTML template has been modified to add some frontmatter (for `permalinks` and those parts) as well as not adding default HTML tags, like `<html>`. The pdoc specific parts can be found [here](https://github.com/postlund/pyatv/tree/master/docs/pdoc_templates). Some of the CSS and Javascript parts that are required are available [here](https://github.com/postlund/pyatv/tree/master/docs/assets).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: Interfaces
|
|
4
|
+
permalink: /internals/interfaces
|
|
5
|
+
link_group: internals
|
|
6
|
+
---
|
|
7
|
+
# :information_desk_person: Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# Interfaces
|
|
13
|
+
|
|
14
|
+
This page covers topics regarding the public interface provided to developers using pyatv.
|
|
15
|
+
|
|
16
|
+
# External Interfaces
|
|
17
|
+
|
|
18
|
+
The external interface is documented in the [developer documentation](https://pyatv.dev/development/api_reference/) (no need do duplicate it here). Everything else should be considered private.
|
|
19
|
+
|
|
20
|
+
In general these interfaces shall not be altered in such a way that it breaks conformity with released versions of pyatv. It is however OK to change an interface on `master` that has not yet been released as part of the development cycle.
|
|
21
|
+
|
|
22
|
+
If a breaking change must be made:
|
|
23
|
+
|
|
24
|
+
* Create issue describing the breaking change, add the `breaking change` label
|
|
25
|
+
* Update the [Migration](https://pyatv.dev/support/migration/) page
|
|
26
|
+
* Make sure this change is clearly marked in `CHANGES.md` (responsibility of @postlund)
|
|
27
|
+
|
|
28
|
+
When adding a new interface or updating an existing one, consider the following:
|
|
29
|
+
|
|
30
|
+
* Make necessary changes in {% include code file="interface.py" %}
|
|
31
|
+
* Is your addition a [feature](#features)?
|
|
32
|
+
* Reflect your changes in the protocols (e.g. {% include code file="dmap/__init__.py" %} and {% include code file="mrp/__init__.py" %})
|
|
33
|
+
* Update [documentation](documentation#interfaces)
|
|
34
|
+
* Generate API reference (`./scripts/api.py generate`)
|
|
35
|
+
|
|
36
|
+
## Adding a new Interface
|
|
37
|
+
|
|
38
|
+
Add a new interface by defining it in {% include code file="interface.py" %}. In general you need to add one interface (class) and also add a property to `interface.AppleTV` used to access the interface. General considerations mentioned above must also be taken into account. You can look at [this](https://github.com/postlund/pyatv/pull/498/commits/bba5a4f03b4dc087f5d8ef44d48c06c3a2360759) commit for some inspiration. The [PR](https://github.com/postlund/pyatv/pull/498) is also a good resource for how the feature was implemented and documented.
|
|
39
|
+
|
|
40
|
+
## Changing Existing Interface
|
|
41
|
+
|
|
42
|
+
Changing an already existing interface is generally simpler than adding a new one. Follow the same guidelines as above and there should be no problems.
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
When adding something with varying availability or something that is device/OS dependent, it should be considered a "feature" and conform to {% include api i="interface.Features" %}. In practice this means that a user should be able to ask if a certain feature is supported by the device or its availability.
|
|
47
|
+
|
|
48
|
+
This is done by adding the `@feature` decorator to your method or property (*only* in {% include code file="interface.py" %}):
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
@abstractmethod
|
|
52
|
+
@feature(6, "Pause", "Pause playing media.")
|
|
53
|
+
def pause(self) -> None:
|
|
54
|
+
"""Press key play."""
|
|
55
|
+
raise exceptions.NotSupportedError()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The decorator takes three arguments:
|
|
59
|
+
|
|
60
|
+
* An index that is unique for the feature
|
|
61
|
+
* Feature name - creates a constant in `const.FeatureName.<name>`
|
|
62
|
+
* Description of the feature - shown in API reference
|
|
63
|
+
|
|
64
|
+
All features need a unique index and it doesn't really matter what it is, but common practice is to use the "next free". pyatv will fail to load if there is a collision. More about this below.
|
|
65
|
+
|
|
66
|
+
When a new feature has been added, the {% include api i="const.FeatureName" %} enum must be updated. You do this simply by running:
|
|
67
|
+
|
|
68
|
+
```shell
|
|
69
|
+
$ python ./scripts/features.py
|
|
70
|
+
# This enum is generated by scripts/features.py
|
|
71
|
+
class FeatureName(Enum):
|
|
72
|
+
"""All supported features."""
|
|
73
|
+
|
|
74
|
+
Up = 0
|
|
75
|
+
"""Up button on remote."""
|
|
76
|
+
|
|
77
|
+
Down = 1
|
|
78
|
+
"""Down button on remote."""
|
|
79
|
+
|
|
80
|
+
Left = 2
|
|
81
|
+
"""Left button on remote."""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
Next free index: 35
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then just copy-paste the generated code. As seen at the bottom of the input, the next free index is printed. So you can just run this script before adding a new feature to get a new index.
|
|
88
|
+
|
|
89
|
+
Next step is to make sure {% include api i="interface.Features.get_feature" %} returns the correct state for your feature. Refer to each protocol implementation and make sure to add tests.
|
|
90
|
+
|
|
91
|
+
## Adding to facade
|
|
92
|
+
|
|
93
|
+
All new interfaces must all be added to {% include code file="support/facade.py" %}. It should in most cases be
|
|
94
|
+
fairly straight-forward: just use one of the other implementations as an example. You can read
|
|
95
|
+
more about the facade pattern under [design](design#facade).
|