python-aidot-cameras 0.15.6__tar.gz → 0.15.8__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 (178) hide show
  1. {python_aidot_cameras-0.15.6/python_aidot_cameras.egg-info → python_aidot_cameras-0.15.8}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/client.py +30 -1
  3. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/sdes_open.py +10 -1
  4. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/pyproject.toml +1 -1
  5. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  6. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/python_aidot_cameras.egg-info/SOURCES.txt +2 -0
  7. python_aidot_cameras-0.15.8/tests/test_bridge_state_sentinels.py +68 -0
  8. python_aidot_cameras-0.15.8/tests/test_serve_loss_tolerance.py +70 -0
  9. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/LICENSE +0 -0
  10. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/MANIFEST.in +0 -0
  11. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/README.md +0 -0
  12. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/__init__.py +0 -0
  13. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/__main__.py +0 -0
  14. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_upstream.py +0 -0
  15. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/__init__.py +0 -0
  16. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
  17. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
  18. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
  19. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
  20. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
  21. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
  22. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
  23. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
  24. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
  25. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
  26. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
  27. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
  28. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/events.py +0 -0
  29. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
  30. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
  31. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
  32. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
  33. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
  34. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
  35. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
  36. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
  37. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
  38. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
  39. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
  40. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
  41. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
  42. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
  43. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
  44. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
  45. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
  46. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
  47. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
  48. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/ble_gateway.py +0 -0
  49. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/__init__.py +0 -0
  50. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/constants.py +0 -0
  51. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/controls.py +0 -0
  52. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/go2rtc.py +0 -0
  53. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/hwaccel.py +0 -0
  54. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/lan_control.py +0 -0
  55. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/models.py +0 -0
  56. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/playback.py +0 -0
  57. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/protocol.py +0 -0
  58. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/sdes.py +0 -0
  59. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/tutk.py +0 -0
  60. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/webrtc.py +0 -0
  61. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/camera/webrtc_open.py +0 -0
  62. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/client.py +0 -0
  63. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/const.py +0 -0
  64. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/credentials.py +0 -0
  65. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/crypto.py +0 -0
  66. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/device_client.py +0 -0
  67. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/discover.py +0 -0
  68. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/exceptions.py +0 -0
  69. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/g711.py +0 -0
  70. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/aidot_cameras/py.typed +0 -0
  71. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  72. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
  73. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/python_aidot_cameras.egg-info/requires.txt +0 -0
  74. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/python_aidot_cameras.egg-info/top_level.txt +0 -0
  75. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/setup.cfg +0 -0
  76. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_aioice_compat.py +0 -0
  77. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_alarm_event.py +0 -0
  78. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_answer_sdp_video_pt.py +0 -0
  79. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_audio_mux_offline.py +0 -0
  80. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_backoff.py +0 -0
  81. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_battery_detection.py +0 -0
  82. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_battery_evidence_corroboration.py +0 -0
  83. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_battery_skip_turn.py +0 -0
  84. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_ble_gateway.py +0 -0
  85. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_constants_imports_resolve.py +0 -0
  86. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_deferred_hardening.py +0 -0
  87. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_device_login_guard.py +0 -0
  88. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_device_user_info_cache.py +0 -0
  89. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dormant_camera_cleanup.py +0 -0
  90. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_mux_terminal_write.py +0 -0
  91. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_not_ready_burst.py +0 -0
  92. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_pinning.py +0 -0
  93. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_serve_decode_skip.py +0 -0
  94. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_serve_open_timeout.py +0 -0
  95. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_skip_signaling_wait.py +0 -0
  96. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_slow_probe.py +0 -0
  97. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_dtls_stdout_producer.py +0 -0
  98. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_egress_guard.py +0 -0
  99. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_env_seams.py +0 -0
  100. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_go2rtc.py +0 -0
  101. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_go2rtc_cli.py +0 -0
  102. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_h264_decode_rate_limit.py +0 -0
  103. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_highport_nomination.py +0 -0
  104. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_hwaccel_probe.py +0 -0
  105. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_ice_config_cache.py +0 -0
  106. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_ice_peer_reflexive.py +0 -0
  107. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_idle_release_both_loops.py +0 -0
  108. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_keepalive_renew.py +0 -0
  109. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_key_restart_sdp.py +0 -0
  110. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_keyframe_prompter.py +0 -0
  111. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_lan_control.py +0 -0
  112. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_light_active_color_mode.py +0 -0
  113. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_live_stream_param.py +0 -0
  114. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_live_validate_policy.py +0 -0
  115. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_logging_caps.py +0 -0
  116. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_media_socket_rcvbuf.py +0 -0
  117. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_media_wait_floor.py +0 -0
  118. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_motion_poll.py +0 -0
  119. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_mqtt_credential_refetch.py +0 -0
  120. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_mqtt_credential_self_heal.py +0 -0
  121. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_mqtt_password_not_persisted.py +0 -0
  122. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_mqtt_session_reconnect.py +0 -0
  123. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_narrow_pc_ice.py +0 -0
  124. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_no_self_referential_go2rtc.py +0 -0
  125. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_no_undefined_names.py +0 -0
  126. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_offline_keepalive_pause.py +0 -0
  127. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_open_fail_logger.py +0 -0
  128. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_open_gate_delay.py +0 -0
  129. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_peerid_reuse.py +0 -0
  130. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_persistent_mqtt.py +0 -0
  131. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_persistent_mqtt_robustness.py +0 -0
  132. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_playback_tls.py +0 -0
  133. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_post_merge_hardening.py +0 -0
  134. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_resolution_persists.py +0 -0
  135. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_restart_cycle.py +0 -0
  136. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_retry_policy.py +0 -0
  137. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_scripts_importable.py +0 -0
  138. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_adaptive.py +0 -0
  139. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_bridge_break_gate.py +0 -0
  140. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_echo_wait_timeout.py +0 -0
  141. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_fast_liveplay.py +0 -0
  142. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
  143. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_idle_release.py +0 -0
  144. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_open_cleanup.py +0 -0
  145. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_serve_audio.py +0 -0
  146. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_serve_cmd.py +0 -0
  147. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_serve_stderr_drain.py +0 -0
  148. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_sprop.py +0 -0
  149. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_talk.py +0 -0
  150. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_teardown_exit_log.py +0 -0
  151. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_terminal_ack.py +0 -0
  152. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_terminal_ack_latency.py +0 -0
  153. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdes_watchdog.py +0 -0
  154. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdp_narrowing_properties.py +0 -0
  155. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sdp_payload_type_narrowing.py +0 -0
  156. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_security_hardening.py +0 -0
  157. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_serializable_login_info.py +0 -0
  158. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_serve_relay.py +0 -0
  159. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_session_stats.py +0 -0
  160. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_shared_house_seam.py +0 -0
  161. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_speak.py +0 -0
  162. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_sprop_staleness.py +0 -0
  163. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_stream_cap.py +0 -0
  164. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_stream_drain_ownership.py +0 -0
  165. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_stream_idle.py +0 -0
  166. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_stream_slot_cap.py +0 -0
  167. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_stream_teardown.py +0 -0
  168. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_talk.py +0 -0
  169. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_terminal_ack.py +0 -0
  170. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_token_file_robustness.py +0 -0
  171. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_token_refresh.py +0 -0
  172. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_turn_create_permission.py +0 -0
  173. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_upstream_compat.py +0 -0
  174. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_viewer_check_throttle.py +0 -0
  175. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_viewer_presence.py +0 -0
  176. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_viewer_present_push_mode.py +0 -0
  177. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_wait_or_event.py +0 -0
  178. {python_aidot_cameras-0.15.6 → python_aidot_cameras-0.15.8}/tests/test_wake_readiness_retry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.15.6
3
+ Version: 0.15.8
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
@@ -365,6 +365,18 @@ def _is_self_referential_source(source: str, stream_name: str) -> bool:
365
365
  return path == stream_name
366
366
 
367
367
 
368
+ #: Packets the serve's demuxer may hold while it waits for a gap to fill. A
369
+ #: keyframe on an A001064 is 146-190 KB - roughly 130 packets - so anything
370
+ #: smaller than a few hundred cannot ride out a burst that arrives out of order.
371
+ #: Env-overridable because the right value depends on the link, not on us.
372
+ _SERVE_REORDER_QUEUE = int(os.environ.get("AIDOT_SERVE_REORDER_QUEUE", "500"))
373
+
374
+ #: How long (microseconds) the demuxer waits for a missing packet before moving
375
+ #: on. Bounded on purpose: waiting forever turns loss into a stall, which is the
376
+ #: failure this is meant to prevent.
377
+ _SERVE_MAX_DELAY_US = int(os.environ.get("AIDOT_SERVE_MAX_DELAY_US", "500000"))
378
+
379
+
368
380
  def _build_sdes_serve_cmd(
369
381
  *,
370
382
  sdp_path: str,
@@ -470,9 +482,26 @@ def _build_sdes_serve_cmd(
470
482
  # 2 s analyzeduration: the camera sends SPS+PPS+IDR in the first burst;
471
483
  # 15 s consumed nearly all packets during analysis. PLI re-arms an IDR
472
484
  # every 5 s so parameters always re-arrive.
473
- "-fflags", "+nobuffer+genpts",
485
+ #
486
+ # ``discardcorrupt`` is the loss half of this: both ends of this path are
487
+ # wireless (the camera, and typically the host), so RTP arrives with real
488
+ # gaps. Measured on the reference install: `RTP: missed N packets` with
489
+ # ZERO drops on every socket in the path, which means the packets were
490
+ # lost before they ever reached the box. Without this ffmpeg passes the
491
+ # damaged frame downstream and then backs its input queue up behind the
492
+ # reordering it thinks it needs - "max delay reached. need to consume
493
+ # packet" - and the serve eventually dies, taking every viewer with it.
494
+ # A brief artefact is a far better outcome than a dropped stream.
495
+ "-fflags", "+nobuffer+genpts+discardcorrupt",
474
496
  "-analyzeduration", "2000000",
475
497
  "-probesize", "500000",
498
+ # Reordering headroom. The default (-1) leaves the demuxer with almost
499
+ # none, so a burst that arrives out of order reads as loss. A keyframe
500
+ # here is 146-190 KB - about 130 packets - so the queue has to be able to
501
+ # hold more than one of them, and max_delay bounds how long it will wait
502
+ # before giving up on a gap rather than stalling the pipeline.
503
+ "-reorder_queue_size", str(_SERVE_REORDER_QUEUE),
504
+ "-max_delay", str(_SERVE_MAX_DELAY_US),
476
505
  "-i", sdp_path,
477
506
  *dest_args,
478
507
  ]
@@ -3637,7 +3637,16 @@ class _SdesOpenMixin:
3637
3637
  if not _sdes_probe_received:
3638
3638
  _sdes_probe_received = True
3639
3639
  _last_hb_ts = _time_br.time() # HB fires in 10s
3640
- if not hasattr(_bridge_fn, '_tutk_seq'):
3640
+ # Guard on a counter that is actually assigned.
3641
+ # This used to test `_tutk_seq`, a name nothing
3642
+ # ever set, so hasattr was False on EVERY packet
3643
+ # and both counters were reset to 0 each time -
3644
+ # every synthesized packet went out with
3645
+ # sequence number 1. ffmpeg reads a constant
3646
+ # sequence as a stream of discontinuities and
3647
+ # reports `RTP: missed N packets` for loss that
3648
+ # never happened.
3649
+ if not hasattr(_bridge_fn, '_tutk_seq_v'):
3641
3650
  _bridge_fn._tutk_seq_a = 0
3642
3651
  _bridge_fn._tutk_seq_v = 0
3643
3652
  if _tk_audio:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.15.6"
7
+ version = "0.15.8"
8
8
  description = "Control AiDot/Leedarson WiFi lights and cameras (WebRTC streaming, two-way audio, PTZ, controls)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.15.6
3
+ Version: 0.15.8
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
@@ -78,6 +78,7 @@ tests/test_battery_detection.py
78
78
  tests/test_battery_evidence_corroboration.py
79
79
  tests/test_battery_skip_turn.py
80
80
  tests/test_ble_gateway.py
81
+ tests/test_bridge_state_sentinels.py
81
82
  tests/test_constants_imports_resolve.py
82
83
  tests/test_deferred_hardening.py
83
84
  tests/test_device_login_guard.py
@@ -151,6 +152,7 @@ tests/test_sdp_narrowing_properties.py
151
152
  tests/test_sdp_payload_type_narrowing.py
152
153
  tests/test_security_hardening.py
153
154
  tests/test_serializable_login_info.py
155
+ tests/test_serve_loss_tolerance.py
154
156
  tests/test_serve_relay.py
155
157
  tests/test_session_stats.py
156
158
  tests/test_shared_house_seam.py
@@ -0,0 +1,68 @@
1
+ """Lazy-init guards on the bridge must name a variable something actually sets.
2
+
3
+ The bridge keeps its per-session state as attributes on its own function object
4
+ and initialises them with `if not hasattr(_bridge_fn, 'X')`. That idiom fails
5
+ silently when `X` is not the attribute the block goes on to assign: the guard is
6
+ true on every packet, so the state resets every packet, and nothing raises.
7
+
8
+ It happened. The TUTK-to-RTP synthesis guarded on `_tutk_seq` while assigning
9
+ `_tutk_seq_a` and `_tutk_seq_v`, so both sequence counters reset on every packet
10
+ and every synthesized RTP packet carried sequence number 1. ffmpeg reads a
11
+ constant sequence as a stream of discontinuities and logs `RTP: missed N
12
+ packets` - loss that never happened, on a hop where the kernel counters showed
13
+ zero drops. Measured on the box: 0 drops on every socket in the path while
14
+ ffmpeg reported 21 new missed-packet lines in one window.
15
+
16
+ These tests read the source because the bridge is one long closure that cannot
17
+ be called in isolation.
18
+ """
19
+ import inspect
20
+ import re
21
+
22
+ from aidot_cameras.camera import sdes_open
23
+
24
+ _SRC = inspect.getsource(sdes_open)
25
+
26
+
27
+ def _assigned_attrs() -> set:
28
+ """Every `_bridge_fn.X = ...` attribute assigned anywhere in the module."""
29
+ return set(re.findall(r"_bridge_fn\.([A-Za-z_][A-Za-z0-9_]*)\s*=[^=]", _SRC))
30
+
31
+
32
+ def test_every_hasattr_sentinel_is_a_name_something_assigns():
33
+ guarded = set(re.findall(r"hasattr\(_bridge_fn,\s*'([^']+)'\)", _SRC))
34
+ assigned = _assigned_attrs()
35
+ orphans = sorted(guarded - assigned)
36
+ assert not orphans, (
37
+ "these hasattr guards name attributes nothing ever assigns, so they are "
38
+ f"true on every packet and reset their state each time: {orphans}"
39
+ )
40
+
41
+
42
+ def test_the_tutk_sequence_counters_survive_between_packets():
43
+ """The specific regression: a constant RTP sequence number.
44
+
45
+ Pin the guard to a counter the block assigns, so a reset-per-packet cannot
46
+ come back by renaming one side of the pair.
47
+ """
48
+ block = re.search(
49
+ r"if not hasattr\(_bridge_fn,\s*'([^']+)'\):\s*\n"
50
+ r"\s*_bridge_fn\._tutk_seq_a = 0\s*\n"
51
+ r"\s*_bridge_fn\._tutk_seq_v = 0",
52
+ _SRC,
53
+ )
54
+ assert block, "the TUTK sequence init block moved - re-point this test"
55
+ sentinel = block.group(1)
56
+ assert sentinel in ("_tutk_seq_a", "_tutk_seq_v"), (
57
+ f"the guard tests {sentinel!r}, which is not one of the counters it "
58
+ "initialises - the counters will reset on every packet and every "
59
+ "synthesized packet will carry the same sequence number"
60
+ )
61
+
62
+
63
+ def test_the_counters_increment_rather_than_being_reassigned():
64
+ """Both counters must advance from their previous value, not restart."""
65
+ for name in ("_tutk_seq_a", "_tutk_seq_v"):
66
+ assert re.search(
67
+ rf"_bridge_fn\.{name} = \(_bridge_fn\.{name} \+ 1\) & 0xFFFF", _SRC
68
+ ), f"{name} no longer advances from its own previous value"
@@ -0,0 +1,70 @@
1
+ """The serve must ride out real packet loss instead of dying on it.
2
+
3
+ Both ends of this path are usually wireless - the camera, and the Home Assistant
4
+ host - so RTP genuinely arrives with gaps. Measured on the reference install:
5
+ ffmpeg logged `RTP: missed N packets` while every socket in the path showed ZERO
6
+ kernel drops, which means those packets were lost before they ever reached the
7
+ box. Nothing in the code can recover them.
8
+
9
+ What the code controls is what happens next. Without reordering headroom the
10
+ demuxer treats a burst that arrives out of order as loss; without a bounded
11
+ max_delay it stalls waiting ("max delay reached. need to consume packet") and
12
+ backs its input queue up until the serve dies, dropping every attached viewer.
13
+ A brief artefact is a far better outcome than a dropped stream.
14
+ """
15
+ import pytest
16
+
17
+ from aidot_cameras.camera.client import (
18
+ _SERVE_MAX_DELAY_US,
19
+ _SERVE_REORDER_QUEUE,
20
+ _build_sdes_serve_cmd,
21
+ )
22
+
23
+ _DESTINATIONS = [
24
+ pytest.param({"rtsp_push_url": "rtsp://127.0.0.1:8554/cam"}, id="push"),
25
+ pytest.param({"rtsp_push_url": "http://127.0.0.1:18981/cam.ts"}, id="http-listen"),
26
+ pytest.param({"output_path": "/tmp/cam.ts"}, id="file"),
27
+ pytest.param({}, id="null-drain"),
28
+ ]
29
+
30
+
31
+ def _cmd(**kwargs):
32
+ return _build_sdes_serve_cmd(sdp_path="/tmp/x.sdp", **kwargs)
33
+
34
+
35
+ @pytest.mark.parametrize("dest", _DESTINATIONS)
36
+ def test_every_destination_gets_the_loss_tolerance_args(dest):
37
+ """The input side is shared, so loss tolerance must not depend on the sink."""
38
+ cmd = _cmd(**dest)
39
+ assert "-reorder_queue_size" in cmd
40
+ assert cmd[cmd.index("-reorder_queue_size") + 1] == str(_SERVE_REORDER_QUEUE)
41
+ assert "-max_delay" in cmd
42
+ assert cmd[cmd.index("-max_delay") + 1] == str(_SERVE_MAX_DELAY_US)
43
+ fflags = cmd[cmd.index("-fflags") + 1]
44
+ assert "discardcorrupt" in fflags, fflags
45
+
46
+
47
+ def test_the_reorder_queue_clears_a_keyframe():
48
+ # 146-190 KB of keyframe is ~130 packets at MTU. A queue that cannot hold
49
+ # more than one keyframe's worth reads an out-of-order burst as loss.
50
+ assert _SERVE_REORDER_QUEUE >= 260
51
+
52
+
53
+ def test_max_delay_is_bounded():
54
+ """Unbounded waiting turns loss into the stall this is meant to prevent."""
55
+ assert 0 < _SERVE_MAX_DELAY_US <= 2_000_000
56
+
57
+
58
+ @pytest.mark.parametrize("dest", _DESTINATIONS)
59
+ def test_the_low_latency_flags_survive(dest):
60
+ """Tolerance must not be bought by silently reintroducing buffering."""
61
+ fflags = _cmd(**dest)[_cmd(**dest).index("-fflags") + 1]
62
+ assert "nobuffer" in fflags and "genpts" in fflags, fflags
63
+
64
+
65
+ def test_the_args_sit_before_the_input():
66
+ """Demuxer options after -i apply to the output and are silently ignored."""
67
+ cmd = _cmd(rtsp_push_url="rtsp://127.0.0.1:8554/cam")
68
+ i = cmd.index("-i")
69
+ for opt in ("-reorder_queue_size", "-max_delay", "-fflags"):
70
+ assert cmd.index(opt) < i, f"{opt} must precede -i to affect the input"