python-aidot-cameras 0.7.24__tar.gz → 0.7.26__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 (59) hide show
  1. {python_aidot_cameras-0.7.24/src/python_aidot_cameras.egg-info → python_aidot_cameras-0.7.26}/PKG-INFO +4 -1
  2. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/README.md +3 -0
  3. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/pyproject.toml +1 -1
  4. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/client.py +82 -9
  5. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26/src/python_aidot_cameras.egg-info}/PKG-INFO +4 -1
  6. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/LICENSE +0 -0
  7. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/MANIFEST.in +0 -0
  8. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/setup.cfg +0 -0
  9. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/__init__.py +0 -0
  10. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/aes_utils.py +0 -0
  11. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/__init__.py +0 -0
  12. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/constants.py +0 -0
  13. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/controls.py +0 -0
  14. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/go2rtc.py +0 -0
  15. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/lan_control.py +0 -0
  16. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/models.py +0 -0
  17. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/playback.py +0 -0
  18. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/protocol.py +0 -0
  19. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/sdes.py +0 -0
  20. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/tutk.py +0 -0
  21. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/camera/webrtc.py +0 -0
  22. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/client.py +0 -0
  23. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/const.py +0 -0
  24. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/credentials.py +0 -0
  25. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/device_client.py +0 -0
  26. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/discover.py +0 -0
  27. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/exceptions.py +0 -0
  28. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/g711.py +0 -0
  29. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/login_const.py +0 -0
  30. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/models/__init__.py +0 -0
  31. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/models/device_client_model.py +0 -0
  32. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/models/device_model.py +0 -0
  33. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/models/discover_model.py +0 -0
  34. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/aidot/py.typed +0 -0
  35. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/python_aidot_cameras.egg-info/SOURCES.txt +0 -0
  36. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
  37. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/python_aidot_cameras.egg-info/requires.txt +0 -0
  38. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/src/python_aidot_cameras.egg-info/top_level.txt +0 -0
  39. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_alarm_event.py +0 -0
  40. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_backoff.py +0 -0
  41. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_go2rtc.py +0 -0
  42. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_highport_nomination.py +0 -0
  43. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_lan_control.py +0 -0
  44. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_live_stream_param.py +0 -0
  45. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_motion_poll.py +0 -0
  46. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_no_undefined_names.py +0 -0
  47. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_post_merge_hardening.py +0 -0
  48. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_sdes_fast_liveplay.py +0 -0
  49. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_sdes_idle_release.py +0 -0
  50. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_sdes_sprop.py +0 -0
  51. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_sdes_talk.py +0 -0
  52. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_sdes_watchdog.py +0 -0
  53. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_serve_relay.py +0 -0
  54. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_speak.py +0 -0
  55. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_stream_cap.py +0 -0
  56. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_stream_idle.py +0 -0
  57. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_talk.py +0 -0
  58. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/tests/test_terminal_ack.py +0 -0
  59. {python_aidot_cameras-0.7.24 → python_aidot_cameras-0.7.26}/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.7.24
3
+ Version: 0.7.26
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
@@ -122,6 +122,8 @@ chosen to work out of the box; override only when tuning.
122
122
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
123
123
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
124
124
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
125
+ | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
126
+ | `AIDOT_SERVE_RELAY` | Holds the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
125
127
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently. | `2` |
126
128
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
127
129
  | `AIDOT_STREAM_IDLE_S` | Seconds of stream idle before an idle release. | `120` |
@@ -131,6 +133,7 @@ chosen to work out of the box; override only when tuning.
131
133
  | `AIDOT_BUSY_RETRY_S` | Delay, in seconds, before retrying when a camera reports busy. | `45` |
132
134
  | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
133
135
  | `AIDOT_GOP_PLI_S` | Interval, in seconds, between PLI (keyframe) requests. | `2.0` |
136
+ | `AIDOT_SDES_PLI_GAPS` | Comma-separated second offsets for the early PLI (keyframe-request) burst on SDES cameras, to pull the first keyframe in faster on cold start. | `0,1.5,2,3` |
134
137
  | `AIDOT_AUDIO_TARGET_DBFS` | Target loudness (dBFS) for two-way audio normalization. | `-15` |
135
138
  | `AIDOT_AUDIO_MAXGAIN_DB` | Maximum gain (dB) applied by the audio normalizer. | `30` |
136
139
  | `AIDOT_AUDIO_MINGAIN_DB` | Minimum gain (dB) applied by the audio normalizer. | `-12` |
@@ -95,6 +95,8 @@ chosen to work out of the box; override only when tuning.
95
95
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
96
96
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
97
97
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
98
+ | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
99
+ | `AIDOT_SERVE_RELAY` | Holds the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
98
100
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently. | `2` |
99
101
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
100
102
  | `AIDOT_STREAM_IDLE_S` | Seconds of stream idle before an idle release. | `120` |
@@ -104,6 +106,7 @@ chosen to work out of the box; override only when tuning.
104
106
  | `AIDOT_BUSY_RETRY_S` | Delay, in seconds, before retrying when a camera reports busy. | `45` |
105
107
  | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
106
108
  | `AIDOT_GOP_PLI_S` | Interval, in seconds, between PLI (keyframe) requests. | `2.0` |
109
+ | `AIDOT_SDES_PLI_GAPS` | Comma-separated second offsets for the early PLI (keyframe-request) burst on SDES cameras, to pull the first keyframe in faster on cold start. | `0,1.5,2,3` |
107
110
  | `AIDOT_AUDIO_TARGET_DBFS` | Target loudness (dBFS) for two-way audio normalization. | `-15` |
108
111
  | `AIDOT_AUDIO_MAXGAIN_DB` | Maximum gain (dB) applied by the audio normalizer. | `30` |
109
112
  | `AIDOT_AUDIO_MINGAIN_DB` | Minimum gain (dB) applied by the audio normalizer. | `-12` |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-aidot-cameras"
7
- version = "0.7.24"
7
+ version = "0.7.26"
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"
@@ -2559,6 +2559,30 @@ class CameraMixin(_CameraControlsMixin):
2559
2559
  return os.environ.get("AIDOT_SDES_FAST_LIVEPLAY", "").strip().lower() in (
2560
2560
  "1", "true", "yes", "on")
2561
2561
 
2562
+ def _resolve_sdes_skip_turn(self) -> bool:
2563
+ """EXPERIMENTAL (opt-in, default off): skip the blocking SDES TURN relay
2564
+ pre-allocation, for cameras reachable LAN-direct.
2565
+
2566
+ Before building the offer the SDES path does two synchronous RFC-5766
2567
+ Allocate round-trips (audio + video) to the cloud TURN server so the
2568
+ offer's c=/m= can carry a relay address - ~2-3 s of pure cold-start
2569
+ latency. On a LAN the camera's host candidate wins and that relay is
2570
+ never used, so skipping it shaves the latency - at the cost of no relay
2571
+ fallback for a camera on a different segment / behind strict NAT (the
2572
+ same trade-off ``AIDOT_FAST_CONNECT`` already makes for DTLS, and which
2573
+ is force-disabled for SDES because it *also* skips the SCTP-arming
2574
+ waits; this flag skips ONLY the relay pre-allocation, leaving the rest
2575
+ of the SDES handshake intact).
2576
+
2577
+ Per-camera ``sdes_skip_turn`` (set via start_keepalive) wins; else the
2578
+ ``AIDOT_SDES_SKIP_TURN_PREALLOC`` env (truthy = 1/true/yes/on), default
2579
+ off. Only consulted for SDES cameras."""
2580
+ opt = getattr(self, "_sdes_skip_turn_opt", None)
2581
+ if opt is not None:
2582
+ return bool(opt)
2583
+ return os.environ.get("AIDOT_SDES_SKIP_TURN_PREALLOC", "").strip().lower() in (
2584
+ "1", "true", "yes", "on")
2585
+
2562
2586
  def _maybe_start_serve_relay(self, serve_url: Optional[str]) -> "Optional[_ServeRelay]":
2563
2587
  """Hold the public serve port via a _ServeRelay so an eager go2rtc pull
2564
2588
  connects-and-waits instead of hitting ECONNREFUSED during the ~16-25s
@@ -3720,7 +3744,14 @@ class CameraMixin(_CameraControlsMixin):
3720
3744
  or msg.get("devId") == device_id):
3721
3745
  loop.call_soon_threadsafe(camera_ready_ev.set)
3722
3746
  # livePlayResp: explicit camera ack/nack for start-play command.
3723
- if method == "livePlayResp" and inner.get("devId") == device_id:
3747
+ # The camera echoes our peer_id (verified live); its payload has NO
3748
+ # devId, so the old devId-only match never fired and this future never
3749
+ # resolved (the wait always timed out). Match on the echoed peer_id
3750
+ # (per-open, precise); keep devId as a fallback for any camera that
3751
+ # does send it. dstAddr is the account-wide userId - too loose to use.
3752
+ if method == "livePlayResp" and (
3753
+ inner.get("peerid") == peer_id
3754
+ or inner.get("devId") == device_id):
3724
3755
  if not liveplay_resp_fut.done():
3725
3756
  loop.call_soon_threadsafe(liveplay_resp_fut.set_result, inner)
3726
3757
  # livePlayReq echo: broker/camera confirmed delivery of our livePlayReq.
@@ -4186,9 +4217,19 @@ class CameraMixin(_CameraControlsMixin):
4186
4217
  _lp_arrived = True
4187
4218
  _lp_code = int(_lp_resp.get("code", 200))
4188
4219
  _lp_on = int(_lp_resp.get("livePlay", 1))
4189
- if _lp_code not in (0, 200) or _lp_on == 0:
4220
+ # Fast-fail only on an unambiguous refusal (livePlay=0).
4221
+ # Other non-OK codes (incl. -50019 "not ready") are
4222
+ # transient on battery cameras and recover via ICE; abort
4223
+ # there would spuriously kill otherwise-good streams.
4224
+ if _lp_on == 0:
4190
4225
  raise RuntimeError(
4191
- f"livePlay rejected by camera (code={_lp_code}, livePlay={_lp_on})"
4226
+ f"livePlay refused by camera (livePlay=0, code={_lp_code})"
4227
+ )
4228
+ elif _lp_code not in (0, 200):
4229
+ _status(
4230
+ f"livePlayResp: non-OK code {_lp_code}"
4231
+ f"{' (not ready, transient)' if _lp_code == -50019 else ''}"
4232
+ " - proceeding"
4192
4233
  )
4193
4234
  except TimeoutError:
4194
4235
  pass
@@ -4231,9 +4272,15 @@ class CameraMixin(_CameraControlsMixin):
4231
4272
  _lp_resp2 = liveplay_resp_fut.result()
4232
4273
  _lp_code2 = int(_lp_resp2.get("code", 200))
4233
4274
  _lp_on2 = int(_lp_resp2.get("livePlay", 1))
4234
- if _lp_code2 not in (0, 200) or _lp_on2 == 0:
4275
+ if _lp_on2 == 0:
4235
4276
  raise RuntimeError(
4236
- f"livePlay rejected by camera (code={_lp_code2}, livePlay={_lp_on2})"
4277
+ f"livePlay refused by camera (livePlay=0, code={_lp_code2})"
4278
+ )
4279
+ elif _lp_code2 not in (0, 200):
4280
+ _status(
4281
+ f"livePlayResp: non-OK code {_lp_code2}"
4282
+ f"{' (not ready, transient)' if _lp_code2 == -50019 else ''}"
4283
+ " - proceeding"
4237
4284
  )
4238
4285
  except RuntimeError:
4239
4286
  raise
@@ -6795,7 +6842,15 @@ class CameraMixin(_CameraControlsMixin):
6795
6842
  # AIDOT_FAST_CONNECT skips this blocking pre-allocation (LAN-direct mode):
6796
6843
  # the offer goes out immediately with host/srflx candidates and the LAN
6797
6844
  # path connects without waiting on a cloud TURN Allocate round-trip.
6798
- if _sdes_turn_entries and not _fast_connect:
6845
+ # AIDOT_SDES_SKIP_TURN_PREALLOC (experimental, opt-in) does the same skip
6846
+ # for SDES specifically, where _fast_connect is force-off (see
6847
+ # _resolve_sdes_skip_turn). Either way the cost is instrumented below so
6848
+ # the saving is measurable: grep ``signaling-wait[`` for sdes-turn-prealloc.
6849
+ _skip_turn_prealloc = self._resolve_sdes_skip_turn()
6850
+ _turn_t0 = time.monotonic()
6851
+ _turn_did = False
6852
+ if _sdes_turn_entries and not _fast_connect and not _skip_turn_prealloc:
6853
+ _turn_did = True
6799
6854
  try:
6800
6855
  import re as _re_pre
6801
6856
  import hashlib as _hlk_pre
@@ -6832,6 +6887,16 @@ class CameraMixin(_CameraControlsMixin):
6832
6887
  )
6833
6888
  except Exception as _pre_exc:
6834
6889
  _LOGGER.warning("TURN pre-allocation error: %s", _pre_exc)
6890
+ if _skip_turn_prealloc and _sdes_turn_entries:
6891
+ _status(
6892
+ "AIDOT_SDES_SKIP_TURN_PREALLOC: skipping TURN relay"
6893
+ " pre-allocation (~2-3s) - host/srflx candidates only, LAN-direct"
6894
+ )
6895
+ _LOGGER.info(
6896
+ "signaling-wait[%s] sdes-turn-prealloc elapsed=%dms allocated=%d skipped=%s",
6897
+ self.device_id,
6898
+ int((time.monotonic() - _turn_t0) * 1000),
6899
+ len(_relay_addrs), bool(_skip_turn_prealloc))
6835
6900
 
6836
6901
  # --- DTLS certificate for m=application probe ----------------------- #
6837
6902
  # PreCon cameras (sptPreconn=1) need SESSION_MODE_REQ via SCTP datachannel.
@@ -7100,10 +7165,18 @@ class CameraMixin(_CameraControlsMixin):
7100
7165
  _lp_arrived = True
7101
7166
  _lp_code_sdes = int(_lp_resp_sdes.get("code", 200))
7102
7167
  _lp_on_sdes = int(_lp_resp_sdes.get("livePlay", 1))
7103
- if _lp_code_sdes not in (0, 200) or _lp_on_sdes == 0:
7168
+ # Only an explicit livePlay=0 is an unambiguous refusal (fast-fail).
7169
+ # Numeric codes (e.g. -50019 "not ready" on a waking battery cam)
7170
+ # are transient - the camera recovers and streams - so log and
7171
+ # proceed rather than abort on a code we can't classify as terminal
7172
+ # (genuine terminal rejects are still caught on the webrtcResp ack).
7173
+ if _lp_on_sdes == 0:
7104
7174
  raise RuntimeError(
7105
- f"livePlay rejected by camera (code={_lp_code_sdes}, livePlay={_lp_on_sdes})"
7106
- )
7175
+ f"livePlay refused by camera (livePlay=0, code={_lp_code_sdes})")
7176
+ elif _lp_code_sdes not in (0, 200):
7177
+ _status(f"livePlayResp: non-OK code {_lp_code_sdes}"
7178
+ f"{' (not ready, transient)' if _lp_code_sdes == -50019 else ''}"
7179
+ " - proceeding")
7107
7180
  except TimeoutError:
7108
7181
  pass
7109
7182
  _LOGGER.info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-aidot-cameras
3
- Version: 0.7.24
3
+ Version: 0.7.26
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
@@ -122,6 +122,8 @@ chosen to work out of the box; override only when tuning.
122
122
  | `AIDOT_SPROP_DIR` | Directory where captured SPS/PPS (sprop) parameter sets are cached. Set this to a writable path (e.g. for Home Assistant) if the default location is read-only. | `<package dir>` |
123
123
  | `AIDOT_DISABLE_HIGHPORT_FIX` | If set (any value), disables the DTLS high-port `USE-CANDIDATE` nomination fix and falls back to upstream aioice behavior (used to measure the baseline connect rate). | unset (fix enabled) |
124
124
  | `AIDOT_FAST_CONNECT` | Enables LAN-direct "fast connect" mode (STUN-only, skips several cloud signaling waits) when set to a truthy value. | unset (off) |
125
+ | `AIDOT_SDES_FAST_LIVEPLAY` | Truthy value skips the `livePlayResp` blocking wait for eligible SDES cameras (~4.5 s faster cold start), while keeping the full ICE/SCTP handshake. Role-reversal models (A001064 PTZ) are always excluded. Soak-validated; opt-in. | unset (off) |
126
+ | `AIDOT_SERVE_RELAY` | Holds the public stream port via an internal relay that proxies to ffmpeg, so the first (cold) view connects instead of failing while ffmpeg can't pre-bind the port. Set to `0` to serve ffmpeg directly. | `1` (enabled) |
125
127
  | `AIDOT_MAX_CONCURRENT_OPENS` | Caps how many stream opens run concurrently. | `2` |
126
128
  | `AIDOT_MAX_CONCURRENT_STREAMS` | Caps how many cameras stream at once. | `3` |
127
129
  | `AIDOT_STREAM_IDLE_S` | Seconds of stream idle before an idle release. | `120` |
@@ -131,6 +133,7 @@ chosen to work out of the box; override only when tuning.
131
133
  | `AIDOT_BUSY_RETRY_S` | Delay, in seconds, before retrying when a camera reports busy. | `45` |
132
134
  | `AIDOT_LIVESTREAM_PARAM` | Set to `0` to skip the cloud `liveStreamParam` pre-connect that provisions battery cameras' live-stream sessions before signaling (without it, battery cameras like the L2 models reject streaming with `-50019`). | `1` (enabled) |
133
135
  | `AIDOT_GOP_PLI_S` | Interval, in seconds, between PLI (keyframe) requests. | `2.0` |
136
+ | `AIDOT_SDES_PLI_GAPS` | Comma-separated second offsets for the early PLI (keyframe-request) burst on SDES cameras, to pull the first keyframe in faster on cold start. | `0,1.5,2,3` |
134
137
  | `AIDOT_AUDIO_TARGET_DBFS` | Target loudness (dBFS) for two-way audio normalization. | `-15` |
135
138
  | `AIDOT_AUDIO_MAXGAIN_DB` | Maximum gain (dB) applied by the audio normalizer. | `30` |
136
139
  | `AIDOT_AUDIO_MINGAIN_DB` | Minimum gain (dB) applied by the audio normalizer. | `-12` |