Изменения в потоке камер
This commit is contained in:
@@ -697,8 +697,8 @@ class RTKeyCamerasApi:
|
|||||||
url = url.update_query({"token": streamer_token})
|
url = url.update_query({"token": streamer_token})
|
||||||
return str(url)
|
return str(url)
|
||||||
|
|
||||||
async def get_camera_stream_url(self, camera_id: str, retry: bool = False) -> str | None:
|
async def get_camera_stream_url(self, camera_id: str) -> str | None:
|
||||||
if self._tokens_invalid and not retry:
|
if self._tokens_invalid:
|
||||||
_LOGGER.debug(f"Токены протухшие, обновляем перед запросом стрима для {camera_id}")
|
_LOGGER.debug(f"Токены протухшие, обновляем перед запросом стрима для {camera_id}")
|
||||||
await self.refresh_tokens()
|
await self.refresh_tokens()
|
||||||
|
|
||||||
@@ -712,27 +712,6 @@ class RTKeyCamerasApi:
|
|||||||
})
|
})
|
||||||
if streamer_token:
|
if streamer_token:
|
||||||
url = url.update_query({"token": streamer_token})
|
url = url.update_query({"token": streamer_token})
|
||||||
|
|
||||||
r = await self._api_get(url, _DEFAULT_HEADERS, timeout=10)
|
|
||||||
if r is None:
|
|
||||||
return str(url)
|
|
||||||
|
|
||||||
if r.status == 403 and not retry:
|
|
||||||
camera_name = self.get_camera_name(camera_id)
|
|
||||||
_LOGGER.warning(f"Токен стрима для {camera_name} ({camera_id}) протух (403), запускаем обновление...")
|
|
||||||
self._tokens_invalid = True
|
|
||||||
await self.refresh_tokens()
|
|
||||||
return await self.get_camera_stream_url(camera_id, retry=True)
|
|
||||||
|
|
||||||
if r.status == 403 and retry:
|
|
||||||
camera_name = self.get_camera_name(camera_id)
|
|
||||||
_LOGGER.error(f"Повторный 403 для стрима {camera_name} ({camera_id}) — проблема с авторизацией")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if r.status == 200:
|
|
||||||
return str(url)
|
|
||||||
|
|
||||||
_LOGGER.warning(f"Стрим для {camera_id}: статус {r.status}")
|
|
||||||
return str(url)
|
return str(url)
|
||||||
|
|
||||||
# === Домофон: открытие ===
|
# === Домофон: открытие ===
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components.camera import Camera
|
from homeassistant.components.camera import Camera, CameraEntityFeature
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
@@ -38,6 +38,8 @@ async def async_setup_entry(
|
|||||||
class RTKeyCamera(Camera):
|
class RTKeyCamera(Camera):
|
||||||
"""Live-камера (для всех устройств — и камер, и домофонов)."""
|
"""Live-камера (для всех устройств — и камер, и домофонов)."""
|
||||||
|
|
||||||
|
_attr_supported_features = CameraEntityFeature.STREAM
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -108,10 +110,11 @@ class RTKeyCamera(Camera):
|
|||||||
|
|
||||||
|
|
||||||
class RTKeyEventCamera(Camera):
|
class RTKeyEventCamera(Camera):
|
||||||
"""Архив события: thumbnail-скриншот + ссылка на видео-файл."""
|
"""Архив события: проигрывание сохранённого видео."""
|
||||||
|
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||||
|
_attr_supported_features = CameraEntityFeature.STREAM
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -175,17 +178,24 @@ class RTKeyEventCamera(Camera):
|
|||||||
self._archive_error = archive_error
|
self._archive_error = archive_error
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def stream_source(self) -> str | None:
|
||||||
|
if self._archive_path and Path(self._archive_path).exists():
|
||||||
|
return f"file://{self._archive_path}"
|
||||||
|
return None
|
||||||
|
|
||||||
async def async_camera_image(
|
async def async_camera_image(
|
||||||
self, width: int | None = None, height: int | None = None
|
self, width: int | None = None, height: int | None = None
|
||||||
) -> bytes | None:
|
) -> bytes | None:
|
||||||
"""Скриншот камеры как thumbnail для карточки."""
|
event_info = self.cameras_api.last_events.get(self.intercom_id)
|
||||||
if self.camera_id:
|
if event_info and event_info.get("screenshot_path"):
|
||||||
return await self.cameras_api.get_camera_image(self.camera_id)
|
path = Path(event_info["screenshot_path"])
|
||||||
|
if path.exists():
|
||||||
|
return await self.hass.async_add_executor_job(path.read_bytes)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
return self.camera_id is not None and self.cameras_api.is_camera_available(self.camera_id)
|
return self._archive_path is not None and Path(self._archive_path).exists()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, Any]:
|
def extra_state_attributes(self) -> dict[str, Any]:
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"documentation": "https://github.com/sirmrak/RTKey",
|
"documentation": "https://github.com/sirmrak/RTKey",
|
||||||
"issue_tracker": "https://github.com/sirmrak/RTKey/issues",
|
"issue_tracker": "https://github.com/sirmrak/RTKey/issues",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"codeowners": ["@sirmrak"]
|
"codeowners": ["@sirmrak"]
|
||||||
|
|||||||
Reference in New Issue
Block a user