| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- """One-shot captures wait for a live view of an external MJPEG camera (SP-C7).
- An ESP32-style MJPEG camera serves one client. Its live view ran no process
- the stream registry tracks, so ``is_stream_active`` said no viewer was
- attached, and timelapse frames, finish photos, Obico and the plate check
- opened a second connection the camera refused. A camera with a separate
- snapshot URL is left out: that URL serves captures beside the live view, at
- full quality, and the plate check's references come from it (#1177).
- """
- import asyncio
- from unittest.mock import patch
- import pytest
- from httpx import AsyncClient
- from backend.app.api.routes import camera
- pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
- FRAME = b"\xff\xd8frame\xff\xd9"
- @pytest.fixture(autouse=True)
- def _forget_frames():
- yield
- for state in (camera._last_frames, camera._last_frame_times, camera._stream_start_times):
- state.clear()
- async def _watch(async_client: AsyncClient, printer_id: int) -> dict:
- seen: dict = {}
- async def _stream(*_args, on_frame=None, **_kwargs):
- on_frame(FRAME)
- yield b"--frame\r\nContent-Type: image/jpeg\r\n\r\n\xff\xd8\xff\xd9\r\n"
- await asyncio.sleep(0.05)
- seen["active"] = camera.is_stream_active(printer_id)
- seen["defer"] = camera.live_frame_for_capture(printer_id)[0]
- yield b"--frame\r\nContent-Type: image/jpeg\r\n\r\n\xff\xd8\xff\xd9\r\n"
- with patch("backend.app.services.external_camera.generate_mjpeg_stream", _stream):
- response = await async_client.get(f"/api/v1/printers/{printer_id}/camera/stream")
- assert response.status_code == 200
- await asyncio.sleep(0.1)
- seen["after"] = camera.is_stream_active(printer_id)
- seen["frame_after"] = camera._last_frames.get(printer_id)
- return seen
- async def test_a_watched_mjpeg_camera_is_not_dialled_again(async_client: AsyncClient, printer_factory):
- printer = await printer_factory(
- external_camera_enabled=True,
- external_camera_url="http://192.0.2.50:81/stream",
- external_camera_type="mjpeg",
- )
- seen = await _watch(async_client, printer.id)
- assert seen["active"] is True
- assert seen["defer"] is True
- assert seen["after"] is False
- # The camera's upstream ended while the viewer was still counted: its last
- # frame must not outlive it, or a plate check or the next viewer's start
- # takes an old picture for a current one.
- assert seen["frame_after"] is None
- async def test_a_camera_with_a_snapshot_url_keeps_using_it(async_client: AsyncClient, printer_factory):
- printer = await printer_factory(
- external_camera_enabled=True,
- # Its own URL: a fan-out key of its own, not the first test's upstream
- # still in its grace.
- external_camera_url="http://192.0.2.51:81/stream",
- external_camera_type="mjpeg",
- external_camera_snapshot_url="http://192.0.2.51/capture",
- )
- seen = await _watch(async_client, printer.id)
- assert seen["active"] is False
|