Skip to content
Snippets Groups Projects
Unverified Commit 0999297e authored by Joost Lekkerkerker's avatar Joost Lekkerkerker Committed by GitHub
Browse files

Introduce Jellyfin client/server base entities (#127572)

parent 62ae2a3b
No related branches found
No related tags found
No related merge requests found
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_NAME, DOMAIN from .const import DEFAULT_NAME, DOMAIN
...@@ -15,20 +16,60 @@ class JellyfinEntity(CoordinatorEntity[JellyfinDataUpdateCoordinator]): ...@@ -15,20 +16,60 @@ class JellyfinEntity(CoordinatorEntity[JellyfinDataUpdateCoordinator]):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(
self, class JellyfinServerEntity(JellyfinEntity):
coordinator: JellyfinDataUpdateCoordinator, """Defines a base Jellyfin server entity."""
description: EntityDescription,
) -> None: def __init__(self, coordinator: JellyfinDataUpdateCoordinator) -> None:
"""Initialize the Jellyfin entity.""" """Initialize the Jellyfin entity."""
super().__init__(coordinator) super().__init__(coordinator)
self.coordinator = coordinator
self.entity_description = description
self._attr_unique_id = f"{coordinator.server_id}-{description.key}"
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE, entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self.coordinator.server_id)}, identifiers={(DOMAIN, coordinator.server_id)},
manufacturer=DEFAULT_NAME, manufacturer=DEFAULT_NAME,
name=self.coordinator.server_name, name=coordinator.server_name,
sw_version=self.coordinator.server_version, sw_version=coordinator.server_version,
) )
class JellyfinClientEntity(JellyfinEntity):
"""Defines a base Jellyfin client entity."""
def __init__(
self,
coordinator: JellyfinDataUpdateCoordinator,
session_id: str,
) -> None:
"""Initialize the Jellyfin entity."""
super().__init__(coordinator)
self.session_id = session_id
self.device_id: str = self.session_data["DeviceId"]
self.device_name: str = self.session_data["DeviceName"]
self.client_name: str = self.session_data["Client"]
self.app_version: str = self.session_data["ApplicationVersion"]
self.capabilities: dict[str, Any] = self.session_data["Capabilities"]
if self.capabilities.get("SupportsPersistentIdentifier", False):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.device_id)},
manufacturer="Jellyfin",
model=self.client_name,
name=self.device_name,
sw_version=self.app_version,
via_device=(DOMAIN, coordinator.server_id),
)
self._attr_name = None
else:
self._attr_device_info = None
self._attr_has_entity_name = False
self._attr_name = self.device_name
@property
def session_data(self) -> dict[str, Any]:
"""Return the session data."""
return self.coordinator.data[self.session_id]
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and self.session_id in self.coordinator.data
...@@ -7,22 +7,20 @@ from typing import Any ...@@ -7,22 +7,20 @@ from typing import Any
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
BrowseMedia, BrowseMedia,
MediaPlayerEntity, MediaPlayerEntity,
MediaPlayerEntityDescription,
MediaPlayerEntityFeature, MediaPlayerEntityFeature,
MediaPlayerState, MediaPlayerState,
MediaType, MediaType,
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.dt import parse_datetime from homeassistant.util.dt import parse_datetime
from . import JellyfinConfigEntry from . import JellyfinConfigEntry
from .browse_media import build_item_response, build_root_response from .browse_media import build_item_response, build_root_response
from .client_wrapper import get_artwork_url from .client_wrapper import get_artwork_url
from .const import CONTENT_TYPE_MAP, DOMAIN, LOGGER from .const import CONTENT_TYPE_MAP, LOGGER
from .coordinator import JellyfinDataUpdateCoordinator from .coordinator import JellyfinDataUpdateCoordinator
from .entity import JellyfinEntity from .entity import JellyfinClientEntity
async def async_setup_entry( async def async_setup_entry(
...@@ -37,11 +35,9 @@ async def async_setup_entry( ...@@ -37,11 +35,9 @@ async def async_setup_entry(
def handle_coordinator_update() -> None: def handle_coordinator_update() -> None:
"""Add media player per session.""" """Add media player per session."""
entities: list[MediaPlayerEntity] = [] entities: list[MediaPlayerEntity] = []
for session_id, session_data in coordinator.data.items(): for session_id in coordinator.data:
if session_id not in coordinator.session_ids: if session_id not in coordinator.session_ids:
entity: MediaPlayerEntity = JellyfinMediaPlayer( entity: MediaPlayerEntity = JellyfinMediaPlayer(coordinator, session_id)
coordinator, session_id, session_data
)
LOGGER.debug("Creating media player for session: %s", session_id) LOGGER.debug("Creating media player for session: %s", session_id)
coordinator.session_ids.add(session_id) coordinator.session_ids.add(session_id)
entities.append(entity) entities.append(entity)
...@@ -52,60 +48,28 @@ async def async_setup_entry( ...@@ -52,60 +48,28 @@ async def async_setup_entry(
entry.async_on_unload(coordinator.async_add_listener(handle_coordinator_update)) entry.async_on_unload(coordinator.async_add_listener(handle_coordinator_update))
class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity): class JellyfinMediaPlayer(JellyfinClientEntity, MediaPlayerEntity):
"""Represents a Jellyfin Player device.""" """Represents a Jellyfin Player device."""
def __init__( def __init__(
self, self,
coordinator: JellyfinDataUpdateCoordinator, coordinator: JellyfinDataUpdateCoordinator,
session_id: str, session_id: str,
session_data: dict[str, Any],
) -> None: ) -> None:
"""Initialize the Jellyfin Media Player entity.""" """Initialize the Jellyfin Media Player entity."""
super().__init__( super().__init__(coordinator, session_id)
coordinator, self._attr_unique_id = f"{coordinator.server_id}-{session_id}"
MediaPlayerEntityDescription(
key=session_id,
),
)
self.session_id = session_id self.now_playing: dict[str, Any] | None = self.session_data.get(
self.session_data: dict[str, Any] | None = session_data "NowPlayingItem"
self.device_id: str = session_data["DeviceId"] )
self.device_name: str = session_data["DeviceName"] self.play_state: dict[str, Any] | None = self.session_data.get("PlayState")
self.client_name: str = session_data["Client"]
self.app_version: str = session_data["ApplicationVersion"]
self.capabilities: dict[str, Any] = session_data["Capabilities"]
self.now_playing: dict[str, Any] | None = session_data.get("NowPlayingItem")
self.play_state: dict[str, Any] | None = session_data.get("PlayState")
if self.capabilities.get("SupportsPersistentIdentifier", False):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.device_id)},
manufacturer="Jellyfin",
model=self.client_name,
name=self.device_name,
sw_version=self.app_version,
via_device=(DOMAIN, coordinator.server_id),
)
self._attr_name = None
else:
self._attr_device_info = None
self._attr_has_entity_name = False
self._attr_name = self.device_name
self._update_from_session_data() self._update_from_session_data()
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:
self.session_data = ( if self.available:
self.coordinator.data.get(self.session_id)
if self.coordinator.data is not None
else None
)
if self.session_data is not None:
self.now_playing = self.session_data.get("NowPlayingItem") self.now_playing = self.session_data.get("NowPlayingItem")
self.play_state = self.session_data.get("PlayState") self.play_state = self.session_data.get("PlayState")
else: else:
...@@ -135,7 +99,7 @@ class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity): ...@@ -135,7 +99,7 @@ class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity):
volume_muted = False volume_muted = False
volume_level = None volume_level = None
if self.session_data is not None: if self.available:
state = MediaPlayerState.IDLE state = MediaPlayerState.IDLE
media_position_updated = ( media_position_updated = (
parse_datetime(self.session_data["LastPlaybackCheckIn"]) parse_datetime(self.session_data["LastPlaybackCheckIn"])
...@@ -233,11 +197,6 @@ class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity): ...@@ -233,11 +197,6 @@ class JellyfinMediaPlayer(JellyfinEntity, MediaPlayerEntity):
return features return features
@property
def available(self) -> bool:
"""Return if entity is available."""
return self.coordinator.last_update_success and self.session_data is not None
def media_seek(self, position: float) -> None: def media_seek(self, position: float) -> None:
"""Send seek command.""" """Send seek command."""
self.coordinator.api_client.jellyfin.remote_seek( self.coordinator.api_client.jellyfin.remote_seek(
......
...@@ -11,8 +11,8 @@ from homeassistant.core import HomeAssistant ...@@ -11,8 +11,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from . import JellyfinConfigEntry from . import JellyfinConfigEntry, JellyfinDataUpdateCoordinator
from .entity import JellyfinEntity from .entity import JellyfinServerEntity
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
...@@ -50,15 +50,25 @@ async def async_setup_entry( ...@@ -50,15 +50,25 @@ async def async_setup_entry(
coordinator = entry.runtime_data coordinator = entry.runtime_data
async_add_entities( async_add_entities(
JellyfinSensor(coordinator, description) for description in SENSOR_TYPES JellyfinServerSensor(coordinator, description) for description in SENSOR_TYPES
) )
class JellyfinSensor(JellyfinEntity, SensorEntity): class JellyfinServerSensor(JellyfinServerEntity, SensorEntity):
"""Defines a Jellyfin sensor entity.""" """Defines a Jellyfin sensor entity."""
entity_description: JellyfinSensorEntityDescription entity_description: JellyfinSensorEntityDescription
def __init__(
self,
coordinator: JellyfinDataUpdateCoordinator,
description: JellyfinSensorEntityDescription,
) -> None:
"""Initialize Jellyfin sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.server_id}-{description.key}"
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment