webrtc: fix livestream request compatibility

Fix StreamRequestBody initialization for legacy and multi-camera clients, ignore unknown request fields, and use the supported nonblocking Params API for keyframe requests. Add regression coverage for request normalization.

Based on PR #101 by inauner: https://github.com/firestar5683/StarPilot/pull/101
This commit is contained in:
inauner
2026-08-25 17:05:11 -05:00
committed by firestar5683
parent c82cc2ed9f
commit 4db69dae3b
4 changed files with 47 additions and 5 deletions
+2 -2
View File
@@ -64,7 +64,7 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack):
self._seen_keyframe = False
def request_keyframe(self) -> None:
self.params.put("LivestreamRequestKeyframe", True, block=False)
self.params.put_nonblocking("LivestreamRequestKeyframe", True)
def _build_frame_data(self, msg) -> bytes:
encode_data = getattr(msg, msg.which())
@@ -91,7 +91,7 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack):
if msg is not None:
if not self._seen_keyframe and (getattr(msg, msg.which()).idx.flags & V4L2_BUF_FLAG_KEYFRAME):
self._seen_keyframe = True
self.params.put("LivestreamRequestKeyframe", False, block=False)
self.params.put_nonblocking("LivestreamRequestKeyframe", False)
break
await asyncio.sleep(0.005)
+12 -2
View File
@@ -9,11 +9,21 @@ WEBRTCD_PORT = 5001
@dataclass
class StreamRequestBody:
sdp: str
init_camera: str
enabled: bool
init_camera: str = ""
enabled: bool = True
cameras: list[str] = field(default_factory=list)
bridge_services_in: list[str] = field(default_factory=list)
bridge_services_out: list[str] = field(default_factory=list)
def __post_init__(self):
if not self.cameras:
if self.init_camera:
self.cameras = [self.init_camera]
else:
self.cameras = ["road"]
if not self.init_camera and self.cameras:
self.init_camera = self.cameras[0]
def post_stream_request(body: StreamRequestBody) -> dict:
t_start = time.monotonic()
+29
View File
@@ -0,0 +1,29 @@
from dataclasses import asdict
from openpilot.system.webrtc.helpers import StreamRequestBody
def test_stream_request_body_defaults_legacy_clients_to_road():
body = StreamRequestBody(sdp="offer")
assert body.init_camera == "road"
assert body.enabled is True
assert body.cameras == ["road"]
assert body.bridge_services_in == []
assert body.bridge_services_out == []
def test_stream_request_body_maps_legacy_camera_to_new_camera_list():
body = StreamRequestBody(sdp="offer", init_camera="driver", enabled=False)
assert body.init_camera == "driver"
assert body.cameras == ["driver"]
assert body.enabled is False
def test_stream_request_body_preserves_explicit_multi_camera_request():
body = StreamRequestBody(sdp="offer", cameras=["road", "driver"])
assert body.init_camera == "road"
assert body.cameras == ["road", "driver"]
assert asdict(body)["cameras"] == ["road", "driver"]
+4 -1
View File
@@ -397,7 +397,10 @@ def _text_response(text: str, status: int = 200) -> tuple[int, bytes, str]:
async def handle_get_stream(state: ServerState, raw_body: bytes) -> tuple[int, bytes, str]:
stream_dict = state.streams
body = StreamRequestBody(**json.loads(raw_body))
parsed_dict = json.loads(raw_body)
valid_fields = {f.name for f in StreamRequestBody.__dataclass_fields__.values()}
filtered_dict = {k: v for k, v in parsed_dict.items() if k in valid_fields}
body = StreamRequestBody(**filtered_dict)
async with state.stream_lock:
# don't remove existing connection on prewarm request