python-aidot-cameras 1.0.0rc43__tar.gz → 1.0.0rc45__tar.gz

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.
Files changed (344) hide show
  1. {python_aidot_cameras-1.0.0rc43/python_aidot_cameras.egg-info → python_aidot_cameras-1.0.0rc45}/PKG-INFO +57 -50
  2. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/README.md +56 -49
  3. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/aac_track.py +6 -2
  4. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/client.py +115 -183
  5. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/h264_sps.py +0 -13
  6. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/hls_ts.py +12 -8
  7. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/protocol.py +95 -22
  8. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/rtsp_publish.py +23 -40
  9. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sdes_open.py +216 -387
  10. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/ts_fanout.py +23 -0
  11. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/ts_tee.py +11 -3
  12. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/webrtc_open.py +6 -18
  13. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/lan_retry.py +89 -5
  14. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/pyproject.toml +1 -1
  15. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45/python_aidot_cameras.egg-info}/PKG-INFO +57 -50
  16. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/SOURCES.txt +4 -7
  17. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_aac_track.py +1 -1
  18. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_abandoned_attempt_skips_the_doomed_serve.py +2 -27
  19. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_detection.py +3 -39
  20. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_serve.py +4 -20
  21. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_serve_only_for_http.py +1 -1
  22. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_tap_states_no_reorder.py +5 -14
  23. python_aidot_cameras-1.0.0rc45/tests/test_first_media_abandon_rules.py +116 -0
  24. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hls_ts_wiring.py +16 -11
  25. python_aidot_cameras-1.0.0rc45/tests/test_idle_release_both_loops.py +112 -0
  26. python_aidot_cameras-1.0.0rc45/tests/test_lan_login_accessors.py +85 -0
  27. python_aidot_cameras-1.0.0rc45/tests/test_lan_retry_silences_upstream_over_ceiling.py +186 -0
  28. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_stream_param.py +1 -11
  29. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_validate_policy.py +3 -3
  30. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_remb_goes_out_the_relay_aware_sender.py +1 -1
  31. python_aidot_cameras-1.0.0rc45/tests/test_removed_keepalive_arguments_are_tolerated.py +30 -0
  32. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtsp_publish.py +13 -76
  33. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_connection_mode.py +3 -5
  34. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_first_media_stall_report.py +43 -0
  35. python_aidot_cameras-1.0.0rc45/tests/test_sdes_idle_release.py +351 -0
  36. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_offer_video_pin.py +17 -6
  37. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_pinned_codec_retry.py +1 -1
  38. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ts_fanout.py +50 -2
  39. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ts_tee.py +137 -1
  40. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_presence.py +14 -0
  41. python_aidot_cameras-1.0.0rc43/tests/test_battery_skip_turn.py +0 -63
  42. python_aidot_cameras-1.0.0rc43/tests/test_battery_wake_gate_app_parity.py +0 -90
  43. python_aidot_cameras-1.0.0rc43/tests/test_bitrate_campaign_arms.py +0 -126
  44. python_aidot_cameras-1.0.0rc43/tests/test_idle_release_both_loops.py +0 -49
  45. python_aidot_cameras-1.0.0rc43/tests/test_offer_bandwidth_line.py +0 -159
  46. python_aidot_cameras-1.0.0rc43/tests/test_sdes_adaptive.py +0 -134
  47. python_aidot_cameras-1.0.0rc43/tests/test_sdes_idle_release.py +0 -157
  48. python_aidot_cameras-1.0.0rc43/tests/test_sdes_offer_video_order.py +0 -285
  49. python_aidot_cameras-1.0.0rc43/tests/test_sdes_tmmbr_bridge.py +0 -236
  50. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/LICENSE +0 -0
  51. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/MANIFEST.in +0 -0
  52. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/__init__.py +0 -0
  53. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/__main__.py +0 -0
  54. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_log_redaction.py +0 -0
  55. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_upstream.py +0 -0
  56. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/__init__.py +0 -0
  57. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/LICENSE +0 -0
  58. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  59. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  60. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  61. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  62. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  63. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  64. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  65. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  66. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  67. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  68. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  69. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  70. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  71. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  72. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  73. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  74. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  75. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  76. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  77. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  78. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  79. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  80. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  81. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  82. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  83. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  84. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  85. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  86. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  87. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  88. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  89. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  90. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/__init__.py +0 -0
  91. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/constants.py +0 -0
  92. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/controls.py +0 -0
  93. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/go2rtc.py +0 -0
  94. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/hwaccel.py +0 -0
  95. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/lan_control.py +0 -0
  96. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/models.py +0 -0
  97. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/playback.py +0 -0
  98. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/recording.py +0 -0
  99. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/rtp_h264.py +0 -0
  100. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_events.py +0 -0
  101. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_listing.py +0 -0
  102. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sd_playback.py +0 -0
  103. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/sdes.py +0 -0
  104. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/tutk.py +0 -0
  105. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/camera/webrtc.py +0 -0
  106. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/client.py +0 -0
  107. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/cloud_auth.py +0 -0
  108. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/const.py +0 -0
  109. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/credentials.py +0 -0
  110. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/crypto.py +0 -0
  111. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/device_client.py +0 -0
  112. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/discover.py +0 -0
  113. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/exceptions.py +0 -0
  114. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/g711.py +0 -0
  115. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/aidot_cameras/py.typed +0 -0
  116. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  117. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  118. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/requires.txt +0 -0
  119. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  120. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/setup.cfg +0 -0
  121. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_abandoned_wait_still_maps_audio.py +0 -0
  122. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_aioice_compat.py +0 -0
  123. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_alarm_event.py +0 -0
  124. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_pt_kinds.py +0 -0
  125. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_sdp_video_pt.py +0 -0
  126. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_answer_section_selection.py +0 -0
  127. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_arrival_timestamps.py +0 -0
  128. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_audio_mux_offline.py +0 -0
  129. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auth_error_routing.py +0 -0
  130. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auto_alarm.py +0 -0
  131. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_auto_light_settings.py +0 -0
  132. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_ctrl_uses_the_session_dseq.py +0 -0
  133. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_request.py +0 -0
  134. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_response_parse.py +0 -0
  135. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_avio_response_router.py +0 -0
  136. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_backoff.py +0 -0
  137. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_evidence_corroboration.py +0 -0
  138. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_stale_offer_is_abandoned_early.py +0 -0
  139. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_battery_wake_settles_before_the_command.py +0 -0
  140. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ble_gateway_is_gone.py +0 -0
  141. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_bridge_state_sentinels.py +0 -0
  142. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_busy_backoff.py +0 -0
  143. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_camera_properties.py +0 -0
  144. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_canary_log_names_the_camera.py +0 -0
  145. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_count.py +0 -0
  146. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_error_log_names_the_exception.py +0 -0
  147. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_error_severity.py +0 -0
  148. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_cloud_plan.py +0 -0
  149. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_constants_imports_resolve.py +0 -0
  150. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_control_verdicts.py +0 -0
  151. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_declared_properties.py +0 -0
  152. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_deferred_hardening.py +0 -0
  153. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_detection_types.py +0 -0
  154. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_cmd_seq_match.py +0 -0
  155. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_login_guard.py +0 -0
  156. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_device_user_info_cache.py +0 -0
  157. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_publish_attaches_negotiated_audio.py +0 -0
  158. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_direct_ts_keyframe_signal.py +0 -0
  159. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dormant_camera_cleanup.py +0 -0
  160. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_drain_is_cancellable.py +0 -0
  161. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_mux_terminal_write.py +0 -0
  162. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_not_ready_burst.py +0 -0
  163. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_pinning.py +0 -0
  164. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_recorder_wiring.py +0 -0
  165. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_resolution_ack.py +0 -0
  166. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_decode_skip.py +0 -0
  167. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_ice_wait.py +0 -0
  168. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_serve_open_timeout.py +0 -0
  169. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_skip_signaling_wait.py +0 -0
  170. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_slow_probe.py +0 -0
  171. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_stdout_producer.py +0 -0
  172. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_ts_copy_recording.py +0 -0
  173. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_dtls_video_presence.py +0 -0
  174. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_egress_guard.py +0 -0
  175. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_env_seams.py +0 -0
  176. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_feature_probe_needs_a_live_session.py +0 -0
  177. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ffmpeg_signal_exit_is_not_a_crash.py +0 -0
  178. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_futile_keepalive.py +0 -0
  179. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_gate_requires_a_decoded_frame.py +0 -0
  180. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_go2rtc.py +0 -0
  181. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_go2rtc_cli.py +0 -0
  182. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_h264_decode_rate_limit.py +0 -0
  183. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_h264_sps.py +0 -0
  184. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_highport_nomination.py +0 -0
  185. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hls_ts_end_to_end.py +0 -0
  186. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_hwaccel_probe.py +0 -0
  187. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_config_cache.py +0 -0
  188. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_credentials_match_the_app.py +0 -0
  189. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ice_peer_reflexive.py +0 -0
  190. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_keepalive_renew.py +0 -0
  191. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_key_restart_sdp.py +0 -0
  192. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_keyframe_prompter.py +0 -0
  193. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_control.py +0 -0
  194. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_retry_reaches_the_storming_devices.py +0 -0
  195. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_lan_sweep_only_with_lights.py +0 -0
  196. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_late_repeats_are_dropped.py +0 -0
  197. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_light_active_color_mode.py +0 -0
  198. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_session_carries_talk.py +0 -0
  199. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_validate_token_refresh.py +0 -0
  200. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_live_video_canary.py +0 -0
  201. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_liveplay_echo_timeout.py +0 -0
  202. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_local_only_controls.py +0 -0
  203. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_log_hygiene.py +0 -0
  204. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_logging_caps.py +0 -0
  205. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_login_connect_deadline.py +0 -0
  206. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_login_reconnect_backoff.py +0 -0
  207. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_counted_only_when_plaintext.py +0 -0
  208. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_path_is_reported.py +0 -0
  209. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_socket_rcvbuf.py +0 -0
  210. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_media_wait_floor.py +0 -0
  211. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_motion_poll.py +0 -0
  212. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_client_id_is_exact.py +0 -0
  213. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_credential_refetch.py +0 -0
  214. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_credential_self_heal.py +0 -0
  215. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_password_not_persisted.py +0 -0
  216. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_session_reconnect.py +0 -0
  217. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_mqtt_thread_not_orphaned.py +0 -0
  218. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nack.py +0 -0
  219. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nack_cannot_add_load_in_a_burst.py +0 -0
  220. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_narrow_pc_ice.py +0 -0
  221. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_key_material_in_logs.py +0 -0
  222. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_private_notes_on_the_public_surface.py +0 -0
  223. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_self_referential_go2rtc.py +0 -0
  224. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_unbounded_hex_dumps.py +0 -0
  225. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_no_undefined_names.py +0 -0
  226. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_nominates_as_soon_as_the_answer_lands.py +0 -0
  227. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_off_subnet_candidate.py +0 -0
  228. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_offer_keeps_rtcp_feedback.py +0 -0
  229. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_offline_keepalive_pause.py +0 -0
  230. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_on_frame_is_an_av_frame.py +0 -0
  231. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_open_fail_logger.py +0 -0
  232. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_open_gate_delay.py +0 -0
  233. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peer_id_class_env.py +0 -0
  234. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peer_id_client_type.py +0 -0
  235. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_peerid_reuse.py +0 -0
  236. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_persistent_mqtt.py +0 -0
  237. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_persistent_mqtt_robustness.py +0 -0
  238. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_playback_tls.py +0 -0
  239. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_post_merge_hardening.py +0 -0
  240. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_ptz_probe_reads_what_it_is_told.py +0 -0
  241. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_public_signature_matches_impl.py +0 -0
  242. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_publish_target_readiness.py +0 -0
  243. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_quality_campaign_in_session.py +0 -0
  244. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_reboot_action.py +0 -0
  245. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_recent_recordings.py +0 -0
  246. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_remb.py +0 -0
  247. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_resent_frames_are_dropped.py +0 -0
  248. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_resolution_persists.py +0 -0
  249. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_restart_cycle.py +0 -0
  250. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_retry_policy.py +0 -0
  251. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtp_h264.py +0 -0
  252. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_rtsp_publish_go2rtc.py +0 -0
  253. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_scripts_importable.py +0 -0
  254. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_init_ack_tsn.py +0 -0
  255. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_reassembly.py +0 -0
  256. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sctp_sack.py +0 -0
  257. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_card_presence.py +0 -0
  258. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_event_decode.py +0 -0
  259. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_event_probe_payloads.py +0 -0
  260. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_listing.py +0 -0
  261. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sd_playback.py +0 -0
  262. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_avio_dispatch.py +0 -0
  263. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_bridge_break_gate.py +0 -0
  264. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_echo_wait_is_not_dead_time.py +0 -0
  265. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_fast_liveplay.py +0 -0
  266. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  267. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_ice_teardown.py +0 -0
  268. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_nack_bridge.py +0 -0
  269. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_open_cleanup.py +0 -0
  270. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_sctp_abort.py +0 -0
  271. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_audio.py +0 -0
  272. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_cmd.py +0 -0
  273. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_serve_stderr_drain.py +0 -0
  274. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_session_cap.py +0 -0
  275. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_sprop.py +0 -0
  276. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_talk.py +0 -0
  277. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_teardown_exit_log.py +0 -0
  278. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_terminal_ack.py +0 -0
  279. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_terminal_ack_latency.py +0 -0
  280. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdes_watchdog.py +0 -0
  281. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdp_narrowing_properties.py +0 -0
  282. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sdp_payload_type_narrowing.py +0 -0
  283. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_security_hardening.py +0 -0
  284. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_self_peer_is_an_address_not_an_ip.py +0 -0
  285. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serializable_login_info.py +0 -0
  286. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_bound_survives_arrival_stamping.py +0 -0
  287. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_input_timeout.py +0 -0
  288. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_loss_tolerance.py +0 -0
  289. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_pt_respects_the_pin.py +0 -0
  290. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_relay.py +0 -0
  291. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_serve_stderr_keeps_the_reason.py +0 -0
  292. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_session_stats.py +0 -0
  293. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_session_stop_twice.py +0 -0
  294. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_shared_house_seam.py +0 -0
  295. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_signalling_is_attributable_to_a_camera.py +0 -0
  296. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_skip_guard.py +0 -0
  297. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_snapshot_cancellation.py +0 -0
  298. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_speak.py +0 -0
  299. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_capture_gate.py +0 -0
  300. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_staleness.py +0 -0
  301. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_unstable_public.py +0 -0
  302. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_sprop_write_is_reported_truthfully.py +0 -0
  303. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_srtp_rx_rekey.py +0 -0
  304. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_srtp_tx_key_note.py +0 -0
  305. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stable_terminal_id.py +0 -0
  306. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_nudge_policy.py +0 -0
  307. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_says_what_the_answer_carried.py +0 -0
  308. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_says_whether_the_trigger_was_answered.py +0 -0
  309. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_separates_silence_from_undecryptable.py +0 -0
  310. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_report_survives_a_caller_giving_up.py +0 -0
  311. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stall_reports_reach_the_artifact.py +0 -0
  312. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_status_and_trace_channels.py +0 -0
  313. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_cap.py +0 -0
  314. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_drain_ownership.py +0 -0
  315. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_idle.py +0 -0
  316. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_slot_cap.py +0 -0
  317. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_stream_teardown.py +0 -0
  318. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_talk.py +0 -0
  319. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_talk_reports_a_speaker_that_never_opened.py +0 -0
  320. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tap_to_mux_end_to_end.py +0 -0
  321. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_terminal_ack.py +0 -0
  322. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tmmbr.py +0 -0
  323. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_token_file_robustness.py +0 -0
  324. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_token_refresh.py +0 -0
  325. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_transport_failure_reporting.py +0 -0
  326. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_turn_create_permission.py +0 -0
  327. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_tutk_path_is_out_of_reach.py +0 -0
  328. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_unbuildable_summary_repeats_at_debug.py +0 -0
  329. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_unreachable_nominee_is_abandoned_early.py +0 -0
  330. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_compat.py +0 -0
  331. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_seam_adapter.py +0 -0
  332. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_upstream_secrets_redacted.py +0 -0
  333. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_validator_shares_one_token.py +0 -0
  334. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_vendored_aiortc_integrity.py +0 -0
  335. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_presence_needs_a_keyframe.py +0 -0
  336. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_profile_log.py +0 -0
  337. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_pts_dts.py +0 -0
  338. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_video_ts_unwrap.py +0 -0
  339. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_check_throttle.py +0 -0
  340. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_viewer_present_push_mode.py +0 -0
  341. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wait_or_event.py +0 -0
  342. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wake_gate_camera_evidence.py +0 -0
  343. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_wake_readiness_retry.py +0 -0
  344. {python_aidot_cameras-1.0.0rc43 → python_aidot_cameras-1.0.0rc45}/tests/test_webrtc_answer_race.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 1.0.0rc43
3
+ Version: 1.0.0rc45
4
4
  Summary: Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)
5
5
  Author-email: cbrightly <chris.brightly@gmail.com>
6
6
  License-Expression: MIT
@@ -100,11 +100,11 @@ not going to change in this release. Neither is a fault to report.
100
100
  records to the cloud or to its own SD card is per-camera, not per-model, and the
101
101
  device reports it as `IsSupportPlayback`. This library lists cloud recordings
102
102
  (`async_get_cloud_recordings`) and resolves a playable HLS URL for one
103
- (`async_get_event_video_media`); it has no SD-card equivalent, so a camera
104
- storing to a card records normally and
105
- nothing here can play it back. On the reference fleet that is four of seven
106
- cameras. The vendor's own commands for it are identified but their responses
107
- have never been decoded; see `docs/ROAD-TO-1.0.md` item 6.
103
+ (`async_get_event_video_media`); for a camera storing to a card it lists the
104
+ on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
105
+ such a camera records normally and nothing here can show you the video. On the
106
+ reference fleet that is four of seven cameras. Playback from the card is closed
107
+ as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
108
108
 
109
109
  **The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
110
110
  app.** Measured from a capture of the app on the same camera: it takes about
@@ -131,10 +131,10 @@ Two things stand between that and a saving you can switch on:
131
131
  - **We cannot select it.** Narrowing the offer to 97 does not select it, it
132
132
  removes the option - no video at all in 3 of 3 rounds (see
133
133
  `AIDOT_SDES_VIDEO_PT` below). Reordering the preference list without
134
- narrowing it (`AIDOT_SDES_VIDEO_PT_ORDER=97,96`) was then tested directly:
135
- eight sessions in blocks of two returned H265 in 2 of 4 with the reorder and
136
- 2 of 4 without, with receipts confirming the reordered list reached the SDP.
137
- The camera chooses, roughly a coin flip, and neither knob moves it.
134
+ narrowing it was then tested directly: eight sessions in blocks of two
135
+ returned H265 in 2 of 4 with the reorder and 2 of 4 without, with receipts
136
+ confirming the reordered list reached the SDP. The camera chooses, roughly
137
+ a coin flip, and neither knob moves it; the reorder knob is gone.
138
138
  - **H.265 may not suit the consumer.** Browser HEVC support over Media Source
139
139
  Extensions is far narrower than H.264, and MSE playback is the reason the
140
140
  bitrate matters here at all. Halving the bitrate is no help if the client
@@ -268,9 +268,9 @@ decides it:
268
268
 
269
269
  | your camera | use | why |
270
270
  | --- | --- | --- |
271
- | **DTLS** (mains, e.g. A000088) | `-` | keeps the mux's 48 kHz AAC. The push path has to transcode audio down to 8 kHz G.711 |
271
+ | **DTLS** (mains, e.g. A000088) | `'{output}'` or `-` | `{output}` is published by the library itself (direct publish, on by default): the camera's 8 kHz A-law plus an in-process AAC track, no ffmpeg. `-` writes the muxed MPEG-TS with its 48 kHz AAC to stdout instead |
272
272
  | **SDES** (the A001513 and A001064 families) | `'{output}'` | these stream by pushing RTSP; there is nothing to read from stdout |
273
- | either, with `AIDOT_DIRECT_PUBLISH=1` | `'{output}'` | published by the library with no ffmpeg; audio stays 8 kHz A-law. An H.265 session falls back to the ffmpeg push |
273
+ | either, with `AIDOT_DIRECT_PUBLISH=0` | `'{output}'` | the push goes through ffmpeg instead, which has to transcode audio down to 8 kHz G.711. A session that negotiates H.265 takes this path whatever the setting |
274
274
  | either, if you want to pull | an `http://` URL | the process serves there and waits for a consumer to connect |
275
275
 
276
276
  `'{output}'` is a placeholder that go2rtc substitutes with the stream's own push
@@ -301,22 +301,24 @@ go2rtc itself gets its environment: export them in the shell before starting it,
301
301
  or set them in its service unit or container definition. If a stream fails
302
302
  immediately with an authentication error, that is the thing to check first.
303
303
 
304
- **ffmpeg must be on PATH.** SDES cameras - two of the three validated models -
305
- stream entirely through an ffmpeg subprocess, and pip cannot install a system
306
- binary. DTLS cameras no longer serve through it: the muxed MPEG-TS goes straight
307
- to the consumer, because that hop was the only component in the chain that lost
308
- timestamps (see `AIDOT_DTLS_DIRECT_SERVE` below). ffmpeg is still required, both
309
- for the SDES path and as the DTLS fallback when the serve port cannot be bound.
310
-
311
- **Or skip ffmpeg for live pushes: `AIDOT_DIRECT_PUBLISH=1` (experimental).** With
312
- it set, an `rtsp://` / `{output}` push is published into go2rtc by the library
304
+ **ffmpeg must be on PATH.** pip cannot install a system binary, and the
305
+ library still needs one: recordings, snapshots, the `-` stdout form, `http://`
306
+ pull serves and any session that negotiates H.265 run through an ffmpeg
307
+ subprocess, and it is the DTLS fallback when a serve port cannot be bound. A
308
+ live `{output}` push does not use it (next paragraph), and a DTLS `-` serve
309
+ sends the muxed MPEG-TS straight to the consumer, because that hop was the only
310
+ component in the chain that lost timestamps.
311
+
312
+ **Live pushes skip ffmpeg: direct publish is on by default** (since 1.0.0rc44;
313
+ `AIDOT_DIRECT_PUBLISH=0` restores the ffmpeg path). An `rtsp://` / `{output}`
314
+ push is published into go2rtc by the library
313
315
  itself - no ffmpeg process per stream - for **both** transports, so a DTLS
314
316
  camera can use `{output}` too. It takes H.264 sessions only; a session that
315
317
  negotiates H.265 keeps the ffmpeg serve. Audio goes out as the camera's own
316
318
  G.711 A-law plus an in-process AAC track (see "Audio" under
317
319
  [Getting an RTSP URL](#getting-an-rtsp-url)).
318
- ffmpeg is still used for recordings and snapshots. Off by default while it is
319
- being validated; see [`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
320
+ ffmpeg is still used for recordings and snapshots; see
321
+ [`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
320
322
 
321
323
  ```bash
322
324
  sudo apt install ffmpeg # Debian/Ubuntu
@@ -502,14 +504,16 @@ the last one leaves - nothing talks to your cameras while nobody is watching:
502
504
 
503
505
  ```yaml
504
506
  streams:
505
- driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera
507
+ driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera (stdout form; {output} works too)
506
508
  frontdoor: exec:aidot-go2rtc 5e6f7a8b... {output} # SDES camera
507
509
  ```
508
510
 
509
- Use `-` for a DTLS camera and `{output}` for an SDES one - that is the whole
510
- difference, and `--list` told you which is which. (`{output}` is a placeholder
511
- go2rtc fills in with its own publish URL. It is not something you type at a
512
- shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
511
+ `{output}` works for both transports: the library publishes the stream into
512
+ go2rtc itself (direct publish, on by default since 1.0.0rc44). A DTLS camera
513
+ can also take `-`, which hands go2rtc the muxed MPEG-TS on stdout; an SDES
514
+ camera cannot, and `--list` told you which is which. (`{output}` is a
515
+ placeholder go2rtc fills in with its own publish URL. It is not something you
516
+ type at a shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
513
517
  variables the CLI does, so set them wherever go2rtc starts.
514
518
 
515
519
  **3. Use the URL.** go2rtc serves every stream you named at:
@@ -531,20 +535,20 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
531
535
  - **Which camera is which.** `--list` reports the transport per device. DTLS is
532
536
  the mains A000088; SDES is the A001064 / A001513 family. Passing the wrong
533
537
  form is not silent - the CLI tells you which one to use and exits.
534
- - **Audio.** Both forms carry AAC at 48 kHz mono, resampled from the camera's
535
- 8 kHz A-law, because 8 kHz AAC plays silent in a lot of browsers and browsers
536
- on the MSE path have no mapping for G.711 at all. Audio is on by default; see
537
- `AIDOT_SDES_SERVE_AUDIO` to turn it off. With `AIDOT_DIRECT_PUBLISH=1` the
538
- publish carries the camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC
539
- viewers play it natively and nothing is transcoded. With
540
- `AIDOT_PUBLISH_AAC=1` it also carries an **AAC-LC 48 kHz mono** track
541
- encoded in-process; an AAC-only consumer (for example Home Assistant's HLS
542
- player) selects it with `?audio=aac`. No ffmpeg transcode
543
- source is needed for this - see
538
+ - **Audio.** A `{output}` push (direct publish, the default) carries the
539
+ camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC viewers play it
540
+ natively and nothing is transcoded - and, unless `AIDOT_PUBLISH_AAC=0`, an
541
+ **AAC-LC 48 kHz mono** track encoded in-process; an AAC-only consumer (for
542
+ example Home Assistant's HLS player) selects it with `?audio=aac`. The `-`
543
+ form carries AAC at 48 kHz mono, resampled from the camera's 8 kHz A-law,
544
+ because 8 kHz AAC plays silent in a lot of browsers and browsers on the MSE
545
+ path have no mapping for G.711 at all. Audio is on by default; see
546
+ `AIDOT_SDES_SERVE_AUDIO` to turn it off. No ffmpeg transcode
547
+ source is needed for the AAC - see
544
548
  [`docs/CAMERAS.md`](https://github.com/cbrightly/python-aidot-cameras/blob/main/docs/CAMERAS.md#an-aac-transcoding-source-makes-every-player-crawl)
545
549
  for why the old `ffmpeg:<stream>#audio=aac` advice made playback crawl.
546
- The AAC track is off by default. A camera whose audio is mu-law (PCMU)
547
- gets no AAC track either way.
550
+ The AAC track is on by default since 1.0.0rc44. A camera whose audio is
551
+ mu-law (PCMU) gets no AAC track either way.
548
552
  - **A stream that starts with no video** and picks it up a few seconds later is
549
553
  normal: the mux waits for a keyframe so the first GOP is decodable. A stream
550
554
  that stays audio-only is a camera that never sent one - retry the view.
@@ -553,9 +557,11 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
553
557
  spawns. That process has to stay running to keep the port bound, so it holds
554
558
  a camera session whether or not anyone is watching. Prefer the `exec:` forms.
555
559
  - **Pushing to your own RTSP server** rather than go2rtc: `rtsp_push_url=`
556
- from Python, or an `rtsp://` output on the CLI. Both transports support it.
557
- A DTLS push transcodes audio to G.711 A-law, matching what SDES has always
558
- sent - ffmpeg's RTSP muxer will not accept the AAC that MPEG-TS carries.
560
+ from Python, or an `rtsp://` output on the CLI. Both transports support it,
561
+ and by default the library publishes it directly, with the audio described
562
+ above. Only on the `AIDOT_DIRECT_PUBLISH=0` ffmpeg path does a DTLS push
563
+ transcode audio to G.711 A-law - ffmpeg's RTSP muxer will not accept the AAC
564
+ that MPEG-TS carries.
559
565
  `output_path=` records to a file. `Go2RtcClient.rtsp_url(name)` builds the
560
566
  address above, and `ensure_stream(name, source)` registers a stream with a
561
567
  running go2rtc if you would rather not edit YAML.
@@ -582,7 +588,7 @@ The library reads the following environment variables.
582
588
 
583
589
  ### Credentials
584
590
 
585
- Used by the credential helper (`aidot.credentials`); they take priority over any
591
+ Used by the credential helper (`aidot_cameras.credentials`); they take priority over any
586
592
  stored credentials file. See [`aidot_cameras/credentials.py`](https://github.com/cbrightly/python-aidot-cameras/blob/main/aidot_cameras/credentials.py).
587
593
 
588
594
  | Variable | Purpose | Default |
@@ -603,13 +609,13 @@ knob is in
603
609
  | `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
604
610
  | `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
605
611
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
606
- | `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all - same lever as `AIDOT_SDES_SKIP_TURN_PREALLOC`, and battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
607
- | `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | unset (both offered) |
612
+ | `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all, though battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
613
+ | `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | `96` (H.264; `none` leaves the camera its choice) |
608
614
  | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
609
- | `AIDOT_DIRECT_PUBLISH` | **Experimental, opt-in.** Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve (see `AIDOT_DIRECT_PUBLISH_H265`). Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | unset (off) |
610
- | `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. Off unless set: `0`/`false`/`no`/`off` (or unset) leaves it off; any other value turns it on. | `0` (off) |
611
- | `AIDOT_HLS_DIRECT_TS` | **Opt-in.** Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | unset (off) |
612
- | `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone (it is retried again the next time something asks for it). Applies to every device, camera or not - the devices that hit this are lights. | `6` |
615
+ | `AIDOT_DIRECT_PUBLISH` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve. Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
616
+ | `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. **On by default** (since 1.0.0rc44); set `0`/`false`/`no`/`off` to turn it off. | `1` (on) |
617
+ | `AIDOT_HLS_DIRECT_TS` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
618
+ | `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone: discovery and the library's own retry loop stop kicking it, and when something else still asks for a login (Home Assistant's light poll does, every five minutes) the attempt is made quietly - upstream's per-attempt warning is dropped - until the device's address changes or a login gets through. One warning marks the moment. Applies to every device, camera or not - the devices that hit this are lights on another subnet. | `6` |
613
619
 
614
620
  ### Security hardening
615
621
 
@@ -622,3 +628,4 @@ the permissive default.
622
628
  | --- | --- | --- |
623
629
  | `AIDOT_ALLOW_LAN_SERVE` | Silences the warning emitted when decrypted media is served on a non-loopback bind (e.g. `0.0.0.0`), where any host on the LAN can read the unencrypted stream. Set when an exposed bind is intentional. | unset (warn on non-loopback) |
624
630
  | `AIDOT_CRED_KEY_FILE` | Path to the Fernet key file for stored credentials. Point it outside the config dir (ideally a separate secret store) so the key isn't co-located with the ciphertext. Applies to the default credentials path only (ignored when an explicit `creds_path` is passed). | `$XDG_CONFIG_HOME/aidot/.key` (falls back to `~/.config/aidot/.key`) |
631
+ | `AIDOT_PLAYBACK_TLS_VERIFY` | Set to `1` to require full certificate and hostname verification on the TLS connection to a camera's playback server. The camera presents a self-signed certificate, so this needs a trust anchor it chains to; unset, verification is skipped and a one-time warning is logged. | unset (skip verification, warn once) |
@@ -60,11 +60,11 @@ not going to change in this release. Neither is a fault to report.
60
60
  records to the cloud or to its own SD card is per-camera, not per-model, and the
61
61
  device reports it as `IsSupportPlayback`. This library lists cloud recordings
62
62
  (`async_get_cloud_recordings`) and resolves a playable HLS URL for one
63
- (`async_get_event_video_media`); it has no SD-card equivalent, so a camera
64
- storing to a card records normally and
65
- nothing here can play it back. On the reference fleet that is four of seven
66
- cameras. The vendor's own commands for it are identified but their responses
67
- have never been decoded; see `docs/ROAD-TO-1.0.md` item 6.
63
+ (`async_get_event_video_media`); for a camera storing to a card it lists the
64
+ on-device recordings (`async_get_sd_recordings`) but cannot play one back, so
65
+ such a camera records normally and nothing here can show you the video. On the
66
+ reference fleet that is four of seven cameras. Playback from the card is closed
67
+ as not feasible; see `docs/CAMERAS.md`, "On-device (SD card) recordings".
68
68
 
69
69
  **The A001064 (PTZ) streams at roughly 2.2 Mbps, about 24% above the vendor
70
70
  app.** Measured from a capture of the app on the same camera: it takes about
@@ -91,10 +91,10 @@ Two things stand between that and a saving you can switch on:
91
91
  - **We cannot select it.** Narrowing the offer to 97 does not select it, it
92
92
  removes the option - no video at all in 3 of 3 rounds (see
93
93
  `AIDOT_SDES_VIDEO_PT` below). Reordering the preference list without
94
- narrowing it (`AIDOT_SDES_VIDEO_PT_ORDER=97,96`) was then tested directly:
95
- eight sessions in blocks of two returned H265 in 2 of 4 with the reorder and
96
- 2 of 4 without, with receipts confirming the reordered list reached the SDP.
97
- The camera chooses, roughly a coin flip, and neither knob moves it.
94
+ narrowing it was then tested directly: eight sessions in blocks of two
95
+ returned H265 in 2 of 4 with the reorder and 2 of 4 without, with receipts
96
+ confirming the reordered list reached the SDP. The camera chooses, roughly
97
+ a coin flip, and neither knob moves it; the reorder knob is gone.
98
98
  - **H.265 may not suit the consumer.** Browser HEVC support over Media Source
99
99
  Extensions is far narrower than H.264, and MSE playback is the reason the
100
100
  bitrate matters here at all. Halving the bitrate is no help if the client
@@ -228,9 +228,9 @@ decides it:
228
228
 
229
229
  | your camera | use | why |
230
230
  | --- | --- | --- |
231
- | **DTLS** (mains, e.g. A000088) | `-` | keeps the mux's 48 kHz AAC. The push path has to transcode audio down to 8 kHz G.711 |
231
+ | **DTLS** (mains, e.g. A000088) | `'{output}'` or `-` | `{output}` is published by the library itself (direct publish, on by default): the camera's 8 kHz A-law plus an in-process AAC track, no ffmpeg. `-` writes the muxed MPEG-TS with its 48 kHz AAC to stdout instead |
232
232
  | **SDES** (the A001513 and A001064 families) | `'{output}'` | these stream by pushing RTSP; there is nothing to read from stdout |
233
- | either, with `AIDOT_DIRECT_PUBLISH=1` | `'{output}'` | published by the library with no ffmpeg; audio stays 8 kHz A-law. An H.265 session falls back to the ffmpeg push |
233
+ | either, with `AIDOT_DIRECT_PUBLISH=0` | `'{output}'` | the push goes through ffmpeg instead, which has to transcode audio down to 8 kHz G.711. A session that negotiates H.265 takes this path whatever the setting |
234
234
  | either, if you want to pull | an `http://` URL | the process serves there and waits for a consumer to connect |
235
235
 
236
236
  `'{output}'` is a placeholder that go2rtc substitutes with the stream's own push
@@ -261,22 +261,24 @@ go2rtc itself gets its environment: export them in the shell before starting it,
261
261
  or set them in its service unit or container definition. If a stream fails
262
262
  immediately with an authentication error, that is the thing to check first.
263
263
 
264
- **ffmpeg must be on PATH.** SDES cameras - two of the three validated models -
265
- stream entirely through an ffmpeg subprocess, and pip cannot install a system
266
- binary. DTLS cameras no longer serve through it: the muxed MPEG-TS goes straight
267
- to the consumer, because that hop was the only component in the chain that lost
268
- timestamps (see `AIDOT_DTLS_DIRECT_SERVE` below). ffmpeg is still required, both
269
- for the SDES path and as the DTLS fallback when the serve port cannot be bound.
270
-
271
- **Or skip ffmpeg for live pushes: `AIDOT_DIRECT_PUBLISH=1` (experimental).** With
272
- it set, an `rtsp://` / `{output}` push is published into go2rtc by the library
264
+ **ffmpeg must be on PATH.** pip cannot install a system binary, and the
265
+ library still needs one: recordings, snapshots, the `-` stdout form, `http://`
266
+ pull serves and any session that negotiates H.265 run through an ffmpeg
267
+ subprocess, and it is the DTLS fallback when a serve port cannot be bound. A
268
+ live `{output}` push does not use it (next paragraph), and a DTLS `-` serve
269
+ sends the muxed MPEG-TS straight to the consumer, because that hop was the only
270
+ component in the chain that lost timestamps.
271
+
272
+ **Live pushes skip ffmpeg: direct publish is on by default** (since 1.0.0rc44;
273
+ `AIDOT_DIRECT_PUBLISH=0` restores the ffmpeg path). An `rtsp://` / `{output}`
274
+ push is published into go2rtc by the library
273
275
  itself - no ffmpeg process per stream - for **both** transports, so a DTLS
274
276
  camera can use `{output}` too. It takes H.264 sessions only; a session that
275
277
  negotiates H.265 keeps the ffmpeg serve. Audio goes out as the camera's own
276
278
  G.711 A-law plus an in-process AAC track (see "Audio" under
277
279
  [Getting an RTSP URL](#getting-an-rtsp-url)).
278
- ffmpeg is still used for recordings and snapshots. Off by default while it is
279
- being validated; see [`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
280
+ ffmpeg is still used for recordings and snapshots; see
281
+ [`docs/DESIGN-direct-publish.md`](docs/DESIGN-direct-publish.md).
280
282
 
281
283
  ```bash
282
284
  sudo apt install ffmpeg # Debian/Ubuntu
@@ -462,14 +464,16 @@ the last one leaves - nothing talks to your cameras while nobody is watching:
462
464
 
463
465
  ```yaml
464
466
  streams:
465
- driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera
467
+ driveway: exec:aidot-go2rtc 1a2b3c4d... - # DTLS camera (stdout form; {output} works too)
466
468
  frontdoor: exec:aidot-go2rtc 5e6f7a8b... {output} # SDES camera
467
469
  ```
468
470
 
469
- Use `-` for a DTLS camera and `{output}` for an SDES one - that is the whole
470
- difference, and `--list` told you which is which. (`{output}` is a placeholder
471
- go2rtc fills in with its own publish URL. It is not something you type at a
472
- shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
471
+ `{output}` works for both transports: the library publishes the stream into
472
+ go2rtc itself (direct publish, on by default since 1.0.0rc44). A DTLS camera
473
+ can also take `-`, which hands go2rtc the muxed MPEG-TS on stdout; an SDES
474
+ camera cannot, and `--list` told you which is which. (`{output}` is a
475
+ placeholder go2rtc fills in with its own publish URL. It is not something you
476
+ type at a shell; `-` is the form for that.) go2rtc needs the same `AIDOT_*` environment
473
477
  variables the CLI does, so set them wherever go2rtc starts.
474
478
 
475
479
  **3. Use the URL.** go2rtc serves every stream you named at:
@@ -491,20 +495,20 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
491
495
  - **Which camera is which.** `--list` reports the transport per device. DTLS is
492
496
  the mains A000088; SDES is the A001064 / A001513 family. Passing the wrong
493
497
  form is not silent - the CLI tells you which one to use and exits.
494
- - **Audio.** Both forms carry AAC at 48 kHz mono, resampled from the camera's
495
- 8 kHz A-law, because 8 kHz AAC plays silent in a lot of browsers and browsers
496
- on the MSE path have no mapping for G.711 at all. Audio is on by default; see
497
- `AIDOT_SDES_SERVE_AUDIO` to turn it off. With `AIDOT_DIRECT_PUBLISH=1` the
498
- publish carries the camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC
499
- viewers play it natively and nothing is transcoded. With
500
- `AIDOT_PUBLISH_AAC=1` it also carries an **AAC-LC 48 kHz mono** track
501
- encoded in-process; an AAC-only consumer (for example Home Assistant's HLS
502
- player) selects it with `?audio=aac`. No ffmpeg transcode
503
- source is needed for this - see
498
+ - **Audio.** A `{output}` push (direct publish, the default) carries the
499
+ camera's own **PCMA (G.711 A-law, 8 kHz)** first - WebRTC viewers play it
500
+ natively and nothing is transcoded - and, unless `AIDOT_PUBLISH_AAC=0`, an
501
+ **AAC-LC 48 kHz mono** track encoded in-process; an AAC-only consumer (for
502
+ example Home Assistant's HLS player) selects it with `?audio=aac`. The `-`
503
+ form carries AAC at 48 kHz mono, resampled from the camera's 8 kHz A-law,
504
+ because 8 kHz AAC plays silent in a lot of browsers and browsers on the MSE
505
+ path have no mapping for G.711 at all. Audio is on by default; see
506
+ `AIDOT_SDES_SERVE_AUDIO` to turn it off. No ffmpeg transcode
507
+ source is needed for the AAC - see
504
508
  [`docs/CAMERAS.md`](https://github.com/cbrightly/python-aidot-cameras/blob/main/docs/CAMERAS.md#an-aac-transcoding-source-makes-every-player-crawl)
505
509
  for why the old `ffmpeg:<stream>#audio=aac` advice made playback crawl.
506
- The AAC track is off by default. A camera whose audio is mu-law (PCMU)
507
- gets no AAC track either way.
510
+ The AAC track is on by default since 1.0.0rc44. A camera whose audio is
511
+ mu-law (PCMU) gets no AAC track either way.
508
512
  - **A stream that starts with no video** and picks it up a few seconds later is
509
513
  normal: the mux waits for a keyframe so the first GOP is decodable. A stream
510
514
  that stays audio-only is a camera that never sent one - retry the view.
@@ -513,9 +517,11 @@ Stream #0:1: Audio: aac (LC), 48000 Hz, mono
513
517
  spawns. That process has to stay running to keep the port bound, so it holds
514
518
  a camera session whether or not anyone is watching. Prefer the `exec:` forms.
515
519
  - **Pushing to your own RTSP server** rather than go2rtc: `rtsp_push_url=`
516
- from Python, or an `rtsp://` output on the CLI. Both transports support it.
517
- A DTLS push transcodes audio to G.711 A-law, matching what SDES has always
518
- sent - ffmpeg's RTSP muxer will not accept the AAC that MPEG-TS carries.
520
+ from Python, or an `rtsp://` output on the CLI. Both transports support it,
521
+ and by default the library publishes it directly, with the audio described
522
+ above. Only on the `AIDOT_DIRECT_PUBLISH=0` ffmpeg path does a DTLS push
523
+ transcode audio to G.711 A-law - ffmpeg's RTSP muxer will not accept the AAC
524
+ that MPEG-TS carries.
519
525
  `output_path=` records to a file. `Go2RtcClient.rtsp_url(name)` builds the
520
526
  address above, and `ensure_stream(name, source)` registers a stream with a
521
527
  running go2rtc if you would rather not edit YAML.
@@ -542,7 +548,7 @@ The library reads the following environment variables.
542
548
 
543
549
  ### Credentials
544
550
 
545
- Used by the credential helper (`aidot.credentials`); they take priority over any
551
+ Used by the credential helper (`aidot_cameras.credentials`); they take priority over any
546
552
  stored credentials file. See [`aidot_cameras/credentials.py`](https://github.com/cbrightly/python-aidot-cameras/blob/main/aidot_cameras/credentials.py).
547
553
 
548
554
  | Variable | Purpose | Default |
@@ -563,13 +569,13 @@ knob is in
563
569
  | `AIDOT_VIDEO_DECODER` | Force a video decoder instead of measuring one: a decoder name (`h264_v4l2m2m`), or an acceleration method prefixed with `hwaccel:` (`hwaccel:videotoolbox`). The prefix is required because the two are not interchangeable - VideoToolbox and VAAPI have no decoder to name. | (measured) |
564
570
  | `AIDOT_DISABLE_HWACCEL` | Keep to software decoding and skip the measurement entirely. | (unset) |
565
571
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
566
- | `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all - same lever as `AIDOT_SDES_SKIP_TURN_PREALLOC`, and battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
567
- | `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | unset (both offered) |
572
+ | `AIDOT_SDES_CONNECTION_MODE` | Which media path the SDES offer proposes: `auto` (default - every reachable candidate, and ICE priority prefers the LAN with the relay as last resort; measured on the full fleet, six of seven cameras stream direct and only the unit with no route to us rides the relay), `lan` (no relay at all, though battery cameras keep the relay regardless because a cloud-woken camera has no other path back), or `relay` (EXPERIMENTAL, and on the reference fleet it does not steer: even with `c=`/`m=` moved to the relay allocation and the WAN permission pre-installed, an A001064 and an A001513 both dialed our host address directly, learning it from our own ICE probes - forcing the relay for real would mean suppressing every direct-path outbound, which is unbuilt). Per-open `sdes_connection_mode` beats the env. Every session reports the path its media actually took as `media_stats().media_path` (`direct`/`relay`). | unset (`auto`) |
573
+ | `AIDOT_SDES_VIDEO_PT` | Pin the SDES offer to ONE video codec by payload type, so the camera cannot choose. The offer advertises 96 (H264) and 97 (H265) and the camera decides which to send; on an A001064 that means the same request comes back h264 1280x720 most sessions and hevc 2560x1440 occasionally, at a third of the bitrate. Set to `96` for H264 only (measured h264 720p in 4 of 4 sessions). **Do not set it to `97`** - an H265-only offer returned no video at all in 3 of 3 rounds; narrowing to H265 removes the option rather than selecting it. | `96` (H.264; `none` leaves the camera its choice) |
568
574
  | `AIDOT_PERSISTENT_MQTT` | Reuse ONE account-level persistent MQTT connection for commands, attribute fetches, and stream-open signaling (matching the official app) instead of connecting per operation. **On by default** (live soaks cut SDES NO_MEDIA from ~57% to ~11-19%); set to `0`/`false`/`no`/`off` to disable. | enabled (on) |
569
- | `AIDOT_DIRECT_PUBLISH` | **Experimental, opt-in.** Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve (see `AIDOT_DIRECT_PUBLISH_H265`). Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | unset (off) |
570
- | `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. Off unless set: `0`/`false`/`no`/`off` (or unset) leaves it off; any other value turns it on. | `0` (off) |
571
- | `AIDOT_HLS_DIRECT_TS` | **Opt-in.** Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | unset (off) |
572
- | `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone (it is retried again the next time something asks for it). Applies to every device, camera or not - the devices that hit this are lights. | `6` |
575
+ | `AIDOT_DIRECT_PUBLISH` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Publish a live `rtsp://` push into go2rtc from the library itself instead of through ffmpeg, for both transports: SDES hands the bridge's plain RTP straight to an RTSP publisher, DTLS packetizes the tapped H.264 and A-law directly. No ffmpeg process per stream, audio is PCMA plus an in-process AAC track, packets put back in order before publishing, and audio attached from the camera's negotiated answer rather than waiting to observe a packet. Recordings, snapshots and `-`/`http://` serves are unchanged. Takes H.264 sessions only - an H.265 session keeps the ffmpeg serve. Live-validated with H.264 on A000088, A001064 and A001513 (see `docs/DESIGN-direct-publish.md`). Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
576
+ | `AIDOT_PUBLISH_AAC` | Whether a direct publish adds an AAC-LC 48 kHz mono track after the A-law one, encoded in-process, so an AAC-only consumer (Home Assistant's HLS player, `camera.record`) has sound; select it with go2rtc's `?audio=aac`. Costs one AAC encode per streaming camera for as long as its direct publish runs. A camera whose audio is mu-law (PCMU) gets no AAC track regardless. **On by default** (since 1.0.0rc44); set `0`/`false`/`no`/`off` to turn it off. | `1` (on) |
577
+ | `AIDOT_HLS_DIRECT_TS` | **On by default** (since 1.0.0rc44; set `0` to turn it off). Give each DTLS camera a library-muxed MPEG-TS on a loopback listener (`CameraMixin.hls_ts_url()`), for Home Assistant's HLS view and recordings to read instead of go2rtc's RTSP. go2rtc re-bases each track for each consumer, so a reader that joins a running stream had its sound 0.1-0.75 s late, differently each time; this TS carries the publisher's video and AAC on one clock, so every reader gets them in the same step, whenever it joins. Needs `AIDOT_DIRECT_PUBLISH` and `AIDOT_PUBLISH_AAC`. An SDES camera gets a URL only on a model whose media the bridge decrypts (A001064, A001513) with its offer pinned to H.264 (`AIDOT_SDES_VIDEO_PT=96`), so that every session feeds it; pulled (non-publishing) sessions get none. When a camera's SPS changes (the A001064 changes it between sessions), the TS's readers are disconnected so they rebuild their decoder. Each camera's URL carries a secret made per listener, as `?auth=<secret>` (Home Assistant masks that query parameter when it logs a stream's URL); a request without it gets 404. For the owner's own tools, the listener's URL for any camera (`http://127.0.0.1:<port>/{name}.ts?auth=<secret>`, where `{name}` is `aidot_<first 12 of the device id>`) is left in `hls-ts-url` in the library's state directory (`AIDOT_SPROP_DIR`), owner-readable only, while the listener runs. A reader of the TS counts as a viewer. Truthy (`1`/`true`/`yes`/`on`) enables. | `1` (on) |
578
+ | `AIDOT_LOGIN_RETRY_LIMIT` | Consecutive failed LAN logins after which a device is left alone: discovery and the library's own retry loop stop kicking it, and when something else still asks for a login (Home Assistant's light poll does, every five minutes) the attempt is made quietly - upstream's per-attempt warning is dropped - until the device's address changes or a login gets through. One warning marks the moment. Applies to every device, camera or not - the devices that hit this are lights on another subnet. | `6` |
573
579
 
574
580
  ### Security hardening
575
581
 
@@ -582,3 +588,4 @@ the permissive default.
582
588
  | --- | --- | --- |
583
589
  | `AIDOT_ALLOW_LAN_SERVE` | Silences the warning emitted when decrypted media is served on a non-loopback bind (e.g. `0.0.0.0`), where any host on the LAN can read the unencrypted stream. Set when an exposed bind is intentional. | unset (warn on non-loopback) |
584
590
  | `AIDOT_CRED_KEY_FILE` | Path to the Fernet key file for stored credentials. Point it outside the config dir (ideally a separate secret store) so the key isn't co-located with the ciphertext. Applies to the default credentials path only (ignored when an explicit `creds_path` is passed). | `$XDG_CONFIG_HOME/aidot/.key` (falls back to `~/.config/aidot/.key`) |
591
+ | `AIDOT_PLAYBACK_TLS_VERIFY` | Set to `1` to require full certificate and hostname verification on the TLS connection to a camera's playback server. The camera presents a self-signed certificate, so this needs a trust anchor it chains to; unset, verification is skipped and a one-time warning is logged. | unset (skip verification, warn once) |
@@ -70,8 +70,12 @@ _OFF = ("0", "false", "no", "off")
70
70
 
71
71
 
72
72
  def publish_aac_enabled() -> bool:
73
- """Whether the direct publish adds the AAC track (default off: opt in)."""
74
- return os.environ.get(ENV_PUBLISH_AAC, "0").strip().lower() not in _OFF
73
+ """Whether the direct publish adds the AAC track (on unless turned off).
74
+
75
+ On by default since 1.0.0rc44: it is what gives an AAC-only consumer
76
+ (Home Assistant's HLS player, ``camera.record``) sound at all.
77
+ """
78
+ return os.environ.get(ENV_PUBLISH_AAC, "1").strip().lower() not in _OFF
75
79
 
76
80
 
77
81
  def audio_specific_config(