python-aidot-cameras 0.5.2__tar.gz → 0.5.4__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 (29) hide show
  1. {python_aidot_cameras-0.5.2/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.5.4}/PKG-INFO +1 -1
  2. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/pyproject.toml +1 -1
  3. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/device_client.py +46 -3
  4. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4/src/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
  5. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/LICENSE +0 -0
  6. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/README.md +0 -0
  7. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/setup.cfg +0 -0
  8. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/__init__.py +0 -0
  9. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/aes_utils.py +0 -0
  10. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/client.py +0 -0
  11. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/const.py +0 -0
  12. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/credentials.py +0 -0
  13. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/discover.py +0 -0
  14. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/exceptions.py +0 -0
  15. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/aidot/g711.py +0 -0
  16. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  17. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  18. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  19. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  20. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_alarm_event.py +0 -0
  21. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_highport_nomination.py +0 -0
  22. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_motion_poll.py +0 -0
  23. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_sdes_talk.py +0 -0
  24. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_sdes_watchdog.py +0 -0
  25. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_speak.py +0 -0
  26. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_stream_cap.py +0 -0
  27. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_talk.py +0 -0
  28. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_terminal_ack.py +0 -0
  29. {python_aidot_cameras-0.5.2 → python_aidot_cameras-0.5.4}/tests/test_token_refresh.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.5.2"
7
+ version = "0.5.4"
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"
@@ -2110,7 +2110,32 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2110
2110
  while True:
2111
2111
  fr = fifo.read(1024) # AAC wants 1024-sample frames
2112
2112
  if fr is None:
2113
- break
2113
+ # 160 PCMA samples → 960 resampled @ 48 kHz, but AAC needs 1024.
2114
+ # Every ~17 packets (~340 ms) the FIFO falls 64 samples short
2115
+ # and would skip an entire 21 ms frame — audible choppiness.
2116
+ # Pad the leftover with silence so the frame is complete.
2117
+ s = getattr(fifo, "samples", 0)
2118
+ if s == 0:
2119
+ break
2120
+ fr = fifo.read(s)
2121
+ if fr is None or fr.samples == 0:
2122
+ break
2123
+ if _np is not None:
2124
+ try:
2125
+ arr = fr.to_ndarray()
2126
+ pad_n = 1024 - arr.shape[1]
2127
+ if pad_n > 0:
2128
+ padded = _np.concatenate(
2129
+ [arr, _np.zeros((arr.shape[0], pad_n), dtype=arr.dtype)],
2130
+ axis=1,
2131
+ )
2132
+ pfr = av.AudioFrame.from_ndarray(
2133
+ padded, format="fltp", layout="mono"
2134
+ )
2135
+ pfr.sample_rate = 48000
2136
+ fr = pfr
2137
+ except Exception:
2138
+ break
2114
2139
  fr.pts = a_pts[0]
2115
2140
  fr.time_base = Fraction(1, 48000)
2116
2141
  a_pts[0] += fr.samples
@@ -2124,7 +2149,7 @@ def _dtls_av_mux_run(vq, aq, out_fileobj, progress, stop_flag) -> None:
2124
2149
  while not stop_flag.is_set():
2125
2150
  _flush_video()
2126
2151
  _flush_audio()
2127
- _t.sleep(0.02)
2152
+ _t.sleep(0.005) # 5 ms — processes audio within 5 ms of arrival vs 20 ms
2128
2153
  _flush_video()
2129
2154
  _flush_audio()
2130
2155
  try:
@@ -4415,14 +4440,32 @@ class DeviceClient(object):
4415
4440
  import threading as _threading
4416
4441
  import queue as _queue
4417
4442
  serve_url = self._keepalive_rtsp_url
4418
- _MIN_DELAY, _MAX_DELAY = 5.0, 300.0
4443
+ # APK parity: the official app gates re-connects to ~15 s (f0.java I1=15000)
4444
+ # and never hammers. We previously floored at 5 s and a partial-success →
4445
+ # reset path could drop the effective spacing even lower, pounding a flaky
4446
+ # A000088 camera fast enough to wedge its DTLS stack (ICE completes but DTLS
4447
+ # never fires until a power-cycle). Floor the backoff at the 15 s gate and,
4448
+ # independently, enforce a hard minimum wall-clock spacing between OPEN
4449
+ # attempts so no code path (reset bug, fast PC-death) can pound the camera.
4450
+ _MIN_DELAY, _MAX_DELAY = 15.0, 300.0
4451
+ _open_gate = float(os.environ.get("AIDOT_DTLS_RETRY_GATE_S", "15"))
4419
4452
  retry_delay = _MIN_DELAY
4453
+ _last_open_at = 0.0 # monotonic of the previous open attempt (0 = none yet)
4420
4454
  loop = asyncio.get_running_loop()
4421
4455
  while self._streaming_active:
4422
4456
  # Thread-safe queues: taps run on the loop, the A/V mux in a thread.
4423
4457
  vq: "_queue.Queue" = _queue.Queue(maxsize=600)
4424
4458
  aq: "_queue.Queue" = _queue.Queue(maxsize=600)
4425
4459
  self._serve_ready.clear() # fresh (cold) session: not ready until bound
4460
+ # Hard inter-attempt gate (APK parity): never start an open within
4461
+ # _open_gate seconds of the previous one, regardless of retry_delay.
4462
+ _since_open = loop.time() - _last_open_at
4463
+ if _last_open_at and _since_open < _open_gate:
4464
+ try:
4465
+ await asyncio.sleep(_open_gate - _since_open)
4466
+ except asyncio.CancelledError:
4467
+ return
4468
+ _last_open_at = loop.time()
4426
4469
  try:
4427
4470
  session = await self.async_open_webrtc_stream(on_frame=lambda _f: None)
4428
4471
  except asyncio.CancelledError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.5.2
3
+ Version: 0.5.4
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