Preserve onboard preview recordings across backpressure and share the complete spatial scene
This commit is contained in:
+60
-10
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import queue
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from aiortc import RTCConfiguration, RTCPeerConnection, RTCSessionDescription
|
||||
@@ -36,7 +37,7 @@ def test_failed_media_offer_retires_peer_without_camera_channel(monkeypatch):
|
||||
peers = NodeMediaPeers(None, Camera())
|
||||
try:
|
||||
with pytest.raises(ValueError):
|
||||
await peers.offer({"sdp": SDP_HEADER})
|
||||
await peers.offer({"sdp": SDP_HEADER, "view_id": str(uuid4())})
|
||||
assert peers.items == {}
|
||||
finally:
|
||||
await peers.close_all()
|
||||
@@ -50,23 +51,28 @@ def test_native_webrtc_roundtrip_and_missing_camera_preserve_rrd(monkeypatch):
|
||||
|
||||
class Subscription:
|
||||
def __init__(self):
|
||||
self.pending = None
|
||||
self.output = queue.Queue()
|
||||
self.output.put(payload)
|
||||
|
||||
def read(self):
|
||||
def next_batch(self, *, wait=True):
|
||||
try:
|
||||
return self.output.get(timeout=0.1)
|
||||
self.pending = (1, self.output.get(timeout=0.1), None)
|
||||
return self.pending
|
||||
except queue.Empty:
|
||||
return b""
|
||||
|
||||
def snapshot(self):
|
||||
def snapshot(self, frame=None):
|
||||
return {"type": "lidar-state", "sequence": 1, "age_ms": 0, "points": 5000}
|
||||
|
||||
def close(self):
|
||||
def acknowledge(self, sequence):
|
||||
self.pending = None
|
||||
|
||||
def release(self):
|
||||
pass
|
||||
|
||||
class Hub:
|
||||
def subscribe(self):
|
||||
def subscribe(self, view_id, after):
|
||||
return Subscription()
|
||||
|
||||
class Camera:
|
||||
@@ -98,11 +104,14 @@ def test_native_webrtc_roundtrip_and_missing_camera_preserve_rrd(monkeypatch):
|
||||
return
|
||||
payloads.append(data)
|
||||
if len(payloads) > 1 and sum(map(len, payloads[1:])) == len(payload):
|
||||
channel.send('{"ack":1}')
|
||||
received.set()
|
||||
|
||||
try:
|
||||
await client.setLocalDescription(await client.createOffer())
|
||||
answer = await peers.offer({"sdp": client.localDescription.sdp})
|
||||
answer = await peers.offer({
|
||||
"sdp": client.localDescription.sdp, "view_id": str(uuid4()),
|
||||
})
|
||||
await client.setRemoteDescription(
|
||||
RTCSessionDescription(sdp=answer["sdp"], type="answer")
|
||||
)
|
||||
@@ -210,14 +219,23 @@ def test_native_rrd_idle_does_not_close_camera_or_peer(monkeypatch):
|
||||
video = client.createDataChannel("camera", ordered=True)
|
||||
ready, resumed, camera_frame = asyncio.Event(), asyncio.Event(), asyncio.Event()
|
||||
|
||||
batch_sequence, remaining = 0, 0
|
||||
|
||||
@rrd.on("message")
|
||||
def rrd_message(data):
|
||||
nonlocal batch_sequence, remaining
|
||||
if isinstance(data, str):
|
||||
value = json.loads(data)
|
||||
if value["sequence"] == 1:
|
||||
batch_sequence = value["sequence"]
|
||||
if value["lidar"]["sequence"] == 1:
|
||||
resumed.set()
|
||||
elif data.startswith(b"MCF1") and remaining == 0:
|
||||
remaining = int.from_bytes(data[4:], "big")
|
||||
else:
|
||||
ready.set()
|
||||
remaining -= len(data)
|
||||
if remaining == 0:
|
||||
rrd.send(json.dumps({"ack": batch_sequence}))
|
||||
ready.set()
|
||||
|
||||
@video.on("message")
|
||||
def camera_message(data):
|
||||
@@ -226,7 +244,9 @@ def test_native_rrd_idle_does_not_close_camera_or_peer(monkeypatch):
|
||||
|
||||
try:
|
||||
await client.setLocalDescription(await client.createOffer())
|
||||
answer = await peers.offer({"sdp": client.localDescription.sdp})
|
||||
answer = await peers.offer({
|
||||
"sdp": client.localDescription.sdp, "view_id": str(uuid4()),
|
||||
})
|
||||
await client.setRemoteDescription(
|
||||
RTCSessionDescription(sdp=answer["sdp"], type="answer")
|
||||
)
|
||||
@@ -251,3 +271,33 @@ def test_native_rrd_idle_does_not_close_camera_or_peer(monkeypatch):
|
||||
assert not peers.items
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_network_backpressure_longer_than_two_seconds_is_a_pause():
|
||||
"""Bounded synthetic scheduler pause; no sockets, scanner or load generation."""
|
||||
import time
|
||||
|
||||
class Channel:
|
||||
readyState = "open"
|
||||
sent = []
|
||||
blocked = True
|
||||
|
||||
@property
|
||||
def bufferedAmount(self):
|
||||
return 1024 * 1024 if self.blocked else 0
|
||||
|
||||
def send(self, value):
|
||||
self.sent.append(value)
|
||||
|
||||
async def run():
|
||||
channel = Channel()
|
||||
peers = object.__new__(NodeMediaPeers)
|
||||
task = asyncio.create_task(peers.send(channel, b"small-synthetic-payload", lambda: True))
|
||||
started = time.monotonic()
|
||||
await asyncio.sleep(2.1)
|
||||
assert not task.done() and channel.sent == []
|
||||
channel.blocked = False
|
||||
await asyncio.wait_for(task, 1)
|
||||
assert time.monotonic() - started >= 2
|
||||
assert channel.sent == [b"MCF1\x00\x00\x00\x17", b"small-synthetic-payload"]
|
||||
asyncio.run(run())
|
||||
|
||||
Reference in New Issue
Block a user