"""Media Source models.""" from __future__ import annotations from abc import ABC from dataclasses import dataclass from typing import Any, cast from homeassistant.components.media_player import BrowseMedia from homeassistant.components.media_player.const import ( MEDIA_CLASS_CHANNEL, MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_CHANNEL, MEDIA_TYPE_CHANNELS, ) from homeassistant.core import HomeAssistant, callback from .const import DOMAIN, URI_SCHEME, URI_SCHEME_REGEX @dataclass class PlayMedia: """Represents a playable media.""" url: str mime_type: str class BrowseMediaSource(BrowseMedia): """Represent a browsable media file.""" children: list[BrowseMediaSource | BrowseMedia] | None def __init__( self, *, domain: str | None, identifier: str | None, **kwargs: Any ) -> None: """Initialize media source browse media.""" media_content_id = f"{URI_SCHEME}{domain or ''}" if identifier: media_content_id += f"/{identifier}" super().__init__(media_content_id=media_content_id, **kwargs) self.domain = domain self.identifier = identifier @dataclass class MediaSourceItem: """A parsed media item.""" hass: HomeAssistant domain: str | None identifier: str async def async_browse(self) -> BrowseMediaSource: """Browse this item.""" if self.domain is None: base = BrowseMediaSource( domain=None, identifier=None, media_class=MEDIA_CLASS_DIRECTORY, media_content_type=MEDIA_TYPE_CHANNELS, title="Media Sources", can_play=False, can_expand=True, children_media_class=MEDIA_CLASS_CHANNEL, ) base.children = [ BrowseMediaSource( domain=source.domain, identifier=None, media_class=MEDIA_CLASS_CHANNEL, media_content_type=MEDIA_TYPE_CHANNEL, title=source.name, can_play=False, can_expand=True, ) for source in self.hass.data[DOMAIN].values() ] return base return await self.async_media_source().async_browse_media(self) async def async_resolve(self) -> PlayMedia: """Resolve to playable item.""" return await self.async_media_source().async_resolve_media(self) @callback def async_media_source(self) -> MediaSource: """Return media source that owns this item.""" return cast(MediaSource, self.hass.data[DOMAIN][self.domain]) @classmethod def from_uri(cls, hass: HomeAssistant, uri: str) -> MediaSourceItem: """Create an item from a uri.""" if not (match := URI_SCHEME_REGEX.match(uri)): raise ValueError("Invalid media source URI") domain = match.group("domain") identifier = match.group("identifier") return cls(hass, domain, identifier) class MediaSource(ABC): """Represents a source of media files.""" name: str | None = None def __init__(self, domain: str) -> None: """Initialize a media source.""" self.domain = domain if not self.name: self.name = domain async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: """Resolve a media item to a playable item.""" raise NotImplementedError async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource: """Browse media.""" raise NotImplementedError