diff --git a/README.md b/README.md index 53093e0..d47123b 100644 --- a/README.md +++ b/README.md @@ -58,4 +58,5 @@ Slot and ACE entities are pre-created and automatically enabled/disabled based o - This integration communicates with KX-Bridge HTTP endpoints and does not connect directly to the printer. - Keep KX-Bridge and Home Assistant on a trusted local network. -- Native WebRTC is not implemented. For WebRTC in Home Assistant, point `go2rtc` (or another WebRTC-capable add-on) to the camera RTSP source. +- Camera streaming prefers the bridge H.264 endpoint (`/api/camera/h264`, MPEG-TS passthrough) on newer bridge releases, with RTSP/MJPEG fallback for older releases. +- Native WebRTC is not implemented. For WebRTC in Home Assistant, point `go2rtc` (or another WebRTC-capable add-on) to the camera source you prefer (H.264 bridge endpoint or RTSP). diff --git a/custom_components/kobrax_lan/api.py b/custom_components/kobrax_lan/api.py index 933b2c0..541996f 100644 --- a/custom_components/kobrax_lan/api.py +++ b/custom_components/kobrax_lan/api.py @@ -24,6 +24,9 @@ class KobraXApiClient: def camera_stream_proxy_url(self) -> str: return self._url("/api/camera/stream") + def camera_h264_proxy_url(self) -> str: + return self._url("/api/camera/h264") + async def _get_json(self, path: str) -> dict[str, Any]: try: async with self._session.get(self._url(path)) as response: diff --git a/custom_components/kobrax_lan/camera.py b/custom_components/kobrax_lan/camera.py index be96964..c1c0351 100644 --- a/custom_components/kobrax_lan/camera.py +++ b/custom_components/kobrax_lan/camera.py @@ -1,5 +1,7 @@ from __future__ import annotations +import re + from homeassistant.components.camera import Camera, CameraEntityFeature from .api import KobraXApiError @@ -21,14 +23,27 @@ class KobraXCamera(KobraXEntity, Camera): @property def extra_state_attributes(self) -> dict[str, str]: + api = self.hass.data[DOMAIN][self._entry.entry_id]["api"] camera_url = self.state_data.get("camera_url") attrs = { - "camera_mjpeg_proxy_url": self.hass.data[DOMAIN][self._entry.entry_id]["api"].camera_stream_proxy_url(), + "camera_h264_proxy_url": api.camera_h264_proxy_url(), + "camera_mjpeg_proxy_url": api.camera_stream_proxy_url(), } if isinstance(camera_url, str) and camera_url: attrs["camera_rtsp_url"] = camera_url return attrs + @staticmethod + def _bridge_supports_h264_stream(version: str | None) -> bool: + """Return True when bridge version includes /api/camera/h264 support.""" + if not version: + return False + match = re.search(r"(\d+)\.(\d+)\.(\d+)", version) + if not match: + return False + major, minor, patch = (int(part) for part in match.groups()) + return (major, minor, patch) >= (0, 9, 17) + async def stream_source(self) -> str | None: api = self.hass.data[DOMAIN][self._entry.entry_id]["api"] try: @@ -36,6 +51,10 @@ class KobraXCamera(KobraXEntity, Camera): except KobraXApiError: pass + version = self.state_data.get("version") + if isinstance(version, str) and self._bridge_supports_h264_stream(version): + return api.camera_h264_proxy_url() + camera_url = self.state_data.get("camera_url") if isinstance(camera_url, str) and camera_url: return camera_url