@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,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: Storage and Settings
|
|
4
|
+
permalink: /development/storage/
|
|
5
|
+
link_group: development
|
|
6
|
+
---
|
|
7
|
+
Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# Storage and Settings
|
|
13
|
+
|
|
14
|
+
The storage API provides a way to store and read settings persistently, e.g. in a local file or remotely
|
|
15
|
+
on a cloud service. Settings include things such as:
|
|
16
|
+
|
|
17
|
+
* Various metadata, e.g. remote name, MAC address
|
|
18
|
+
* Credentials and passwords for services
|
|
19
|
+
* Protocol specific settings, e.g. port numbers
|
|
20
|
+
|
|
21
|
+
As storage is still a new API, only a handful settings are supported at the time of writing. More settings
|
|
22
|
+
will be added over time. To request a new setting, please open a new issue.
|
|
23
|
+
|
|
24
|
+
## Available Storage Modules
|
|
25
|
+
|
|
26
|
+
The following storage modules are shipped with pyatv:
|
|
27
|
+
|
|
28
|
+
| **Module** | **Description** | **API** |
|
|
29
|
+
| MemoryStorage | Stores settings within an instance in memory and are discarded when the object is no longer referenced. | {% include api i="storage/memory_storage.MemoryStorage" %}
|
|
30
|
+
| FileStorage | Stores settings in a file in JSON format. | {% include api i="storage/file_storage.FileStorage" %}
|
|
31
|
+
| AbstractStorage | Base class to ease development of custom storage modules, see [here](#custom-storage-module). | {% include api i="storage.AbstractStorage" %}
|
|
32
|
+
|
|
33
|
+
All main methods in `pyatv` ({% include api i="pyatv.connect" %}, {% include api i="pyatv.scan" %} and
|
|
34
|
+
{% include api i="pyatv.pair" %}) accept `storage` as an argument. Typically a storage instance is created once
|
|
35
|
+
and are then passed on to all mentioned methods. If no storage module is provided, a
|
|
36
|
+
{% include api i="storage/memory_storage.MemoryStorage" %} instance is created internally and used for storage.
|
|
37
|
+
|
|
38
|
+
## Storage Guidelines
|
|
39
|
+
|
|
40
|
+
When using the storage API, there are a few guidelines to consider:
|
|
41
|
+
|
|
42
|
+
* All settings are device specific
|
|
43
|
+
* If a setting is not set, a default value is used (see {% include api i="settings" %} for values)
|
|
44
|
+
* Settings are generally not changed after {% include api i="pyatv.connect" %} has been called
|
|
45
|
+
|
|
46
|
+
The gist is more or less that settings are independent on device level (you can set different MAC addresses
|
|
47
|
+
per device for instance) and they apply instantly. It is however not certain that it will be used until the
|
|
48
|
+
next {% include api i="pyatv.connect" %} call, since many settings are only used when connecting.
|
|
49
|
+
|
|
50
|
+
Internally, `pyatv` will automatically populate the provided storage with new devices when scanning and also
|
|
51
|
+
insert credentials when pairing. Once a protocol has been successfully paired, no further credential
|
|
52
|
+
management is necessary as the credentials will be available via the storage.
|
|
53
|
+
|
|
54
|
+
Settings are managed via an instance of {% include api i="settings.Settings" %}, typically available via
|
|
55
|
+
{% include api i="interface.AppleTV.settings" %}. It is also possible to retrieve settings directly from
|
|
56
|
+
storage using {% include api i="interface.Storage.get_settings" %}.
|
|
57
|
+
|
|
58
|
+
*Note: All changes made to a storage are stored in memory only until
|
|
59
|
+
{% include api i="interface.Storage.save" %} is called. Make sure to call this method after all
|
|
60
|
+
relevant updates, otherwise they will be lost!*
|
|
61
|
+
|
|
62
|
+
## Settings Priority
|
|
63
|
+
|
|
64
|
+
Storage is treated as a "first-class citizen" in pyatv. This means that internally, whenever pyatv
|
|
65
|
+
needs settings for a configuration, it will load whatever is in storage and overwrite what is currently
|
|
66
|
+
set in the configuration. If a setting is not present in storage, the corresponding setting in the
|
|
67
|
+
configuration will however remain unchanged.
|
|
68
|
+
|
|
69
|
+
The practical way of thinking about this is that settings and configurations are modified independently,
|
|
70
|
+
i.e. you can change a setting in storage to one value and corresponding setting in the configuration to
|
|
71
|
+
another value, thus creating an inconsistency. However, when you call a pyatv method, e.g.
|
|
72
|
+
{% include api i="pyatv.connect" %}, settings will be loaded from storage and overwrite whatever is
|
|
73
|
+
set in the configuration. Here is a simple example illustrating this:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
storage = FileStorage("pyatv.conf", loop)
|
|
77
|
+
|
|
78
|
+
conf = await pyatv.scan(loop, identifier="xxx")[0] # Scan without storage
|
|
79
|
+
conf.get_service(Protocol.AirPlay).password ="pyatv" # Change something in configuration
|
|
80
|
+
|
|
81
|
+
settings = await storage.get_settings(conf) # "conf" does not exist in this storage => new settings
|
|
82
|
+
# password for AirPlay will be copied
|
|
83
|
+
|
|
84
|
+
conf.get_service(Protocol.AirPlay).password ="pass" # Change to something else
|
|
85
|
+
settings = await storage.get_settings(conf) # "conf" exists => return existing settings with
|
|
86
|
+
# AirPlay password set to "pyatv"
|
|
87
|
+
|
|
88
|
+
# connect will read settings from storage and apply to configuration (e.g. "pyatv" as AirPlay password)
|
|
89
|
+
atv = await pyatv.connect(conf, loop, storage=storage)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If you make changes to a configuration and want to save them, use
|
|
93
|
+
{% include api i="interface.Storage.update_settings" %} explicitly to force an update to storage:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
storage = FileStorage("pyatv.conf", loop)
|
|
97
|
+
conf = await pyatv.scan(loop, identifier="xxx", storage=storage)[0] # Scan and insert into storage
|
|
98
|
+
|
|
99
|
+
conf.get_service(Protocol.AirPlay).credentials ="new_creds" # Change something in configuration
|
|
100
|
+
await storage.update_settings(conf)
|
|
101
|
+
|
|
102
|
+
# "new_creds" have been saved to storage, so connect till use that password
|
|
103
|
+
atv = await pyatv.connect(conf, loop, storage=storage)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The recommended way to alter settings is update settings directly in storage, i.e. by changing
|
|
107
|
+
the instance returned by {% include api i="interface.Storage.get_settings" %} and not
|
|
108
|
+
changing the configuration.
|
|
109
|
+
|
|
110
|
+
## Using the Storage API
|
|
111
|
+
|
|
112
|
+
Create a new storage of choice:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from pyatv.storage.file_storage import FileStorage
|
|
116
|
+
|
|
117
|
+
loop = asyncio.get_event_loop()
|
|
118
|
+
storage = FileStorage("pyatv.json", loop)
|
|
119
|
+
await storage.load()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Note that the storage must be loaded using {% include api i="interface.Storage.load" %} in order to fetch
|
|
123
|
+
settings from the underlying storage, e.g. reading from file. Not calling this method results in undefined
|
|
124
|
+
behavior.
|
|
125
|
+
|
|
126
|
+
Scanning with storage:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
confs = await pyatv.scan(loop, storage=storage)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The storage will automatically be populated with all discovered devices.
|
|
133
|
+
|
|
134
|
+
To pair with a storage:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
pairing = await pair(atvs[0], Protocol.AirPlay, loop, storage=storage)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
When a successful pairing has been made, the resulting credentials are automatically inserted into
|
|
141
|
+
the storage for further references.
|
|
142
|
+
|
|
143
|
+
To connect with storage:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
atv = await pyatv.connect(confs[0], loop, storage=storage)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
An instance of {% include api i="settings.Settings" %} containing loaded settings is available
|
|
150
|
+
via {% include api i="interface.AppleTV.settings" %}.
|
|
151
|
+
|
|
152
|
+
Changes made to settings in the storage are only stored in memory and must be saved using
|
|
153
|
+
{% include api i="interface.Storage.save" %} to make them persistent:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
await storage.save()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Storages are supposed to keep track of changes and only save changes if something has actually
|
|
160
|
+
changed.
|
|
161
|
+
|
|
162
|
+
### Default File Storage
|
|
163
|
+
|
|
164
|
+
When using tools bundled with pyatv, e.g. `atvremote` or `atvscript`,
|
|
165
|
+
{% include api i="storage/file_storage.FileStorage" %} is used with the file `$HOME/.pyatv.conf`.
|
|
166
|
+
You can tap into this storage with your own applications, thus sharing credentials with pyatv.
|
|
167
|
+
There is a convenience method called
|
|
168
|
+
{% include api i="storage/file_storage.FileStorage.default_storage" %} that is recommended to use
|
|
169
|
+
as it is platform agnostic:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
loop = asyncio.get_event_loop()
|
|
173
|
+
storage = FileStorage.default_storage(loop)
|
|
174
|
+
await storage.load()
|
|
175
|
+
```
|
|
176
|
+
## Changing Settings
|
|
177
|
+
|
|
178
|
+
As stated in [Storage Guidelines](#storage-guidelines), some settings are only used when connecting
|
|
179
|
+
while others can be used at any given time (e.g. port numbers). It is recommended to update settings
|
|
180
|
+
prior to connecting, but it is allowed to change setting even after connecting but the changes
|
|
181
|
+
might not apply until the next time you connect.
|
|
182
|
+
|
|
183
|
+
To get settings for a device, use {% include api i="interface.Storage.get_settings" %}:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
conf = ... # Scan for device
|
|
187
|
+
|
|
188
|
+
settings = await storage.get_settings(conf)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
To change setting, simply set new values according to your needs:
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
settings.name = "My App" # Named used when pairing
|
|
195
|
+
settings.mac = "aa:bb:cc:dd:ee:ff" # MAC address pyatv presents itself when needed
|
|
196
|
+
|
|
197
|
+
settings.raop.password = "never_gonna_give_you_up"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Remember to save changes to storage to ensure they are stored persistently
|
|
201
|
+
({% include api i="interface.Storage.save" %}).
|
|
202
|
+
|
|
203
|
+
To find available settings, look at {% include api i="settings.Settings" %} in the API
|
|
204
|
+
reference as each field are described there (including default values when applicable).
|
|
205
|
+
|
|
206
|
+
## Custom Storage Module
|
|
207
|
+
|
|
208
|
+
To implement your own storage module, it is recommended to inherit from
|
|
209
|
+
{% include api i="storage.AbstractStorage" %} and implement the missing methods. Internally `pyatv` uses
|
|
210
|
+
[pydantic](https://docs.pydantic.dev/) for the storage model, simplifying things like serialization when
|
|
211
|
+
storing and loading settings. The {% include api i="storage.AbstractStorage.storage_model" %} property
|
|
212
|
+
is used to get the current representation but also to update it when loading a model from an external
|
|
213
|
+
source.
|
|
214
|
+
|
|
215
|
+
Simply put:
|
|
216
|
+
|
|
217
|
+
* `save` shall serialize {% include api i="storage.AbstractStorage.storage_model" %} in some way, e.g.
|
|
218
|
+
into JSON or YAML and save that somewhere
|
|
219
|
+
* `load` shall take the saved data, de-serialize it and set
|
|
220
|
+
{% include api i="storage.AbstractStorage.storage_model" %} with the de-serialized data
|
|
221
|
+
|
|
222
|
+
A simple pseudo example looks like this:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import json
|
|
226
|
+
from pyatv.storage import AbstractStorage
|
|
227
|
+
|
|
228
|
+
class CloudStorage(AbstractStorage):
|
|
229
|
+
|
|
230
|
+
def __init__(self, cloud_api):
|
|
231
|
+
super().__init__()
|
|
232
|
+
self.cloud_api = cloud_api
|
|
233
|
+
|
|
234
|
+
async def save(self) -> None:
|
|
235
|
+
if self.changed:
|
|
236
|
+
json_data = self.storage_model.model_dump_json(exclude_defaults=True)
|
|
237
|
+
await self.cloud_api.save("myfile.json", json_data)
|
|
238
|
+
self.mark_as_saved()
|
|
239
|
+
|
|
240
|
+
async def load(self) -> None:
|
|
241
|
+
json_data = await self.cloud_api.load("myfile.json")
|
|
242
|
+
self.storage_model = StorageModel(**json.loads(json_data))
|
|
243
|
+
self.mark_as_saved()
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Nothing else is really needed to implement a new storage module (implementing `__str__` for debugging
|
|
248
|
+
purposes is also recommended). Do note how {% include api i="storage.AbstractStorage.changed" %} and
|
|
249
|
+
{% include api i="storage.AbstractStorage.mark_as_saved" %} are used to only save the storage model
|
|
250
|
+
in case it was changed. This removes unnecessary writes to disk since the same content would be
|
|
251
|
+
re-written every time {% include api i="interface.Storage.save" %} is called otherwise.
|
|
252
|
+
|
|
253
|
+
More complex implementations may inherit from
|
|
254
|
+
{% include api i="interface.Storage" %} directly, but additional care must be taken to implement the
|
|
255
|
+
interface correctly.
|
|
256
|
+
|
|
257
|
+
*Note: Ensure to pass `exclude_defaults=True` when dumping the model, otherwise you will also save
|
|
258
|
+
default values. This pollutes the output a lot, but also causes problems if default values are changed
|
|
259
|
+
in pyatv as the settings written to storage will be used instead, i.e. old default values.*
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: Stream
|
|
4
|
+
permalink: /development/stream/
|
|
5
|
+
link_group: development
|
|
6
|
+
---
|
|
7
|
+
Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# Stream
|
|
13
|
+
|
|
14
|
+
It is possible to stream audio and video to a device via the stream interface using
|
|
15
|
+
AirPlay. The AirPlay suite consists of two protocols:
|
|
16
|
+
|
|
17
|
+
* AirTunes/RAOP - Used for real time streaming of audio
|
|
18
|
+
* "AirPlay - Everything else (video, images and screen mirroring)
|
|
19
|
+
|
|
20
|
+
Currently there is some AirPlay functionality supported in pyatv, but it is
|
|
21
|
+
very limited. These features are currently supported:
|
|
22
|
+
|
|
23
|
+
- Device authentication ("pairing")
|
|
24
|
+
- Playing media via URL
|
|
25
|
+
- Streaming of local files
|
|
26
|
+
|
|
27
|
+
Early support for streaming audio files via RAOP is also supported
|
|
28
|
+
(even for non-Apple TV devices). MP3, wav, FLAC and ogg files are
|
|
29
|
+
supported. Devices that require a password are only supported for the AirTunes/RAOP protocol, not the AirPlay protocol.
|
|
30
|
+
|
|
31
|
+
In the external interface, AirPlay (including RAOP) support is implemented via
|
|
32
|
+
the {% include api i="interface.Stream" %} interface.
|
|
33
|
+
|
|
34
|
+
## Using the streaming API
|
|
35
|
+
|
|
36
|
+
Devices supporting the AirPlay protocol (e.g. Apple TV) can play files by simply providing
|
|
37
|
+
a URL. It will then be streamed directly from the device. Audio can be streamed to other
|
|
38
|
+
devices (like AirPlay speakers) that does not support this.
|
|
39
|
+
|
|
40
|
+
### Play from URL
|
|
41
|
+
|
|
42
|
+
Playing a URL is as simple as passing the URL to {% include api i="interface.Stream.play_url" %}:
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
url = "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
|
|
46
|
+
await atv.stream.play_url(url)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
If the device requires device authentication, credentials must be present for
|
|
50
|
+
the AirPlay service. Otherwise an error message will be shown on the screen.
|
|
51
|
+
|
|
52
|
+
To play a local file, just pass a local file to {% include api i="interface.Stream.play_url" %}
|
|
53
|
+
instead:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
url = "/home/user/BigBuckBunny.mp4"
|
|
57
|
+
await atv.stream.play_url(url)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
When doing this, pyatv will internally start web server on a random port, serving this
|
|
61
|
+
file only and start streaming from there. When streaming is done, the web server is shut
|
|
62
|
+
down.
|
|
63
|
+
|
|
64
|
+
The Apple TV will not provide any feedback if anything is not working. If you have
|
|
65
|
+
problems, start by testing the example file above (`BigBuckBunny.mp4`) as that is
|
|
66
|
+
known to work. Also make sure that you don't stream from an HTTPS server with a bad
|
|
67
|
+
or self-signed certificate: that will not work.
|
|
68
|
+
|
|
69
|
+
### Stream a file
|
|
70
|
+
|
|
71
|
+
To stream a file, use {% include api i="interface.Stream.stream_file" %}:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
stream = ...
|
|
75
|
+
await stream.stream_file("sample.mp3")
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Files in MP3, WAV, FLAC and OGG format are supported and will be automatically converted
|
|
79
|
+
to a format the receiving device supports. Metadata is also extracted from files
|
|
80
|
+
of these types and sent to the receiver.
|
|
81
|
+
|
|
82
|
+
It is also possible to stream directly from a buffer. In this example, a file is
|
|
83
|
+
read into a buffer and streamed:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import io
|
|
87
|
+
|
|
88
|
+
with io.open("myfile.mp3", "rb") as source_file:
|
|
89
|
+
await stream.stream_file(source_file)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Streaming directly from `stdin` also works, e.g. when piping output from another
|
|
93
|
+
process:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
await stream.stream_file(sys.stdin.buffer)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
As `stdin` is a text stream, the underlying binary buffer must be retrieved and used.
|
|
100
|
+
|
|
101
|
+
It is also possible to use an asyncio
|
|
102
|
+
[StreamReader](https://docs.python.org/3/library/asyncio-stream.html#streamreader) as
|
|
103
|
+
input. Here is an example piping output from ffmpeg:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
import asyncio.subprocess as asp
|
|
107
|
+
|
|
108
|
+
process = await asp.create_subprocess_exec(
|
|
109
|
+
"ffmpeg", "-i", "file.mp3", "-f", "mp3", "-",
|
|
110
|
+
stdin=None, stdout=asp.PIPE, stderr=None,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
await self.atv.stream.stream_file(process.stdout)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
When streaming from a buffer, it's important to know that some audio formats are
|
|
117
|
+
not suitable for that. MP3 works fine, WAV and OGG does not. The reason is that
|
|
118
|
+
seeking is done in the stream and `stdin` does for instance not support that. If
|
|
119
|
+
the buffer supports seeking, then all formats will work fine, otherwise stick with
|
|
120
|
+
MP3. For the same reason, metadata will not work if seeking is not supported as
|
|
121
|
+
that is extracted prior to playing the file, so seeking is needed to return to
|
|
122
|
+
the beginning of file again before playback.
|
|
123
|
+
|
|
124
|
+
Note 1: Since pyatv v0.13.0, buffer improvements have been made to support some
|
|
125
|
+
seeking, even in non-seekable streams. This is however not fool-proof and not all
|
|
126
|
+
audio formats (or files/streams) work, but compatibility is much better from that
|
|
127
|
+
version and onwards.
|
|
128
|
+
|
|
129
|
+
Note 2: that there's (roughly) a two second delay until audio starts to play. This
|
|
130
|
+
is part of the buffering mechanism and not much pyatv can do anything about.
|
|
131
|
+
|
|
132
|
+
#### Custom Metadata
|
|
133
|
+
|
|
134
|
+
By default, pyatv will try to extract metadata from whatever content you are playing.
|
|
135
|
+
Some file formats or streams either does not support nor provide any metadata,
|
|
136
|
+
in which case you can manually provide the metadata that pyatv will report to the
|
|
137
|
+
receiver by passing an instance of {% include api i="interface.MediaMetadata" %}
|
|
138
|
+
when starting to stream:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from pyatv.interface import MediaMetadata
|
|
142
|
+
|
|
143
|
+
metadata = MediaMetadata(artist="pyatv", title="Look at me, I'm streaming")
|
|
144
|
+
await stream.stream_file("myfile.mp3", metadata=metadata)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
All fields in {% include api i="interface.MediaMetadata" %} can be overridden (including
|
|
148
|
+
artwork) except for {% include api i="interface.MediaMetadata.duration" %}, which is
|
|
149
|
+
ignored. Please note that artwork must be in JPEG format.
|
|
150
|
+
|
|
151
|
+
Custom metadata will override any metadata provided by the streamed content. You
|
|
152
|
+
can however tell pyatv to only override metadata fields that are missing by setting
|
|
153
|
+
`override_missing_metadata` to `True`:
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from pyatv.interface import MediaMetadata
|
|
157
|
+
|
|
158
|
+
metadata = MediaMetadata(artist="pyatv")
|
|
159
|
+
await stream.stream_file("myfile.mp3", metadata=metadata, override_missing_metadata=True)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This will use all the metadata from `myfile.mp3`, but use _pyatv_ as artist but **only**
|
|
163
|
+
if that field is not present in the file.
|
|
164
|
+
|
|
165
|
+
#### Stream from HTTP(S)
|
|
166
|
+
|
|
167
|
+
There is experimental support for streaming directly from HTTP or HTTPS. A URL can
|
|
168
|
+
be passed instead of a file path:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
await stream.stream_file("https://foo.bar/test.mp3")
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
#### File Compatibility
|
|
175
|
+
|
|
176
|
+
It is possible to verify if a file is supported programmatically using
|
|
177
|
+
{% include api i="helpers.is_streamable" %}:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from pyatv.helpers import is_streamable
|
|
181
|
+
|
|
182
|
+
if await is_streamable("myfile.mp3"):
|
|
183
|
+
await atv.stream_file("myfile.mp3")
|
|
184
|
+
else:
|
|
185
|
+
print("File is not supported")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
There are a few caveats worth knowing:
|
|
189
|
+
|
|
190
|
+
* No exception is ever raised, even when file is not found or lack of permissions
|
|
191
|
+
* Only valid for {% include api i="interface.Stream.stream_file" %} (*not*
|
|
192
|
+
{% include api i="interface.Stream.play_url" %})
|
|
193
|
+
* Only a basic check is made, the file might be broken and not still not playable
|
|
194
|
+
|
|
195
|
+
## Password
|
|
196
|
+
|
|
197
|
+
If you stream audio using the RAOP protocol and the device requires a password, you can set the password like this:
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
raop_service = atv_conf.get_service(Protocol.RAOP)
|
|
201
|
+
raop_service.password = "test"
|
|
202
|
+
atv = await connect(atv_conf, ...)
|
|
203
|
+
await atv.stream.stream_file("sample.mp3")
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Device Authentication
|
|
207
|
+
|
|
208
|
+
In tvOS 10.2, Apple started to enforce a feature called "device authentication".
|
|
209
|
+
This requires every device that streams content via AirPlay to enter a PIN code
|
|
210
|
+
the first time before playback is started. Once done, the user will never have
|
|
211
|
+
to do this again. The actual feature has been available for a while but as
|
|
212
|
+
opt-in, so it would have to be explicitly enabled. Now it is enabled by default
|
|
213
|
+
and cannot be disabled. Devices not running tvOS (e.g. Apple TV 2nd and 3rd
|
|
214
|
+
generation) are not affected, even though device authentication can be enabled
|
|
215
|
+
on these devices as well.
|
|
216
|
+
|
|
217
|
+
The device authentication process is based on the *Secure Remote Password*
|
|
218
|
+
protocol (SRP), with slight modifications. All the reverse engineering required
|
|
219
|
+
for this process was made by funtax (GitHub username) and has merely been ported
|
|
220
|
+
to python for usage in this library. Please see references at bottom of page
|
|
221
|
+
for reference implementation.
|
|
222
|
+
|
|
223
|
+
### Device Pairing in pyatv
|
|
224
|
+
|
|
225
|
+
When performing device authentication, a device identifier and a private key is
|
|
226
|
+
required. Once authenticated, they can be used to authenticate without using a
|
|
227
|
+
PIN code. So they must be saved and reused whenever something is to be played.
|
|
228
|
+
|
|
229
|
+
In this library, the device identifier and private key is called
|
|
230
|
+
*AirPlay credentials* and are concatenated into a string, using : as separator.
|
|
231
|
+
An example might look like this:
|
|
232
|
+
|
|
233
|
+
```raw
|
|
234
|
+
D9B75D737BE2F0F1:6A26D8EB6F4AE2408757D5CA5FF9C37E96BEBB22C632426C4A02AD4FA895A85B
|
|
235
|
+
^ ^
|
|
236
|
+
Identifier Private key
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The device authentication is performed via the pairing API, just like with
|
|
240
|
+
any other protocol. New random credentials are generated by default, as long
|
|
241
|
+
as no existing credentials are provided. So there is nothing special here.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: template
|
|
3
|
+
title: atvlog
|
|
4
|
+
permalink: /documentation/atvlog/
|
|
5
|
+
link_group: documentation
|
|
6
|
+
---
|
|
7
|
+
# Table of Contents
|
|
8
|
+
{:.no_toc}
|
|
9
|
+
* TOC
|
|
10
|
+
{:toc}
|
|
11
|
+
|
|
12
|
+
# atvlog
|
|
13
|
+
|
|
14
|
+
The `atvlog` script simplifies log inspection by generating an HTML file with basic
|
|
15
|
+
live filtering capabilities.
|
|
16
|
+
|
|
17
|
+
*Note: This is an incubating script and may change behavior with short notice.*
|
|
18
|
+
|
|
19
|
+
# Features
|
|
20
|
+
|
|
21
|
+
Log output from the following tools are supported as input:
|
|
22
|
+
|
|
23
|
+
* atvremote and atvscript
|
|
24
|
+
* Home Assistant log
|
|
25
|
+
|
|
26
|
+
A special `markdown` mode is supported, which extracts a log from the following
|
|
27
|
+
format:
|
|
28
|
+
|
|
29
|
+
~~~
|
|
30
|
+
text here is ignored
|
|
31
|
+
```log
|
|
32
|
+
log data here
|
|
33
|
+
```
|
|
34
|
+
also ignored
|
|
35
|
+
~~~
|
|
36
|
+
|
|
37
|
+
Filtering can be performed on the following attributes:
|
|
38
|
+
|
|
39
|
+
* Include entries based on regexp
|
|
40
|
+
* Exclude entries based on regexp (performed prior to include regexp)
|
|
41
|
+
* Log levels
|
|
42
|
+
* Date can be stripped for more compact log
|
|
43
|
+
|
|
44
|
+
# Serving output via web server
|
|
45
|
+
|
|
46
|
+
It is possible to serve the generated log output via a built in web server using flag `-w`:
|
|
47
|
+
|
|
48
|
+
```shell
|
|
49
|
+
$ atvlog -w pyatv.log
|
|
50
|
+
Press ENTER to quit
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Then visit `http://<ip>:8008` to see the log. The port number can be changed
|
|
54
|
+
with `-p xxxx`.
|
|
55
|
+
|
|
56
|
+
# Examples
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
$ atvlog pyatv.log # Print output to stdout
|
|
60
|
+
$ atvlog --output pyatv.html pyatv.log
|
|
61
|
+
$ cat pyatv.log | atvlog - # Read from stdin
|
|
62
|
+
$ cat markdown.log | atvlog --format=markdown -
|
|
63
|
+
```
|
|
64
|
+
|