"""MJPEG HTTP stream server for browser-based monitoring."""
from __future__ import annotations
import threading
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Optional
import cv2
from utils.config import load_config
from utils.logger import get_logger
log = get_logger("Inference", "streaming")
_CFG = load_config("core")["stream"]
_stream_frame: Optional[bytes] = None
_stream_lock = threading.Lock()
class MJPEGHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(b'
'
b'
'
b'')
elif self.path == "/stream":
self.send_response(200)
self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=frame")
self.end_headers()
while True:
with _stream_lock:
jpeg = _stream_frame
if jpeg is None:
time.sleep(0.03)
continue
try:
self.wfile.write(b"--frame\r\n"
b"Content-Type: image/jpeg\r\n\r\n" + jpeg + b"\r\n")
except BrokenPipeError:
break
else:
self.send_error(404)
def log_message(self, format, *args):
pass
def start_stream_server(port: Optional[int] = None, host: Optional[str] = None):
h = host or _CFG["host"]
p = port if port is not None else _CFG["port"]
server = HTTPServer((h, p), MJPEGHandler)
t = threading.Thread(target=server.serve_forever, daemon=True)
t.start()
log.info(f"MJPEG stream server started on http://{h}:{p}")
return server
def update_stream_frame(frame):
global _stream_frame
_, jpeg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, _CFG["jpeg_quality"]])
with _stream_lock:
_stream_frame = jpeg.tobytes()