python-aidot-cameras 0.12.4__tar.gz → 0.12.7__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.
- {python_aidot_cameras-0.12.4/python_aidot_cameras.egg-info → python_aidot_cameras-0.12.7}/PKG-INFO +1 -1
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/client.py +16 -18
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/sdes_open.py +133 -20
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/pyproject.toml +1 -1
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7/python_aidot_cameras.egg-info}/PKG-INFO +1 -1
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/python_aidot_cameras.egg-info/SOURCES.txt +1 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_serve_audio.py +17 -18
- python_aidot_cameras-0.12.7/tests/test_sdp_payload_type_narrowing.py +83 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/LICENSE +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/MANIFEST.in +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/README.md +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/__main__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/clock.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/base.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/g711.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/g722.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/h264.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/opus.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/codecs/vpx.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/contrib/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/contrib/media.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/contrib/signaling.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/events.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/exceptions.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/jitterbuffer.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/mediastreams.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rate.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcconfiguration.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcdatachannel.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcdtlstransport.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcicetransport.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcpeerconnection.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcrtpparameters.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcrtpreceiver.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcrtpsender.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcrtptransceiver.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcsctptransport.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtcsessiondescription.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtp.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/sdp.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/stats.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/utils.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/__init__.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/constants.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/controls.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/go2rtc.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/lan_control.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/models.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/playback.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/protocol.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/sdes.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/tutk.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/webrtc.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/webrtc_open.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/client.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/const.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/credentials.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/crypto.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/device_client.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/discover.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/exceptions.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/g711.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/py.typed +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/python_aidot_cameras.egg-info/dependency_links.txt +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/python_aidot_cameras.egg-info/entry_points.txt +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/python_aidot_cameras.egg-info/requires.txt +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/python_aidot_cameras.egg-info/top_level.txt +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/setup.cfg +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_aioice_compat.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_alarm_event.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_backoff.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_deferred_hardening.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_device_login_guard.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_device_user_info_cache.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_not_ready_burst.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_pinning.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_serve_decode_skip.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_serve_open_timeout.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_skip_signaling_wait.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_slow_probe.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_egress_guard.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_go2rtc.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_go2rtc_cli.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_h264_decode_rate_limit.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_highport_nomination.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_ice_config_cache.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_keepalive_renew.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_keyframe_prompter.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_lan_control.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_light_active_color_mode.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_live_stream_param.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_logging_caps.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_motion_poll.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_credential_refetch.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_credential_self_heal.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_password_not_persisted.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_session_reconnect.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_narrow_pc_ice.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_no_undefined_names.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_offline_keepalive_pause.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_open_fail_logger.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_open_gate_delay.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_persistent_mqtt.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_persistent_mqtt_robustness.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_playback_tls.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_post_merge_hardening.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_retry_policy.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_adaptive.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_bridge_break_gate.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_echo_wait_timeout.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_fast_liveplay.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_ffmpeg_stderr_log.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_idle_release.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_open_cleanup.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_serve_cmd.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_sprop.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_talk.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_teardown_exit_log.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_watchdog.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_security_hardening.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_serializable_login_info.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_serve_relay.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_session_stats.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_speak.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_stream_cap.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_stream_drain_ownership.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_stream_idle.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_stream_teardown.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_talk.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_terminal_ack.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_token_file_robustness.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_token_refresh.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_upstream_compat.py +0 -0
- {python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_wait_or_event.py +0 -0
{python_aidot_cameras-0.12.4/python_aidot_cameras.egg-info → python_aidot_cameras-0.12.7}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.7
|
|
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
|
|
@@ -3360,27 +3360,25 @@ class CameraMixin(_CameraControlsMixin, _WebRTCOpenMixin, _SdesOpenMixin):
|
|
|
3360
3360
|
def _resolve_sdes_serve_audio(self) -> bool:
|
|
3361
3361
|
"""Whether to include audio in the SDES camera serve.
|
|
3362
3362
|
|
|
3363
|
-
**Default
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
``
|
|
3377
|
-
``AIDOT_SDES_SERVE_AUDIO=1`` - on a camera that does send audio steadily
|
|
3378
|
-
it works, which is why this stays an option rather than being removed."""
|
|
3363
|
+
**Default ON** for app-parity (the official app plays camera audio).
|
|
3364
|
+
|
|
3365
|
+
This was briefly defaulted off, because mapping audio used to serve no
|
|
3366
|
+
video at all: the SDP advertises two payload types per media line and
|
|
3367
|
+
ffmpeg binds the first, so the synthesised PCMA (pt 8) was discarded in
|
|
3368
|
+
favour of the advertised PCMU (pt 0) - and since the mpegts mux withholds
|
|
3369
|
+
its PAT/PMT until every mapped stream has produced a packet, the consumer
|
|
3370
|
+
got zero bytes and lost the picture too. The audio line is now narrowed to
|
|
3371
|
+
the payload type actually in use, the same way the video line already was,
|
|
3372
|
+
and the serve falls back to video-only if the audio payload type cannot be
|
|
3373
|
+
characterised in time. So audio can no longer cost the video.
|
|
3374
|
+
|
|
3375
|
+
Per-camera ``sdes_audio`` (via ``start_keepalive``) wins; else the
|
|
3376
|
+
``AIDOT_SDES_SERVE_AUDIO`` env (falsy = 0/false/no/off disables)."""
|
|
3379
3377
|
opt = getattr(self, "_sdes_audio_opt", None)
|
|
3380
3378
|
if opt is not None:
|
|
3381
3379
|
return bool(opt)
|
|
3382
|
-
return os.environ.get("AIDOT_SDES_SERVE_AUDIO", "").strip().lower() in (
|
|
3383
|
-
"
|
|
3380
|
+
return os.environ.get("AIDOT_SDES_SERVE_AUDIO", "").strip().lower() not in (
|
|
3381
|
+
"0", "false", "no", "off")
|
|
3384
3382
|
|
|
3385
3383
|
def _resolve_sdes_audio_gain_db(self) -> float:
|
|
3386
3384
|
"""Gain (dB) applied to the served SDES audio (the camera mic runs hot).
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/sdes_open.py
RENAMED
|
@@ -34,6 +34,62 @@ from .protocol import (
|
|
|
34
34
|
_LOGGER = logging.getLogger(__name__)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
# Payload types the ffmpeg SDP advertises per media line, and their alternates.
|
|
38
|
+
# The SDP offers both because the camera picks one per session.
|
|
39
|
+
_SDP_ALTERNATE_PT = {96: 97, 97: 96, 0: 8, 8: 0}
|
|
40
|
+
|
|
41
|
+
# How long to wait for the FIRST media of a session before launching the serve.
|
|
42
|
+
# Nothing useful can happen before then: the payload types are unknown, so the SDP
|
|
43
|
+
# cannot be narrowed, and ffmpeg would bind the wrong depacketizers. Sized to the
|
|
44
|
+
# documented cold-start window for a battery camera (25-70s). Measured: a cold
|
|
45
|
+
# session started media at ~21s, which the previous 15s deadline missed entirely -
|
|
46
|
+
# it launched blind with BOTH payload types still unknown, which is what left the
|
|
47
|
+
# audio line advertising PCMU on a PCMA camera.
|
|
48
|
+
_FIRST_MEDIA_WAIT_S = 45.0
|
|
49
|
+
|
|
50
|
+
# Extra time for the audio payload type once video is known. Measured on an
|
|
51
|
+
# A001513: audio follows video by 40-70ms, because the camera answers BUNDLE and
|
|
52
|
+
# both share one 5-tuple - so audio is never "late" and this only absorbs jitter.
|
|
53
|
+
# Kept tight so a camera that genuinely sends no audio barely delays the picture.
|
|
54
|
+
_AUDIO_PT_GRACE_S = 1.0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def narrow_sdp_payload_types(sdp_text: str, keep_video=None, keep_audio=None) -> str:
|
|
58
|
+
"""Rewrite an SDP to advertise a single payload type per media line.
|
|
59
|
+
|
|
60
|
+
ffmpeg binds each RTP depacketizer to the FIRST payload type on the m-line and
|
|
61
|
+
silently discards packets carrying any other one. The SDP written for the
|
|
62
|
+
bridge lists both candidates per line (``m=video ... 96 97`` for H.264/H.265,
|
|
63
|
+
``m=audio ... 0 8`` for PCMU/PCMA) because which one the camera uses varies per
|
|
64
|
+
session, so whichever it actually sends has to be promoted before launch.
|
|
65
|
+
|
|
66
|
+
Getting this wrong on the VIDEO line costs the picture. Getting it wrong on the
|
|
67
|
+
AUDIO line costs the picture too, and less obviously: the mpegts mux withholds
|
|
68
|
+
its PAT/PMT until every mapped stream has produced a packet, so an audio stream
|
|
69
|
+
whose packets are all discarded means the consumer receives zero bytes - video
|
|
70
|
+
included - while every other signal looks healthy.
|
|
71
|
+
|
|
72
|
+
``keep_video`` / ``keep_audio`` of None leaves that line alone.
|
|
73
|
+
"""
|
|
74
|
+
drop = {_SDP_ALTERNATE_PT[k] for k in (keep_video, keep_audio)
|
|
75
|
+
if k in _SDP_ALTERNATE_PT}
|
|
76
|
+
kept = []
|
|
77
|
+
for line in sdp_text.splitlines(keepends=True):
|
|
78
|
+
stripped = line.lstrip()
|
|
79
|
+
if stripped.startswith("m=video") and keep_video is not None:
|
|
80
|
+
line = line.replace(" 96 97", f" {keep_video}")
|
|
81
|
+
elif stripped.startswith("m=audio") and keep_audio is not None:
|
|
82
|
+
line = line.replace(" 0 8", f" {keep_audio}")
|
|
83
|
+
elif any(
|
|
84
|
+
stripped.startswith(f"a={attr}:{pt}{sep}")
|
|
85
|
+
for pt in drop
|
|
86
|
+
for attr, sep in (("rtpmap", " "), ("fmtp", " "), ("fmtp", ";"))
|
|
87
|
+
):
|
|
88
|
+
continue
|
|
89
|
+
kept.append(line)
|
|
90
|
+
return "".join(kept)
|
|
91
|
+
|
|
92
|
+
|
|
37
93
|
def _sdes_echo_wait_timeout(skip_liveplay: bool) -> float:
|
|
38
94
|
"""Seconds to block on the camera's webrtcReq echo before proceeding.
|
|
39
95
|
|
|
@@ -2141,6 +2197,11 @@ class _SdesOpenMixin:
|
|
|
2141
2197
|
# bridge on the first video RTP packet so the ffmpeg SDP can be narrowed
|
|
2142
2198
|
# to the matching single codec before ffmpeg is launched.
|
|
2143
2199
|
_first_video_pt: list = [None]
|
|
2200
|
+
# Same for audio: the ffmpeg SDP advertises BOTH PCMU (0) and PCMA (8),
|
|
2201
|
+
# and ffmpeg binds the depacketizer to the first one listed - so the line
|
|
2202
|
+
# has to be narrowed to the payload type the camera actually sends, the
|
|
2203
|
+
# same way the video line already is.
|
|
2204
|
+
_first_audio_pt: list = [None]
|
|
2144
2205
|
# Media-liveness: bridge sets [0] = time.monotonic() on every forwarded
|
|
2145
2206
|
# media packet; the keepalive watchdog reads it via SdesSession to
|
|
2146
2207
|
# restart a session the camera silently stopped feeding.
|
|
@@ -3095,10 +3156,21 @@ class _SdesOpenMixin:
|
|
|
3095
3156
|
)
|
|
3096
3157
|
if not _br_first_audio_logged:
|
|
3097
3158
|
_br_first_audio_logged = True
|
|
3159
|
+
# Record the payload type this path
|
|
3160
|
+
# SYNTHESISES (_tk_pt, PCMA) so the
|
|
3161
|
+
# SDP can be narrowed to it. Without
|
|
3162
|
+
# this the m=audio line keeps
|
|
3163
|
+
# advertising "0 8", ffmpeg binds
|
|
3164
|
+
# PCMU and discards every packet, and
|
|
3165
|
+
# the mpegts PMT is never written - so
|
|
3166
|
+
# the consumer gets zero bytes and the
|
|
3167
|
+
# VIDEO is lost with the audio.
|
|
3168
|
+
_first_audio_pt[0] = _tk_pt
|
|
3098
3169
|
_status(
|
|
3099
3170
|
f"bridge: first SDES audio -> ffmpeg"
|
|
3100
3171
|
f" loopback:{_lo_audio_port}"
|
|
3101
3172
|
f" ({len(_pd_plain)}B PCMA)"
|
|
3173
|
+
f" pt={_tk_pt}"
|
|
3102
3174
|
)
|
|
3103
3175
|
except Exception:
|
|
3104
3176
|
_LOGGER.debug("camera %s: swallowed exception in %s", getattr(self, "device_id", "?"), '_enc_c8_sctp', exc_info=True)
|
|
@@ -3190,6 +3262,7 @@ class _SdesOpenMixin:
|
|
|
3190
3262
|
)
|
|
3191
3263
|
if _kind == "audio" and not _br_first_audio_logged:
|
|
3192
3264
|
_br_first_audio_logged = True
|
|
3265
|
+
_first_audio_pt[0] = _pt
|
|
3193
3266
|
_status(f"bridge: first audio RTP pt={_pt}")
|
|
3194
3267
|
# Capture the camera's audio return address so the talk
|
|
3195
3268
|
# pump (SdesSession.async_start_talk) can send outbound
|
|
@@ -3454,12 +3527,13 @@ class _SdesOpenMixin:
|
|
|
3454
3527
|
if out_dir:
|
|
3455
3528
|
os.makedirs(out_dir, exist_ok=True)
|
|
3456
3529
|
# Build the ffmpeg command (single source of truth: _build_sdes_serve_cmd).
|
|
3530
|
+
_serve_audio = self._resolve_sdes_serve_audio()
|
|
3457
3531
|
cmd = _build_sdes_serve_cmd(
|
|
3458
3532
|
sdp_path=sdp_path,
|
|
3459
3533
|
rtsp_push_url=rtsp_push_url,
|
|
3460
3534
|
output_path=output_path,
|
|
3461
3535
|
max_seconds=max_seconds,
|
|
3462
|
-
sdes_audio=
|
|
3536
|
+
sdes_audio=_serve_audio,
|
|
3463
3537
|
audio_gain_db=self._resolve_sdes_audio_gain_db(),
|
|
3464
3538
|
)
|
|
3465
3539
|
# --- H.265 fix: narrow the ffmpeg SDP to the camera's actual codec ----
|
|
@@ -3470,36 +3544,75 @@ class _SdesOpenMixin:
|
|
|
3470
3544
|
# video pt, then rewrite the SDP to that single codec before launch.
|
|
3471
3545
|
# ffmpeg recovers on the next periodic keyframe, so the small spawn delay
|
|
3472
3546
|
# is harmless. Falls back to the dual-codec SDP if no video is seen.
|
|
3473
|
-
|
|
3474
|
-
|
|
3547
|
+
# When audio is being served the same wait has to cover the AUDIO payload
|
|
3548
|
+
# type, and for a stricter reason: an unnarrowed video line merely risks the
|
|
3549
|
+
# picture, while an unnarrowed audio line guarantees the mux never writes its
|
|
3550
|
+
# PAT/PMT, which loses the picture AND the audio. Wait for both, then take
|
|
3551
|
+
# whatever has been observed by the deadline.
|
|
3552
|
+
# Wait for the session's first media, then briefly for audio.
|
|
3553
|
+
#
|
|
3554
|
+
# Measured on an A001513: a cold session's media starts at ~21s, and its
|
|
3555
|
+
# video and audio payload types then arrive 40-70ms apart because the camera
|
|
3556
|
+
# answers BUNDLE. So audio is never "late" relative to video - the old 15s
|
|
3557
|
+
# video deadline simply expired BEFORE any media at all, and the serve
|
|
3558
|
+
# launched with both payload types unknown. Waiting for first media costs
|
|
3559
|
+
# no picture latency: ffmpeg cannot produce before media exists, and
|
|
3560
|
+
# launching earlier only binds the wrong depacketizers.
|
|
3561
|
+
_media_deadline = time.monotonic() + _FIRST_MEDIA_WAIT_S
|
|
3562
|
+
while _first_video_pt[0] is None and time.monotonic() < _media_deadline:
|
|
3475
3563
|
await asyncio.sleep(0.1)
|
|
3564
|
+
if _serve_audio and _first_audio_pt[0] is None:
|
|
3565
|
+
_apt_deadline = time.monotonic() + _AUDIO_PT_GRACE_S
|
|
3566
|
+
while _first_audio_pt[0] is None and time.monotonic() < _apt_deadline:
|
|
3567
|
+
await asyncio.sleep(0.1)
|
|
3476
3568
|
_vpt = _first_video_pt[0]
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3569
|
+
_apt = _first_audio_pt[0]
|
|
3570
|
+
if _serve_audio and _apt is None:
|
|
3571
|
+
# No audio observed in the window, so the line cannot be narrowed and
|
|
3572
|
+
# mapping audio would stall the mux. Serve video only rather than
|
|
3573
|
+
# serving nothing, and say so.
|
|
3574
|
+
_LOGGER.warning(
|
|
3575
|
+
"camera %s: no audio observed before the serve launched, so the "
|
|
3576
|
+
"audio payload type is unknown; mapping it would stall the mpegts "
|
|
3577
|
+
"mux and serve no video. Continuing without audio.",
|
|
3578
|
+
getattr(self, "device_id", "?"),
|
|
3579
|
+
)
|
|
3580
|
+
_serve_audio = False
|
|
3581
|
+
cmd = _build_sdes_serve_cmd(
|
|
3582
|
+
sdp_path=sdp_path,
|
|
3583
|
+
rtsp_push_url=rtsp_push_url,
|
|
3584
|
+
output_path=output_path,
|
|
3585
|
+
max_seconds=max_seconds,
|
|
3586
|
+
sdes_audio=False,
|
|
3587
|
+
audio_gain_db=self._resolve_sdes_audio_gain_db(),
|
|
3588
|
+
)
|
|
3589
|
+
# Audio matters even more than video here. A multi-PT m-line makes ffmpeg
|
|
3590
|
+
# bind the depacketizer to the FIRST payload type and silently discard the
|
|
3591
|
+
# rest, and the mpegts mux withholds its PAT/PMT until EVERY mapped stream
|
|
3592
|
+
# has produced a packet - so an audio line advertising "0 8" (PCMU first)
|
|
3593
|
+
# on a camera that sends PCMA discards every audio packet and the consumer
|
|
3594
|
+
# receives zero bytes. That takes the video down with it, which is why
|
|
3595
|
+
# enabling serve audio appeared to break streaming outright.
|
|
3596
|
+
if _vpt in (96, 97) or (_serve_audio and _apt in (0, 8)):
|
|
3491
3597
|
def _read_sdp_file() -> str:
|
|
3492
3598
|
with open(sdp_path, encoding="utf-8") as _f_sdp:
|
|
3493
3599
|
return _f_sdp.read()
|
|
3494
3600
|
try:
|
|
3601
|
+
_keep_v = int(_vpt) if _vpt in (96, 97) else None
|
|
3602
|
+
_keep_a = int(_apt) if (_serve_audio and _apt in (0, 8)) else None
|
|
3495
3603
|
_cur_sdp = await asyncio.get_running_loop().run_in_executor(
|
|
3496
3604
|
None, _read_sdp_file)
|
|
3497
3605
|
await asyncio.get_running_loop().run_in_executor(
|
|
3498
3606
|
None, _write_text_file, sdp_path,
|
|
3499
|
-
|
|
3607
|
+
narrow_sdp_payload_types(_cur_sdp, _keep_v, _keep_a))
|
|
3500
3608
|
_status(
|
|
3501
|
-
|
|
3502
|
-
f"
|
|
3609
|
+
"SDES: narrowed ffmpeg SDP to"
|
|
3610
|
+
+ (f" video pt={_keep_v}"
|
|
3611
|
+
f" ({'H265' if _keep_v == 97 else 'H264'})"
|
|
3612
|
+
if _keep_v is not None else "")
|
|
3613
|
+
+ (f" audio pt={_keep_a}"
|
|
3614
|
+
f" ({'PCMA' if _keep_a == 8 else 'PCMU'})"
|
|
3615
|
+
if _keep_a is not None else ""))
|
|
3503
3616
|
except Exception:
|
|
3504
3617
|
_LOGGER.debug("camera %s: swallowed exception in %s", getattr(self, "device_id", "?"), '_open_sdes_stream', exc_info=True)
|
|
3505
3618
|
_LOGGER.info("SDES ffmpeg cmd: %s", " ".join(cmd))
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "python-aidot-cameras"
|
|
7
|
-
version = "0.12.
|
|
7
|
+
version = "0.12.7"
|
|
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"
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7/python_aidot_cameras.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-aidot-cameras
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.7
|
|
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
|
|
@@ -118,6 +118,7 @@ tests/test_sdes_sprop.py
|
|
|
118
118
|
tests/test_sdes_talk.py
|
|
119
119
|
tests/test_sdes_teardown_exit_log.py
|
|
120
120
|
tests/test_sdes_watchdog.py
|
|
121
|
+
tests/test_sdp_payload_type_narrowing.py
|
|
121
122
|
tests/test_security_hardening.py
|
|
122
123
|
tests/test_serializable_login_info.py
|
|
123
124
|
tests/test_serve_relay.py
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
"""Unit tests for _resolve_sdes_serve_audio - SDES serve audio (default
|
|
1
|
+
"""Unit tests for _resolve_sdes_serve_audio - SDES serve audio (default ON).
|
|
2
2
|
|
|
3
|
-
Locks the opt/env precedence (per-camera kwarg wins over AIDOT_SDES_SERVE_AUDIO
|
|
4
|
-
so the wiring is verifiable without a camera.
|
|
3
|
+
Locks the opt/env precedence (per-camera kwarg wins over AIDOT_SDES_SERVE_AUDIO,
|
|
4
|
+
default on for app-parity) so the wiring is verifiable without a camera.
|
|
5
5
|
|
|
6
|
-
The default
|
|
7
|
-
|
|
8
|
-
packet
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
The default was briefly OFF because mapping audio served no video at all - the
|
|
7
|
+
audio m-line advertised PCMU while the bridge sends PCMA, so ffmpeg discarded
|
|
8
|
+
every audio packet and the mpegts mux never wrote its PAT/PMT. The audio payload
|
|
9
|
+
type is narrowed now, with a video-only fallback when it cannot be characterised,
|
|
10
|
+
so audio can no longer cost the picture and the default is back on. The narrowing
|
|
11
|
+
itself is covered by test_sdp_payload_type_narrowing.py.
|
|
11
12
|
"""
|
|
12
13
|
import os
|
|
13
14
|
import sys
|
|
@@ -24,23 +25,21 @@ def _cam():
|
|
|
24
25
|
return _CAM.__new__(_CAM)
|
|
25
26
|
|
|
26
27
|
|
|
27
|
-
def
|
|
28
|
+
def test_default_on_for_app_parity(monkeypatch):
|
|
28
29
|
monkeypatch.delenv("AIDOT_SDES_SERVE_AUDIO", raising=False)
|
|
29
|
-
assert _cam()._resolve_sdes_serve_audio() is
|
|
30
|
+
assert _cam()._resolve_sdes_serve_audio() is True
|
|
30
31
|
|
|
31
32
|
|
|
32
|
-
def
|
|
33
|
-
for val in ("
|
|
33
|
+
def test_env_disables(monkeypatch):
|
|
34
|
+
for val in ("0", "false", "no", "off", " Off "):
|
|
34
35
|
monkeypatch.setenv("AIDOT_SDES_SERVE_AUDIO", val)
|
|
35
|
-
assert _cam()._resolve_sdes_serve_audio() is
|
|
36
|
+
assert _cam()._resolve_sdes_serve_audio() is False, val
|
|
36
37
|
|
|
37
38
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
# unrecognised value must not silently re-enable the mux-stalling path.
|
|
41
|
-
for val in ("0", "false", "no", "off", "", "anything"):
|
|
39
|
+
def test_env_truthy_or_unknown_stays_on(monkeypatch):
|
|
40
|
+
for val in ("1", "true", "yes", "on", "", "anything"):
|
|
42
41
|
monkeypatch.setenv("AIDOT_SDES_SERVE_AUDIO", val)
|
|
43
|
-
assert _cam()._resolve_sdes_serve_audio() is
|
|
42
|
+
assert _cam()._resolve_sdes_serve_audio() is True, val
|
|
44
43
|
|
|
45
44
|
|
|
46
45
|
def test_kwarg_option_wins_over_env(monkeypatch):
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""The ffmpeg SDP must advertise ONE payload type per media line.
|
|
2
|
+
|
|
3
|
+
ffmpeg binds each RTP depacketizer to the first payload type on the m-line and
|
|
4
|
+
silently discards packets carrying any other one. The SDP lists both candidates
|
|
5
|
+
per line because the camera picks one per session, so whichever it actually sends
|
|
6
|
+
has to be promoted before launch.
|
|
7
|
+
|
|
8
|
+
On the video line, getting this wrong costs the picture - that was the known
|
|
9
|
+
H.265 bug. On the audio line it also costs the picture, and far less obviously:
|
|
10
|
+
the mpegts mux withholds its PAT/PMT until every mapped stream has produced a
|
|
11
|
+
packet, so an audio stream whose packets are all discarded leaves the consumer
|
|
12
|
+
with zero bytes, video included, while signaling looks perfectly healthy. That is
|
|
13
|
+
what made enabling serve audio look like it broke streaming outright.
|
|
14
|
+
"""
|
|
15
|
+
from aidot_cameras.camera.sdes_open import narrow_sdp_payload_types
|
|
16
|
+
|
|
17
|
+
_SDP = (
|
|
18
|
+
"v=0\r\n"
|
|
19
|
+
"o=- 1 1 IN IP4 0.0.0.0\r\n"
|
|
20
|
+
"s=aidot-sdes-rx\r\n"
|
|
21
|
+
"t=0 0\r\n"
|
|
22
|
+
"m=audio 34162 RTP/AVP 0 8\r\n"
|
|
23
|
+
"c=IN IP4 127.0.0.1\r\n"
|
|
24
|
+
"a=rtpmap:0 PCMU/8000\r\n"
|
|
25
|
+
"a=rtpmap:8 PCMA/8000\r\n"
|
|
26
|
+
"m=video 38738 RTP/AVP 96 97\r\n"
|
|
27
|
+
"c=IN IP4 127.0.0.1\r\n"
|
|
28
|
+
"a=rtpmap:96 H264/90000\r\n"
|
|
29
|
+
"a=rtpmap:97 H265/90000\r\n"
|
|
30
|
+
"a=fmtp:96 packetization-mode=1\r\n"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_pcma_camera_gets_an_audio_line_advertising_only_pcma():
|
|
35
|
+
# The live failure: the camera sends PCMA (8) but PCMU (0) is listed first, so
|
|
36
|
+
# ffmpeg bound PCMU and discarded every audio packet.
|
|
37
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=96, keep_audio=8)
|
|
38
|
+
assert "m=audio 34162 RTP/AVP 8\r\n" in out
|
|
39
|
+
assert "a=rtpmap:8 PCMA/8000" in out
|
|
40
|
+
assert "a=rtpmap:0 PCMU/8000" not in out
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_pcmu_camera_gets_the_other_one():
|
|
44
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=96, keep_audio=0)
|
|
45
|
+
assert "m=audio 34162 RTP/AVP 0\r\n" in out
|
|
46
|
+
assert "a=rtpmap:0 PCMU/8000" in out
|
|
47
|
+
assert "a=rtpmap:8 PCMA/8000" not in out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_h265_video_narrowing_still_works():
|
|
51
|
+
# The pre-existing behaviour this generalises; it must not regress.
|
|
52
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=97, keep_audio=8)
|
|
53
|
+
assert "m=video 38738 RTP/AVP 97\r\n" in out
|
|
54
|
+
assert "a=rtpmap:97 H265/90000" in out
|
|
55
|
+
assert "a=rtpmap:96 H264/90000" not in out
|
|
56
|
+
assert "a=fmtp:96 " not in out # the dropped codec's fmtp goes too
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_h264_keeps_its_fmtp():
|
|
60
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=96, keep_audio=8)
|
|
61
|
+
assert "a=fmtp:96 packetization-mode=1" in out
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_none_leaves_a_line_untouched():
|
|
65
|
+
# Video observed, audio not (or audio not being served): the audio line must
|
|
66
|
+
# be left exactly as it was rather than half-rewritten.
|
|
67
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=96, keep_audio=None)
|
|
68
|
+
assert "m=audio 34162 RTP/AVP 0 8\r\n" in out
|
|
69
|
+
assert "a=rtpmap:0 PCMU/8000" in out
|
|
70
|
+
assert "a=rtpmap:8 PCMA/8000" in out
|
|
71
|
+
assert "m=video 38738 RTP/AVP 96\r\n" in out
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_both_none_is_an_exact_passthrough():
|
|
75
|
+
assert narrow_sdp_payload_types(_SDP) == _SDP
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_line_endings_and_ordering_survive():
|
|
79
|
+
# ffmpeg is strict about SDP framing; the rewrite must not reflow it.
|
|
80
|
+
out = narrow_sdp_payload_types(_SDP, keep_video=96, keep_audio=8)
|
|
81
|
+
assert out.endswith("\r\n")
|
|
82
|
+
assert "\n\n" not in out
|
|
83
|
+
assert out.index("m=audio") < out.index("m=video") # stream order preserved
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/clock.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/events.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rate.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/rtp.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/sdp.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/stats.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/_vendor/aiortc/utils.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/__init__.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/constants.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/controls.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/lan_control.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/playback.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/protocol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/aidot_cameras/camera/webrtc_open.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_deferred_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_device_login_guard.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_device_user_info_cache.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_not_ready_burst.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_serve_decode_skip.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_serve_open_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_dtls_skip_signaling_wait.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_h264_decode_rate_limit.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_highport_nomination.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_light_active_color_mode.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_credential_refetch.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_credential_self_heal.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_mqtt_session_reconnect.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_no_undefined_names.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_offline_keepalive_pause.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_persistent_mqtt_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_post_merge_hardening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_bridge_break_gate.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_echo_wait_timeout.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_fast_liveplay.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_ffmpeg_stderr_log.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_sdes_teardown_exit_log.py
RENAMED
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_security_hardening.py
RENAMED
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_serializable_login_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_stream_drain_ownership.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_aidot_cameras-0.12.4 → python_aidot_cameras-0.12.7}/tests/test_token_file_robustness.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|