@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
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@generated by mypy-protobuf. Do not edit manually!
|
|
3
|
+
isort:skip_file
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
import google.protobuf.descriptor
|
|
8
|
+
import google.protobuf.internal.extension_dict
|
|
9
|
+
import google.protobuf.message
|
|
10
|
+
import pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2
|
|
11
|
+
import pyatv.protocols.mrp.protobuf.VolumeControlAvailabilityMessage_pb2
|
|
12
|
+
import typing
|
|
13
|
+
|
|
14
|
+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
15
|
+
|
|
16
|
+
@typing.final
|
|
17
|
+
class VolumeControlCapabilitiesDidChangeMessage(google.protobuf.message.Message):
|
|
18
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
19
|
+
|
|
20
|
+
CAPABILITIES_FIELD_NUMBER: builtins.int
|
|
21
|
+
ENDPOINTUID_FIELD_NUMBER: builtins.int
|
|
22
|
+
OUTPUTDEVICEUID_FIELD_NUMBER: builtins.int
|
|
23
|
+
endpointUID: builtins.str
|
|
24
|
+
outputDeviceUID: builtins.str
|
|
25
|
+
@property
|
|
26
|
+
def capabilities(self) -> pyatv.protocols.mrp.protobuf.VolumeControlAvailabilityMessage_pb2.VolumeControlAvailabilityMessage: ...
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
*,
|
|
30
|
+
capabilities: pyatv.protocols.mrp.protobuf.VolumeControlAvailabilityMessage_pb2.VolumeControlAvailabilityMessage | None = ...,
|
|
31
|
+
endpointUID: builtins.str | None = ...,
|
|
32
|
+
outputDeviceUID: builtins.str | None = ...,
|
|
33
|
+
) -> None: ...
|
|
34
|
+
def HasField(self, field_name: typing.Literal["capabilities", b"capabilities", "endpointUID", b"endpointUID", "outputDeviceUID", b"outputDeviceUID"]) -> builtins.bool: ...
|
|
35
|
+
def ClearField(self, field_name: typing.Literal["capabilities", b"capabilities", "endpointUID", b"endpointUID", "outputDeviceUID", b"outputDeviceUID"]) -> None: ...
|
|
36
|
+
|
|
37
|
+
global___VolumeControlCapabilitiesDidChangeMessage = VolumeControlCapabilitiesDidChangeMessage
|
|
38
|
+
|
|
39
|
+
VOLUMECONTROLCAPABILITIESDIDCHANGEMESSAGE_FIELD_NUMBER: builtins.int
|
|
40
|
+
volumeControlCapabilitiesDidChangeMessage: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2.ProtocolMessage, global___VolumeControlCapabilitiesDidChangeMessage]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
syntax = "proto2";
|
|
2
|
+
|
|
3
|
+
import "pyatv/protocols/mrp/protobuf/ProtocolMessage.proto";
|
|
4
|
+
|
|
5
|
+
extend ProtocolMessage {
|
|
6
|
+
optional VolumeDidChangeMessage volumeDidChangeMessage = 56;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
message VolumeDidChangeMessage {
|
|
10
|
+
optional float volume = 1;
|
|
11
|
+
optional string endpointUID = 2;
|
|
12
|
+
optional string outputDeviceUID = 3;
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@generated by mypy-protobuf. Do not edit manually!
|
|
3
|
+
isort:skip_file
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
import google.protobuf.descriptor
|
|
8
|
+
import google.protobuf.internal.extension_dict
|
|
9
|
+
import google.protobuf.message
|
|
10
|
+
import pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2
|
|
11
|
+
import typing
|
|
12
|
+
|
|
13
|
+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
14
|
+
|
|
15
|
+
@typing.final
|
|
16
|
+
class VolumeDidChangeMessage(google.protobuf.message.Message):
|
|
17
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
18
|
+
|
|
19
|
+
VOLUME_FIELD_NUMBER: builtins.int
|
|
20
|
+
ENDPOINTUID_FIELD_NUMBER: builtins.int
|
|
21
|
+
OUTPUTDEVICEUID_FIELD_NUMBER: builtins.int
|
|
22
|
+
volume: builtins.float
|
|
23
|
+
endpointUID: builtins.str
|
|
24
|
+
outputDeviceUID: builtins.str
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
*,
|
|
28
|
+
volume: builtins.float | None = ...,
|
|
29
|
+
endpointUID: builtins.str | None = ...,
|
|
30
|
+
outputDeviceUID: builtins.str | None = ...,
|
|
31
|
+
) -> None: ...
|
|
32
|
+
def HasField(self, field_name: typing.Literal["endpointUID", b"endpointUID", "outputDeviceUID", b"outputDeviceUID", "volume", b"volume"]) -> builtins.bool: ...
|
|
33
|
+
def ClearField(self, field_name: typing.Literal["endpointUID", b"endpointUID", "outputDeviceUID", b"outputDeviceUID", "volume", b"volume"]) -> None: ...
|
|
34
|
+
|
|
35
|
+
global___VolumeDidChangeMessage = VolumeDidChangeMessage
|
|
36
|
+
|
|
37
|
+
VOLUMEDIDCHANGEMESSAGE_FIELD_NUMBER: builtins.int
|
|
38
|
+
volumeDidChangeMessage: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2.ProtocolMessage, global___VolumeDidChangeMessage]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@generated by mypy-protobuf. Do not edit manually!
|
|
3
|
+
isort:skip_file
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import builtins
|
|
7
|
+
import google.protobuf.descriptor
|
|
8
|
+
import google.protobuf.internal.extension_dict
|
|
9
|
+
import google.protobuf.message
|
|
10
|
+
import pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2
|
|
11
|
+
import typing
|
|
12
|
+
|
|
13
|
+
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
14
|
+
|
|
15
|
+
@typing.final
|
|
16
|
+
class WakeDeviceMessage(google.protobuf.message.Message):
|
|
17
|
+
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
) -> None: ...
|
|
22
|
+
|
|
23
|
+
global___WakeDeviceMessage = WakeDeviceMessage
|
|
24
|
+
|
|
25
|
+
WAKEDEVICEMESSAGE_FIELD_NUMBER: builtins.int
|
|
26
|
+
wakeDeviceMessage: google.protobuf.internal.extension_dict._ExtensionFieldDescriptor[pyatv.protocols.mrp.protobuf.ProtocolMessage_pb2.ProtocolMessage, global___WakeDeviceMessage]
|
|
File without changes
|
package/pyatv/pylintrc
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[MASTER]
|
|
2
|
+
load-plugins=pylint.extensions.no_self_use
|
|
3
|
+
reports=no
|
|
4
|
+
|
|
5
|
+
[BASIC]
|
|
6
|
+
good-names=
|
|
7
|
+
ex,
|
|
8
|
+
rd,
|
|
9
|
+
f,
|
|
10
|
+
i,
|
|
11
|
+
T,
|
|
12
|
+
ws
|
|
13
|
+
|
|
14
|
+
[FORMAT]
|
|
15
|
+
max-line-length=88
|
|
16
|
+
|
|
17
|
+
[TYPECHECK]
|
|
18
|
+
ignore=protobuf
|
|
19
|
+
ignored-modules=pyatv.protocols.mrp.protobuf
|
|
20
|
+
ignored-classes=
|
|
21
|
+
ProtocolMessage,
|
|
22
|
+
SetConnectionStateMessage,
|
|
23
|
+
SetStateMessage,
|
|
24
|
+
ContentItemMetadata,
|
|
25
|
+
CommandInfo,
|
|
26
|
+
PlaybackState,
|
|
27
|
+
ShuffleMode,
|
|
28
|
+
RepeatMode,
|
|
29
|
+
HandlerReturnStatus,
|
|
30
|
+
SendError,
|
|
31
|
+
DeviceClass
|
|
32
|
+
|
|
33
|
+
disable=
|
|
34
|
+
broad-except,
|
|
35
|
+
cyclic-import, # TODO: Bug in pylint?
|
|
36
|
+
locally-disabled,
|
|
37
|
+
duplicate-code,
|
|
38
|
+
fixme,
|
|
39
|
+
unused-argument,
|
|
40
|
+
no-self-use,
|
|
41
|
+
too-few-public-methods,
|
|
42
|
+
too-many-public-methods,
|
|
43
|
+
too-many-arguments,
|
|
44
|
+
too-many-instance-attributes,
|
|
45
|
+
too-many-lines,
|
|
46
|
+
too-many-positional-arguments
|
|
47
|
+
|
|
48
|
+
[EXCEPTIONS]
|
|
49
|
+
overgeneral-exceptions=builtins.Exception
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "setuptools.build_meta"
|
|
3
|
+
requires = ["setuptools>=77.0"]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name="pyatv"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE.md"]
|
|
9
|
+
description = "A client library for Apple TV and AirPlay devices"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
authors = [{ name = "Pierre Ståhl", email = "pierre.staahl@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.9.0"
|
|
13
|
+
keywords = ["apple", "tv", "airplay", "raop", "companion", "dmap", "dacp"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3.9",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Software Development :: Libraries",
|
|
25
|
+
"Topic :: Home Automation",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
]
|
|
28
|
+
dynamic = ["version", "dependencies"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
"Homepage" = "https://pyatv.dev"
|
|
32
|
+
"Repository" = "https://github.com/postlund/pyatv"
|
|
33
|
+
"Bug Reports" = "https://github.com/postlund/pyatv/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
atvremote = "pyatv.scripts.atvremote:main"
|
|
37
|
+
atvproxy = "pyatv.scripts.atvproxy:main"
|
|
38
|
+
atvscript = "pyatv.scripts.atvscript:main"
|
|
39
|
+
atvlog = "pyatv.scripts.atvlog:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.dynamic]
|
|
42
|
+
version = { attr = "pyatv.const.__version__" }
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
include = ["pyatv*"]
|
|
46
|
+
|
|
47
|
+
[tool.black]
|
|
48
|
+
extend-exclude = '(protobuf/(__init__|.*_pb2).py)|__pycache__'
|
|
49
|
+
include = '(pyatv|tests|examples|scripts).*\.py'
|
|
50
|
+
|
|
51
|
+
[tool.isort]
|
|
52
|
+
skip_glob = "pyatv/protocols/mrp/protobuf/*.py"
|
|
53
|
+
profile = "black"
|
|
54
|
+
force_sort_within_sections = true
|
|
55
|
+
known_first_party = [
|
|
56
|
+
"pyatv",
|
|
57
|
+
"tests",
|
|
58
|
+
"scripts",
|
|
59
|
+
]
|
|
60
|
+
forced_separate = [
|
|
61
|
+
"tests",
|
|
62
|
+
"scripts",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[[tool.mypy.overrides]]
|
|
66
|
+
module = [
|
|
67
|
+
"miniaudio",
|
|
68
|
+
"audio_metadata",
|
|
69
|
+
"srptools",
|
|
70
|
+
]
|
|
71
|
+
ignore_missing_imports = true
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
aiohttp==3.13.3
|
|
2
|
+
async-timeout==5.0.1;python_version<'3.11'
|
|
3
|
+
cryptography==46.0.3
|
|
4
|
+
chacha20poly1305-reuseable==0.13.2
|
|
5
|
+
ifaddr==0.2.0
|
|
6
|
+
ifaddr==0.2.0
|
|
7
|
+
miniaudio==1.61
|
|
8
|
+
protobuf==6.33.2
|
|
9
|
+
pydantic==2.12.5
|
|
10
|
+
requests==2.32.5
|
|
11
|
+
srptools==1.0.1
|
|
12
|
+
tabulate==0.9.0
|
|
13
|
+
tinytag==2.1.2
|
|
14
|
+
zeroconf==0.148.0
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
black==25.11.0
|
|
2
|
+
deepdiff==8.6.1
|
|
3
|
+
flake8==7.3.0
|
|
4
|
+
isort==6.0.1
|
|
5
|
+
mutagen==1.47.0
|
|
6
|
+
pyfakefs==5.10.2
|
|
7
|
+
pylint==3.3.9
|
|
8
|
+
pytest==8.4.2
|
|
9
|
+
pytest-asyncio==0.26.0
|
|
10
|
+
pytest-cov==7.0.0
|
|
11
|
+
pytest-timeout==2.4.0
|
|
12
|
+
pytest-aiohttp==1.0.5
|
|
13
|
+
pytest_httpserver==1.1.3
|
|
14
|
+
pytest-xdist==3.8.0
|
|
15
|
+
pydocstyle==6.3.0
|
|
16
|
+
mypy==1.19.0
|
|
17
|
+
mypy-protobuf==3.7.0
|
|
18
|
+
types-protobuf==6.32.1.20251210
|
|
19
|
+
types-requests==2.32.4.20260107
|
|
20
|
+
types-tabulate==0.9.0.20241207
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
run()
|
|
4
|
+
{
|
|
5
|
+
docker run -p 4000:4000 --rm \
|
|
6
|
+
-v "$PWD:/srv/jekyll" \
|
|
7
|
+
-v "$PWD/docs/vendor/bundle:/usr/local/bundle" \
|
|
8
|
+
-w /srv/jekyll/docs \
|
|
9
|
+
-it jekyll/jekyll:3.8 "$@"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if [[ $GITPOD_INSTANCE_ID ]]; then
|
|
13
|
+
cd docs && bundle install && bundle update github-pages && bundle exec jekyll serve --incremental --watch
|
|
14
|
+
else
|
|
15
|
+
run bundle update github-pages
|
|
16
|
+
run jekyll serve --incremental --watch
|
|
17
|
+
fi
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Set in GitPod which breaks installation in venv
|
|
4
|
+
unset PIP_USER
|
|
5
|
+
|
|
6
|
+
VERSIONS="3.10 3.9 3.8 3.7 3.6"
|
|
7
|
+
|
|
8
|
+
found_version=
|
|
9
|
+
for p in $VERSIONS
|
|
10
|
+
do
|
|
11
|
+
which python${p} 2>&1 > /dev/null
|
|
12
|
+
if [ $? -eq 0 ]; then
|
|
13
|
+
found_version=python${p}
|
|
14
|
+
break
|
|
15
|
+
fi
|
|
16
|
+
done
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
if [ -z $found_version ]; then
|
|
21
|
+
>&2 echo "no python installation found"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
echo "-> Using python: $found_version"
|
|
26
|
+
|
|
27
|
+
echo "-> Creating python virtual environment..."
|
|
28
|
+
$found_version -m venv .
|
|
29
|
+
|
|
30
|
+
echo "-> Activating virtual environment..."
|
|
31
|
+
source bin/activate
|
|
32
|
+
sed -i '' 's/false/true/' pyvenv.cfg
|
|
33
|
+
|
|
34
|
+
echo "-> Upgrading pip..."
|
|
35
|
+
pip install --upgrade pip
|
|
36
|
+
|
|
37
|
+
echo "-> Installing library as develop..."
|
|
38
|
+
python setup.py develop
|
|
39
|
+
|
|
40
|
+
echo "-> Installing protobuf-setuptools..."
|
|
41
|
+
pip install protobuf-setuptools
|
|
42
|
+
|
|
43
|
+
echo "-> Installing dependencies..."
|
|
44
|
+
pip install --upgrade pyyaml -r requirements/requirements_test.txt -r requirements/requirements_docs.txt
|
|
45
|
+
|
|
46
|
+
echo "-> Running tests as verification..."
|
|
47
|
+
python setup.py test
|
|
48
|
+
|
|
49
|
+
cat <<EOF
|
|
50
|
+
==================================================
|
|
51
|
+
|
|
52
|
+
When starting a new shell, run:
|
|
53
|
+
|
|
54
|
+
source bin/activate
|
|
55
|
+
|
|
56
|
+
To run tests, run any of:
|
|
57
|
+
|
|
58
|
+
python setup.py test
|
|
59
|
+
pytest tests/test_conf.py # Single test
|
|
60
|
+
|
|
61
|
+
Test everything with chickn:
|
|
62
|
+
|
|
63
|
+
./scripts/chickn.py
|
|
64
|
+
|
|
65
|
+
To re-generate protobuf messages:
|
|
66
|
+
|
|
67
|
+
./scripts/protobuf.py --download generate
|
|
68
|
+
|
|
69
|
+
The CLI application can be used, e.g. run:
|
|
70
|
+
|
|
71
|
+
atvremote --debug commands
|
|
72
|
+
atvremote --debug playing
|
|
73
|
+
|
|
74
|
+
To preview documentation in docs, run:
|
|
75
|
+
|
|
76
|
+
./scripts/build_docs.sh
|
|
77
|
+
|
|
78
|
+
and navigate to http://127.0.0.1:4000
|
|
79
|
+
|
|
80
|
+
==================================================
|
|
81
|
+
|
|
82
|
+
Environment is configured and ready to use!
|
|
83
|
+
EOF
|
package/pyatv/setup.cfg
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[aliases]
|
|
2
|
+
test=pytest
|
|
3
|
+
|
|
4
|
+
[tool:pytest]
|
|
5
|
+
testpaths = tests
|
|
6
|
+
norecursedirs = .git
|
|
7
|
+
|
|
8
|
+
[flake8]
|
|
9
|
+
exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
|
|
10
|
+
max-line-length = 88
|
|
11
|
+
ignore = E203, W503
|
|
12
|
+
|
|
13
|
+
[pydocstyle]
|
|
14
|
+
match_dir = ^((?!\.|www_static).)*$
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Audio files in this directory have been generated by the
|
|
2
|
+
commands below in case they ever need to be re-generated.
|
|
3
|
+
|
|
4
|
+
Only metadata, no audio:
|
|
5
|
+
|
|
6
|
+
python ../../scripts/audiogen.py --title pyatv --artist postlund --album raop -n 0 -o only_metadata.wav
|
|
7
|
+
|
|
8
|
+
Audio, 10 frames (=20 samples):
|
|
9
|
+
|
|
10
|
+
python ../../scripts/audiogen.py -n 10 -o audio_10_frames.wav
|
|
11
|
+
|
|
12
|
+
Audio, 352 * 3 frames = 3 packets (=1056 samples):
|
|
13
|
+
|
|
14
|
+
python ../../scripts/audiogen.py -n 1056 -o audio_3_packets.wav
|
|
15
|
+
|
|
16
|
+
Audio, 8 seconds static (no sound), ogg for compression:
|
|
17
|
+
|
|
18
|
+
python ../../scripts/audiogen.py -n 132300 -s -o static_3sec.wav
|
|
19
|
+
ffmpeg -i static_3sec.wav static_3sec.ogg
|
|
20
|
+
|
|
21
|
+
Audio, 1 packets (=352 samples), with metadata:
|
|
22
|
+
|
|
23
|
+
python ../../scripts/audiogen.py --title pyatv --artist postlund --album raop -n 352 -o audio_1_packet_metadata.wav
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
a file for testing
|
package/src/index.ts
CHANGED
|
@@ -118,19 +118,68 @@ export interface AppleTVConnection {
|
|
|
118
118
|
protocol: CompanionProtocol;
|
|
119
119
|
device: AppleTVDevice;
|
|
120
120
|
credentials: Credentials;
|
|
121
|
+
/** Credentials + endpoint used for this connection attempt/result. */
|
|
122
|
+
usedCredentials: ConnectionCredentialsSnapshot;
|
|
123
|
+
/** True when usedCredentials exactly match the values passed to connect(...). */
|
|
124
|
+
usedCredentialsMatchProvided: boolean;
|
|
121
125
|
/** @internal */
|
|
122
126
|
_keyboardFocusState: KeyboardFocusState;
|
|
123
127
|
}
|
|
124
128
|
|
|
129
|
+
export interface ConnectionCredentialsSnapshot {
|
|
130
|
+
airplay: string;
|
|
131
|
+
companion: string;
|
|
132
|
+
device: {
|
|
133
|
+
name: string;
|
|
134
|
+
address: string;
|
|
135
|
+
port: number;
|
|
136
|
+
airplayPort: number;
|
|
137
|
+
identifier: string;
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
function errorMessage(error: unknown): string {
|
|
126
142
|
if (error instanceof Error) return error.message;
|
|
127
143
|
return String(error);
|
|
128
144
|
}
|
|
129
145
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
146
|
+
const ID_PROPERTY_KEYS = new Set(['rpmrtid', 'deviceid', 'macaddress']);
|
|
147
|
+
|
|
148
|
+
function normalizeIdentifier(value: string): string {
|
|
149
|
+
return value.trim().toLowerCase();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getIdentifiers(device: AppleTVDevice): Set<string> {
|
|
153
|
+
const identifiers = new Set<string>();
|
|
154
|
+
|
|
155
|
+
if (device.identifier) {
|
|
156
|
+
identifiers.add(normalizeIdentifier(device.identifier));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [key, value] of Object.entries(device.properties || {})) {
|
|
160
|
+
if (!value || !ID_PROPERTY_KEYS.has(key.toLowerCase())) continue;
|
|
161
|
+
identifiers.add(normalizeIdentifier(value));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return identifiers;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function sharesIdentifier(target: AppleTVDevice, candidate: AppleTVDevice): boolean {
|
|
168
|
+
const targetIds = getIdentifiers(target);
|
|
169
|
+
if (targetIds.size === 0) return false;
|
|
170
|
+
|
|
171
|
+
for (const identifier of getIdentifiers(candidate)) {
|
|
172
|
+
if (targetIds.has(identifier)) return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function deviceMatchScore(target: AppleTVDevice, candidate: AppleTVDevice): number {
|
|
179
|
+
if (sharesIdentifier(target, candidate)) return 100;
|
|
180
|
+
if (candidate.address === target.address) return 10;
|
|
181
|
+
if (candidate.name === target.name) return 1;
|
|
182
|
+
return 0;
|
|
134
183
|
}
|
|
135
184
|
|
|
136
185
|
function mergeDiscoveredDevice(target: AppleTVDevice, discovered: AppleTVDevice): AppleTVDevice {
|
|
@@ -141,6 +190,40 @@ function mergeDiscoveredDevice(target: AppleTVDevice, discovered: AppleTVDevice)
|
|
|
141
190
|
};
|
|
142
191
|
}
|
|
143
192
|
|
|
193
|
+
function hasCompanionEndpointChanged(current: AppleTVDevice, discovered: AppleTVDevice): boolean {
|
|
194
|
+
return current.address !== discovered.address || current.port !== discovered.port;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toCredentialSnapshot(
|
|
198
|
+
device: AppleTVDevice,
|
|
199
|
+
credentials: Credentials,
|
|
200
|
+
): ConnectionCredentialsSnapshot {
|
|
201
|
+
return {
|
|
202
|
+
airplay: credentials.airplay,
|
|
203
|
+
companion: credentials.companion,
|
|
204
|
+
device: {
|
|
205
|
+
name: device.name,
|
|
206
|
+
address: device.address,
|
|
207
|
+
port: device.port,
|
|
208
|
+
airplayPort: device.airplayPort,
|
|
209
|
+
identifier: device.identifier,
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function snapshotsEqual(
|
|
215
|
+
left: ConnectionCredentialsSnapshot,
|
|
216
|
+
right: ConnectionCredentialsSnapshot,
|
|
217
|
+
): boolean {
|
|
218
|
+
return left.airplay === right.airplay
|
|
219
|
+
&& left.companion === right.companion
|
|
220
|
+
&& left.device.name === right.device.name
|
|
221
|
+
&& left.device.address === right.device.address
|
|
222
|
+
&& left.device.port === right.device.port
|
|
223
|
+
&& left.device.airplayPort === right.device.airplayPort
|
|
224
|
+
&& left.device.identifier === right.device.identifier;
|
|
225
|
+
}
|
|
226
|
+
|
|
144
227
|
async function connectCompanion(
|
|
145
228
|
device: AppleTVDevice,
|
|
146
229
|
companionCredentials: string,
|
|
@@ -154,7 +237,29 @@ async function connectCompanion(
|
|
|
154
237
|
|
|
155
238
|
async function discoverLatestDevice(device: AppleTVDevice): Promise<AppleTVDevice | null> {
|
|
156
239
|
const discovered = await scanDevices(3000, false);
|
|
157
|
-
|
|
240
|
+
let bestMatch: AppleTVDevice | null = null;
|
|
241
|
+
let bestScore = 0;
|
|
242
|
+
|
|
243
|
+
for (const candidate of discovered) {
|
|
244
|
+
const score = deviceMatchScore(device, candidate);
|
|
245
|
+
if (score === 0) continue;
|
|
246
|
+
|
|
247
|
+
if (!bestMatch || score > bestScore) {
|
|
248
|
+
bestMatch = candidate;
|
|
249
|
+
bestScore = score;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (score === bestScore) {
|
|
254
|
+
const candidatePortMatches = candidate.port === device.port;
|
|
255
|
+
const currentPortMatches = bestMatch.port === device.port;
|
|
256
|
+
if (candidatePortMatches && !currentPortMatches) {
|
|
257
|
+
bestMatch = candidate;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return bestMatch;
|
|
158
263
|
}
|
|
159
264
|
|
|
160
265
|
/**
|
|
@@ -166,6 +271,7 @@ export async function connect(
|
|
|
166
271
|
credentials: Credentials,
|
|
167
272
|
): Promise<AppleTVConnection> {
|
|
168
273
|
let activeDevice = device;
|
|
274
|
+
const providedCredentials = toCredentialSnapshot(device, credentials);
|
|
169
275
|
let protocol: CompanionProtocol;
|
|
170
276
|
|
|
171
277
|
try {
|
|
@@ -178,17 +284,22 @@ export async function connect(
|
|
|
178
284
|
discovered = null;
|
|
179
285
|
}
|
|
180
286
|
|
|
181
|
-
if (!discovered
|
|
287
|
+
if (!discovered) {
|
|
288
|
+
throw initialError;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const mergedDevice = mergeDiscoveredDevice(activeDevice, discovered);
|
|
292
|
+
if (!hasCompanionEndpointChanged(activeDevice, mergedDevice)) {
|
|
182
293
|
throw initialError;
|
|
183
294
|
}
|
|
184
295
|
|
|
185
|
-
activeDevice =
|
|
296
|
+
activeDevice = mergedDevice;
|
|
186
297
|
|
|
187
298
|
try {
|
|
188
299
|
protocol = await connectCompanion(activeDevice, credentials.companion);
|
|
189
300
|
} catch (retryError) {
|
|
190
301
|
throw new Error(
|
|
191
|
-
`Companion connection failed on saved
|
|
302
|
+
`Companion connection failed on saved endpoint ${device.address}:${device.port} and discovered endpoint ${activeDevice.address}:${activeDevice.port}: ${errorMessage(retryError)}`,
|
|
192
303
|
{ cause: initialError instanceof Error ? initialError : undefined },
|
|
193
304
|
);
|
|
194
305
|
}
|
|
@@ -236,8 +347,11 @@ export async function connect(
|
|
|
236
347
|
protocol,
|
|
237
348
|
device: activeDevice,
|
|
238
349
|
credentials,
|
|
350
|
+
usedCredentials: toCredentialSnapshot(activeDevice, credentials),
|
|
351
|
+
usedCredentialsMatchProvided: false,
|
|
239
352
|
_keyboardFocusState: KeyboardFocusState.Unknown,
|
|
240
353
|
};
|
|
354
|
+
conn.usedCredentialsMatchProvided = snapshotsEqual(conn.usedCredentials, providedCredentials);
|
|
241
355
|
|
|
242
356
|
// Watch keyboard focus state
|
|
243
357
|
_watchKeyboardFocus(protocol, (state) => {
|