mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Provide most media metadata in DlnaDmrEntity (#56728)
Co-authored-by: Steven Looman <steven.looman@gmail.com>
This commit is contained in:
parent
718f8d8bf7
commit
f7d95588f8
@ -1,8 +1,12 @@
|
|||||||
"""Constants for the DLNA DMR component."""
|
"""Constants for the DLNA DMR component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
import logging
|
import logging
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
from homeassistant.components.media_player import const as _mp_const
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
DOMAIN: Final = "dlna_dmr"
|
DOMAIN: Final = "dlna_dmr"
|
||||||
@ -14,3 +18,43 @@ CONF_POLL_AVAILABILITY: Final = "poll_availability"
|
|||||||
DEFAULT_NAME: Final = "DLNA Digital Media Renderer"
|
DEFAULT_NAME: Final = "DLNA Digital Media Renderer"
|
||||||
|
|
||||||
CONNECT_TIMEOUT: Final = 10
|
CONNECT_TIMEOUT: Final = 10
|
||||||
|
|
||||||
|
# Map UPnP class to media_player media_content_type
|
||||||
|
MEDIA_TYPE_MAP: Mapping[str, str] = {
|
||||||
|
"object": _mp_const.MEDIA_TYPE_URL,
|
||||||
|
"object.item": _mp_const.MEDIA_TYPE_URL,
|
||||||
|
"object.item.imageItem": _mp_const.MEDIA_TYPE_IMAGE,
|
||||||
|
"object.item.imageItem.photo": _mp_const.MEDIA_TYPE_IMAGE,
|
||||||
|
"object.item.audioItem": _mp_const.MEDIA_TYPE_MUSIC,
|
||||||
|
"object.item.audioItem.musicTrack": _mp_const.MEDIA_TYPE_MUSIC,
|
||||||
|
"object.item.audioItem.audioBroadcast": _mp_const.MEDIA_TYPE_MUSIC,
|
||||||
|
"object.item.audioItem.audioBook": _mp_const.MEDIA_TYPE_PODCAST,
|
||||||
|
"object.item.videoItem": _mp_const.MEDIA_TYPE_VIDEO,
|
||||||
|
"object.item.videoItem.movie": _mp_const.MEDIA_TYPE_MOVIE,
|
||||||
|
"object.item.videoItem.videoBroadcast": _mp_const.MEDIA_TYPE_TVSHOW,
|
||||||
|
"object.item.videoItem.musicVideoClip": _mp_const.MEDIA_TYPE_VIDEO,
|
||||||
|
"object.item.playlistItem": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.item.textItem": _mp_const.MEDIA_TYPE_URL,
|
||||||
|
"object.item.bookmarkItem": _mp_const.MEDIA_TYPE_URL,
|
||||||
|
"object.item.epgItem": _mp_const.MEDIA_TYPE_EPISODE,
|
||||||
|
"object.item.epgItem.audioProgram": _mp_const.MEDIA_TYPE_EPISODE,
|
||||||
|
"object.item.epgItem.videoProgram": _mp_const.MEDIA_TYPE_EPISODE,
|
||||||
|
"object.container": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.container.person": _mp_const.MEDIA_TYPE_ARTIST,
|
||||||
|
"object.container.person.musicArtist": _mp_const.MEDIA_TYPE_ARTIST,
|
||||||
|
"object.container.playlistContainer": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.container.album": _mp_const.MEDIA_TYPE_ALBUM,
|
||||||
|
"object.container.album.musicAlbum": _mp_const.MEDIA_TYPE_ALBUM,
|
||||||
|
"object.container.album.photoAlbum": _mp_const.MEDIA_TYPE_ALBUM,
|
||||||
|
"object.container.genre": _mp_const.MEDIA_TYPE_GENRE,
|
||||||
|
"object.container.genre.musicGenre": _mp_const.MEDIA_TYPE_GENRE,
|
||||||
|
"object.container.genre.movieGenre": _mp_const.MEDIA_TYPE_GENRE,
|
||||||
|
"object.container.channelGroup": _mp_const.MEDIA_TYPE_CHANNELS,
|
||||||
|
"object.container.channelGroup.audioChannelGroup": _mp_const.MEDIA_TYPE_CHANNELS,
|
||||||
|
"object.container.channelGroup.videoChannelGroup": _mp_const.MEDIA_TYPE_CHANNELS,
|
||||||
|
"object.container.epgContainer": _mp_const.MEDIA_TYPE_TVSHOW,
|
||||||
|
"object.container.storageSystem": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.container.storageVolume": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.container.storageFolder": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
"object.container.bookmarkFolder": _mp_const.MEDIA_TYPE_PLAYLIST,
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "DLNA Digital Media Renderer",
|
"name": "DLNA Digital Media Renderer",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
|
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
|
||||||
"requirements": ["async-upnp-client==0.22.3"],
|
"requirements": ["async-upnp-client==0.22.4"],
|
||||||
"dependencies": ["network", "ssdp"],
|
"dependencies": ["network", "ssdp"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
|
@ -37,7 +37,6 @@ from homeassistant.const import (
|
|||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_PAUSED,
|
STATE_PAUSED,
|
||||||
STATE_PLAYING,
|
STATE_PLAYING,
|
||||||
STATE_UNKNOWN,
|
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry, entity_registry
|
from homeassistant.helpers import device_registry, entity_registry
|
||||||
@ -51,6 +50,7 @@ from .const import (
|
|||||||
DEFAULT_NAME,
|
DEFAULT_NAME,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
LOGGER as _LOGGER,
|
LOGGER as _LOGGER,
|
||||||
|
MEDIA_TYPE_MAP,
|
||||||
)
|
)
|
||||||
from .data import EventListenAddr, get_domain_data
|
from .data import EventListenAddr, get_domain_data
|
||||||
|
|
||||||
@ -389,11 +389,6 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
|||||||
domain_data = get_domain_data(self.hass)
|
domain_data = get_domain_data(self.hass)
|
||||||
await domain_data.async_release_event_notifier(self._event_addr)
|
await domain_data.async_release_event_notifier(self._event_addr)
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Device is available when we have a connection to it."""
|
|
||||||
return self._device is not None and self._device.profile_device.available
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Retrieve the latest data."""
|
"""Retrieve the latest data."""
|
||||||
if not self._device:
|
if not self._device:
|
||||||
@ -426,6 +421,44 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
|||||||
self.check_available = True
|
self.check_available = True
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Device is available when we have a connection to it."""
|
||||||
|
return self._device is not None and self._device.profile_device.available
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Report the UDN (Unique Device Name) as this entity's unique ID."""
|
||||||
|
return self.udn
|
||||||
|
|
||||||
|
@property
|
||||||
|
def usn(self) -> str:
|
||||||
|
"""Get the USN based on the UDN (Unique Device Name) and device type."""
|
||||||
|
return f"{self.udn}::{self.device_type}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> str | None:
|
||||||
|
"""State of the player."""
|
||||||
|
if not self._device or not self.available:
|
||||||
|
return STATE_OFF
|
||||||
|
if self._device.transport_state is None:
|
||||||
|
return STATE_ON
|
||||||
|
if self._device.transport_state in (
|
||||||
|
TransportState.PLAYING,
|
||||||
|
TransportState.TRANSITIONING,
|
||||||
|
):
|
||||||
|
return STATE_PLAYING
|
||||||
|
if self._device.transport_state in (
|
||||||
|
TransportState.PAUSED_PLAYBACK,
|
||||||
|
TransportState.PAUSED_RECORDING,
|
||||||
|
):
|
||||||
|
return STATE_PAUSED
|
||||||
|
if self._device.transport_state == TransportState.VENDOR_DEFINED:
|
||||||
|
# Unable to map this state to anything reasonable, so it's "Unknown"
|
||||||
|
return None
|
||||||
|
|
||||||
|
return STATE_IDLE
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self) -> int:
|
def supported_features(self) -> int:
|
||||||
"""Flag media player features that are supported at this moment.
|
"""Flag media player features that are supported at this moment.
|
||||||
@ -552,7 +585,8 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
|||||||
"""Title of current playing media."""
|
"""Title of current playing media."""
|
||||||
if not self._device:
|
if not self._device:
|
||||||
return None
|
return None
|
||||||
return self._device.media_title
|
# Use the best available title
|
||||||
|
return self._device.media_program_title or self._device.media_title
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_image_url(self) -> str | None:
|
def media_image_url(self) -> str | None:
|
||||||
@ -562,26 +596,18 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
|||||||
return self._device.media_image_url
|
return self._device.media_image_url
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> str:
|
def media_content_id(self) -> str | None:
|
||||||
"""State of the player."""
|
"""Content ID of current playing media."""
|
||||||
if not self._device or not self.available:
|
if not self._device:
|
||||||
return STATE_OFF
|
return None
|
||||||
if self._device.transport_state is None:
|
return self._device.current_track_uri
|
||||||
return STATE_ON
|
|
||||||
if self._device.transport_state in (
|
|
||||||
TransportState.PLAYING,
|
|
||||||
TransportState.TRANSITIONING,
|
|
||||||
):
|
|
||||||
return STATE_PLAYING
|
|
||||||
if self._device.transport_state in (
|
|
||||||
TransportState.PAUSED_PLAYBACK,
|
|
||||||
TransportState.PAUSED_RECORDING,
|
|
||||||
):
|
|
||||||
return STATE_PAUSED
|
|
||||||
if self._device.transport_state == TransportState.VENDOR_DEFINED:
|
|
||||||
return STATE_UNKNOWN
|
|
||||||
|
|
||||||
return STATE_IDLE
|
@property
|
||||||
|
def media_content_type(self) -> str | None:
|
||||||
|
"""Content type of current playing media."""
|
||||||
|
if not self._device or not self._device.media_class:
|
||||||
|
return None
|
||||||
|
return MEDIA_TYPE_MAP.get(self._device.media_class)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_duration(self) -> int | None:
|
def media_duration(self) -> int | None:
|
||||||
@ -608,11 +634,80 @@ class DlnaDmrEntity(MediaPlayerEntity):
|
|||||||
return self._device.media_position_updated_at
|
return self._device.media_position_updated_at
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def media_artist(self) -> str | None:
|
||||||
"""Report the UDN (Unique Device Name) as this entity's unique ID."""
|
"""Artist of current playing media, music track only."""
|
||||||
return self.udn
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_artist
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def usn(self) -> str:
|
def media_album_name(self) -> str | None:
|
||||||
"""Get the USN based on the UDN (Unique Device Name) and device type."""
|
"""Album name of current playing media, music track only."""
|
||||||
return f"{self.udn}::{self.device_type}"
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_album_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_album_artist(self) -> str | None:
|
||||||
|
"""Album artist of current playing media, music track only."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_album_artist
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_track(self) -> int | None:
|
||||||
|
"""Track number of current playing media, music track only."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_track_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_series_title(self) -> str | None:
|
||||||
|
"""Title of series of current playing media, TV show only."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_series_title
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_season(self) -> str | None:
|
||||||
|
"""Season number, starting at 1, of current playing media, TV show only."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
# Some DMRs, like Kodi, leave this as 0 and encode the season & episode
|
||||||
|
# in the episode_number metadata, as {season:d}{episode:02d}
|
||||||
|
if (
|
||||||
|
not self._device.media_season_number
|
||||||
|
or self._device.media_season_number == "0"
|
||||||
|
) and self._device.media_episode_number:
|
||||||
|
try:
|
||||||
|
episode = int(self._device.media_episode_number, 10)
|
||||||
|
if episode > 100:
|
||||||
|
return str(episode // 100)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return self._device.media_season_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_episode(self) -> str | None:
|
||||||
|
"""Episode number of current playing media, TV show only."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
# Complement to media_season math above
|
||||||
|
if (
|
||||||
|
not self._device.media_season_number
|
||||||
|
or self._device.media_season_number == "0"
|
||||||
|
) and self._device.media_episode_number:
|
||||||
|
try:
|
||||||
|
episode = int(self._device.media_episode_number, 10)
|
||||||
|
if episode > 100:
|
||||||
|
return str(episode % 100)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return self._device.media_episode_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def media_channel(self) -> str | None:
|
||||||
|
"""Channel name currently playing."""
|
||||||
|
if not self._device:
|
||||||
|
return None
|
||||||
|
return self._device.media_channel_name
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "ssdp",
|
"domain": "ssdp",
|
||||||
"name": "Simple Service Discovery Protocol (SSDP)",
|
"name": "Simple Service Discovery Protocol (SSDP)",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/ssdp",
|
"documentation": "https://www.home-assistant.io/integrations/ssdp",
|
||||||
"requirements": ["async-upnp-client==0.22.3"],
|
"requirements": ["async-upnp-client==0.22.4"],
|
||||||
"dependencies": ["network"],
|
"dependencies": ["network"],
|
||||||
"after_dependencies": ["zeroconf"],
|
"after_dependencies": ["zeroconf"],
|
||||||
"codeowners": [],
|
"codeowners": [],
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "UPnP/IGD",
|
"name": "UPnP/IGD",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/upnp",
|
"documentation": "https://www.home-assistant.io/integrations/upnp",
|
||||||
"requirements": ["async-upnp-client==0.22.3"],
|
"requirements": ["async-upnp-client==0.22.4"],
|
||||||
"dependencies": ["network", "ssdp"],
|
"dependencies": ["network", "ssdp"],
|
||||||
"codeowners": ["@StevenLooman","@ehendrix23"],
|
"codeowners": ["@StevenLooman","@ehendrix23"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"domain": "yeelight",
|
"domain": "yeelight",
|
||||||
"name": "Yeelight",
|
"name": "Yeelight",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/yeelight",
|
"documentation": "https://www.home-assistant.io/integrations/yeelight",
|
||||||
"requirements": ["yeelight==0.7.5", "async-upnp-client==0.22.3"],
|
"requirements": ["yeelight==0.7.5", "async-upnp-client==0.22.4"],
|
||||||
"codeowners": ["@rytilahti", "@zewelor", "@shenxn", "@starkillerOG"],
|
"codeowners": ["@rytilahti", "@zewelor", "@shenxn", "@starkillerOG"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["network"],
|
"dependencies": ["network"],
|
||||||
|
@ -4,7 +4,7 @@ aiodiscover==1.4.2
|
|||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
astral==2.2
|
astral==2.2
|
||||||
async-upnp-client==0.22.3
|
async-upnp-client==0.22.4
|
||||||
async_timeout==3.0.1
|
async_timeout==3.0.1
|
||||||
attrs==21.2.0
|
attrs==21.2.0
|
||||||
awesomeversion==21.8.1
|
awesomeversion==21.8.1
|
||||||
|
@ -330,7 +330,7 @@ asterisk_mbox==0.5.0
|
|||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
# homeassistant.components.upnp
|
# homeassistant.components.upnp
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
async-upnp-client==0.22.3
|
async-upnp-client==0.22.4
|
||||||
|
|
||||||
# homeassistant.components.supla
|
# homeassistant.components.supla
|
||||||
asyncpysupla==0.0.5
|
asyncpysupla==0.0.5
|
||||||
|
@ -224,7 +224,7 @@ arcam-fmj==0.7.0
|
|||||||
# homeassistant.components.ssdp
|
# homeassistant.components.ssdp
|
||||||
# homeassistant.components.upnp
|
# homeassistant.components.upnp
|
||||||
# homeassistant.components.yeelight
|
# homeassistant.components.yeelight
|
||||||
async-upnp-client==0.22.3
|
async-upnp-client==0.22.4
|
||||||
|
|
||||||
# homeassistant.components.aurora
|
# homeassistant.components.aurora
|
||||||
auroranoaa==0.0.2
|
auroranoaa==0.0.2
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections.abc import AsyncIterable
|
from collections.abc import AsyncIterable, Mapping
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import ANY, DEFAULT, Mock, patch
|
from unittest.mock import ANY, DEFAULT, Mock, patch
|
||||||
|
|
||||||
from async_upnp_client.exceptions import UpnpConnectionError, UpnpError
|
from async_upnp_client.exceptions import UpnpConnectionError, UpnpError
|
||||||
@ -73,6 +74,8 @@ async def mock_entity_id(
|
|||||||
"""
|
"""
|
||||||
entity_id = await setup_mock_component(hass, config_entry_mock)
|
entity_id = await setup_mock_component(hass, config_entry_mock)
|
||||||
|
|
||||||
|
assert dmr_device_mock.async_subscribe_services.await_count == 1
|
||||||
|
|
||||||
yield entity_id
|
yield entity_id
|
||||||
|
|
||||||
# Unload config entry to clean up
|
# Unload config entry to clean up
|
||||||
@ -97,6 +100,8 @@ async def mock_disconnected_entity_id(
|
|||||||
|
|
||||||
entity_id = await setup_mock_component(hass, config_entry_mock)
|
entity_id = await setup_mock_component(hass, config_entry_mock)
|
||||||
|
|
||||||
|
assert dmr_device_mock.async_subscribe_services.await_count == 0
|
||||||
|
|
||||||
yield entity_id
|
yield entity_id
|
||||||
|
|
||||||
# Unload config entry to clean up
|
# Unload config entry to clean up
|
||||||
@ -239,7 +244,6 @@ async def test_setup_entry_with_options(
|
|||||||
|
|
||||||
async def test_event_subscribe_failure(
|
async def test_event_subscribe_failure(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain_data_mock: Mock,
|
|
||||||
config_entry_mock: MockConfigEntry,
|
config_entry_mock: MockConfigEntry,
|
||||||
dmr_device_mock: Mock,
|
dmr_device_mock: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -310,11 +314,15 @@ async def test_available_device(
|
|||||||
await async_update_entity(hass, mock_entity_id)
|
await async_update_entity(hass, mock_entity_id)
|
||||||
|
|
||||||
# Check attributes come directly from the device
|
# Check attributes come directly from the device
|
||||||
entity_state = hass.states.get(mock_entity_id)
|
async def get_attrs() -> Mapping[str, Any]:
|
||||||
assert entity_state is not None
|
await async_update_entity(hass, mock_entity_id)
|
||||||
attrs = entity_state.attributes
|
entity_state = hass.states.get(mock_entity_id)
|
||||||
assert attrs is not None
|
assert entity_state is not None
|
||||||
|
attrs = entity_state.attributes
|
||||||
|
assert attrs is not None
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
attrs = await get_attrs()
|
||||||
assert attrs[mp_const.ATTR_MEDIA_VOLUME_LEVEL] is dmr_device_mock.volume_level
|
assert attrs[mp_const.ATTR_MEDIA_VOLUME_LEVEL] is dmr_device_mock.volume_level
|
||||||
assert attrs[mp_const.ATTR_MEDIA_VOLUME_MUTED] is dmr_device_mock.is_volume_muted
|
assert attrs[mp_const.ATTR_MEDIA_VOLUME_MUTED] is dmr_device_mock.is_volume_muted
|
||||||
assert attrs[mp_const.ATTR_MEDIA_DURATION] is dmr_device_mock.media_duration
|
assert attrs[mp_const.ATTR_MEDIA_DURATION] is dmr_device_mock.media_duration
|
||||||
@ -323,9 +331,43 @@ async def test_available_device(
|
|||||||
attrs[mp_const.ATTR_MEDIA_POSITION_UPDATED_AT]
|
attrs[mp_const.ATTR_MEDIA_POSITION_UPDATED_AT]
|
||||||
is dmr_device_mock.media_position_updated_at
|
is dmr_device_mock.media_position_updated_at
|
||||||
)
|
)
|
||||||
assert attrs[mp_const.ATTR_MEDIA_TITLE] is dmr_device_mock.media_title
|
assert attrs[mp_const.ATTR_MEDIA_CONTENT_ID] is dmr_device_mock.current_track_uri
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_ARTIST] is dmr_device_mock.media_artist
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_ALBUM_NAME] is dmr_device_mock.media_album_name
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_ALBUM_ARTIST] is dmr_device_mock.media_album_artist
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_TRACK] is dmr_device_mock.media_track_number
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_SERIES_TITLE] is dmr_device_mock.media_series_title
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_SEASON] is dmr_device_mock.media_season_number
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_EPISODE] is dmr_device_mock.media_episode_number
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_CHANNEL] is dmr_device_mock.media_channel_name
|
||||||
# Entity picture is cached, won't correspond to remote image
|
# Entity picture is cached, won't correspond to remote image
|
||||||
assert isinstance(attrs[ha_const.ATTR_ENTITY_PICTURE], str)
|
assert isinstance(attrs[ha_const.ATTR_ENTITY_PICTURE], str)
|
||||||
|
# media_title depends on what is available
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_TITLE] is dmr_device_mock.media_program_title
|
||||||
|
dmr_device_mock.media_program_title = None
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_TITLE] is dmr_device_mock.media_title
|
||||||
|
# media_content_type is mapped from UPnP class to MediaPlayer type
|
||||||
|
dmr_device_mock.media_class = "object.item.audioItem.musicTrack"
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_CONTENT_TYPE] == mp_const.MEDIA_TYPE_MUSIC
|
||||||
|
dmr_device_mock.media_class = "object.item.videoItem.movie"
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_CONTENT_TYPE] == mp_const.MEDIA_TYPE_MOVIE
|
||||||
|
dmr_device_mock.media_class = "object.item.videoItem.videoBroadcast"
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_CONTENT_TYPE] == mp_const.MEDIA_TYPE_TVSHOW
|
||||||
|
# media_season & media_episode have a special case
|
||||||
|
dmr_device_mock.media_season_number = "0"
|
||||||
|
dmr_device_mock.media_episode_number = "123"
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_SEASON] == "1"
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_EPISODE] == "23"
|
||||||
|
dmr_device_mock.media_season_number = "0"
|
||||||
|
dmr_device_mock.media_episode_number = "S1E23" # Unexpected and not parsed
|
||||||
|
attrs = await get_attrs()
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_SEASON] == "0"
|
||||||
|
assert attrs[mp_const.ATTR_MEDIA_EPISODE] == "S1E23"
|
||||||
|
|
||||||
# Check supported feature flags, one at a time.
|
# Check supported feature flags, one at a time.
|
||||||
# tuple(async_upnp_client feature check property, HA feature flag)
|
# tuple(async_upnp_client feature check property, HA feature flag)
|
||||||
@ -688,7 +730,6 @@ async def test_multiple_ssdp_alive(
|
|||||||
domain_data_mock: Mock,
|
domain_data_mock: Mock,
|
||||||
ssdp_scanner_mock: Mock,
|
ssdp_scanner_mock: Mock,
|
||||||
mock_disconnected_entity_id: str,
|
mock_disconnected_entity_id: str,
|
||||||
dmr_device_mock: Mock,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test multiple SSDP alive notifications is ok, only connects to device once."""
|
"""Test multiple SSDP alive notifications is ok, only connects to device once."""
|
||||||
domain_data_mock.upnp_factory.async_create_device.reset_mock()
|
domain_data_mock.upnp_factory.async_create_device.reset_mock()
|
||||||
@ -1028,7 +1069,6 @@ async def test_ssdp_bootid(
|
|||||||
|
|
||||||
async def test_become_unavailable(
|
async def test_become_unavailable(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain_data_mock: Mock,
|
|
||||||
mock_entity_id: str,
|
mock_entity_id: str,
|
||||||
dmr_device_mock: Mock,
|
dmr_device_mock: Mock,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1226,7 +1266,6 @@ async def test_config_update_connect_failure(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
domain_data_mock: Mock,
|
domain_data_mock: Mock,
|
||||||
config_entry_mock: MockConfigEntry,
|
config_entry_mock: MockConfigEntry,
|
||||||
dmr_device_mock: Mock,
|
|
||||||
mock_entity_id: str,
|
mock_entity_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test DlnaDmrEntity gracefully handles connect failure after config change."""
|
"""Test DlnaDmrEntity gracefully handles connect failure after config change."""
|
||||||
|
Loading…
x
Reference in New Issue
Block a user