1
mirror of https://github.com/home-assistant/core synced 2024-08-28 03:36:46 +02:00

Add media player platform to Tessie (#106214)

* Add media platform

* Add more props

* Fix platform filename

* Working

* Add a test

* Update test and fixture

* Refactor media player properties to handle null values

* Add comments

* add more assertions

* Fix test docstring

* Use walrus instead.

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Test  when media player is idle

* Fix tests

* Remove None type from volume_level

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Return media position only when a media duration is > 0

* Remove impossible None type

* Add snapshot and freezer

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Brett Adams 2023-12-22 21:07:47 +10:00 committed by GitHub
parent 2c2e6171e2
commit b4f8fe8d4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 225 additions and 8 deletions

View File

@ -21,6 +21,7 @@ PLATFORMS = [
Platform.COVER,
Platform.DEVICE_TRACKER,
Platform.LOCK,
Platform.MEDIA_PLAYER,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,

View File

@ -0,0 +1,109 @@
"""Media Player platform for Tessie integration."""
from __future__ import annotations
from homeassistant.components.media_player import (
MediaPlayerDeviceClass,
MediaPlayerEntity,
MediaPlayerState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import TessieDataUpdateCoordinator
from .entity import TessieEntity
STATES = {
"Playing": MediaPlayerState.PLAYING,
"Paused": MediaPlayerState.PAUSED,
"Stopped": MediaPlayerState.IDLE,
}
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the Tessie Media platform from a config entry."""
coordinators = hass.data[DOMAIN][entry.entry_id]
async_add_entities(TessieMediaEntity(coordinator) for coordinator in coordinators)
class TessieMediaEntity(TessieEntity, MediaPlayerEntity):
"""Vehicle Location Media Class."""
_attr_name = None
_attr_device_class = MediaPlayerDeviceClass.SPEAKER
def __init__(
self,
coordinator: TessieDataUpdateCoordinator,
) -> None:
"""Initialize the media player entity."""
super().__init__(coordinator, "media")
@property
def state(self) -> MediaPlayerState:
"""State of the player."""
return STATES.get(
self.get("vehicle_state_media_info_media_playback_status"),
MediaPlayerState.OFF,
)
@property
def volume_level(self) -> float:
"""Volume level of the media player (0..1)."""
return self.get("vehicle_state_media_info_audio_volume", 0) / self.get(
"vehicle_state_media_info_audio_volume_max", 10.333333
)
@property
def media_duration(self) -> int | None:
"""Duration of current playing media in seconds."""
if duration := self.get("vehicle_state_media_info_now_playing_duration"):
return duration / 1000
return None
@property
def media_position(self) -> int | None:
"""Position of current playing media in seconds."""
# Return media position only when a media duration is > 0
if self.get("vehicle_state_media_info_now_playing_duration"):
return self.get("vehicle_state_media_info_now_playing_elapsed") / 1000
return None
@property
def media_title(self) -> str | None:
"""Title of current playing media."""
if title := self.get("vehicle_state_media_info_now_playing_title"):
return title
return None
@property
def media_artist(self) -> str | None:
"""Artist of current playing media, music track only."""
if artist := self.get("vehicle_state_media_info_now_playing_artist"):
return artist
return None
@property
def media_album_name(self) -> str | None:
"""Album name of current playing media, music track only."""
if album := self.get("vehicle_state_media_info_now_playing_album"):
return album
return None
@property
def media_playlist(self) -> str | None:
"""Title of Playlist currently playing."""
if playlist := self.get("vehicle_state_media_info_now_playing_station"):
return playlist
return None
@property
def source(self) -> str | None:
"""Name of the current input source."""
if source := self.get("vehicle_state_media_info_now_playing_source"):
return source
return None

View File

@ -204,14 +204,14 @@
"audio_volume": 2.3333,
"audio_volume_increment": 0.333333,
"audio_volume_max": 10.333333,
"media_playback_status": "Stopped",
"now_playing_album": "",
"now_playing_artist": "",
"now_playing_duration": 0,
"now_playing_elapsed": 0,
"media_playback_status": "Playing",
"now_playing_album": "Album",
"now_playing_artist": "Artist",
"now_playing_duration": 60000,
"now_playing_elapsed": 30000,
"now_playing_source": "Spotify",
"now_playing_station": "",
"now_playing_title": ""
"now_playing_station": "Playlist",
"now_playing_title": "Song"
},
"media_state": {
"remote_control_enabled": false

View File

@ -222,7 +222,7 @@
"now_playing_artist": "",
"now_playing_duration": 0,
"now_playing_elapsed": 0,
"now_playing_source": "Spotify",
"now_playing_source": "",
"now_playing_station": "",
"now_playing_title": ""
},

View File

@ -0,0 +1,61 @@
# serializer version: 1
# name: test_media_player_idle
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test',
'supported_features': <MediaPlayerEntityFeature: 0>,
'volume_level': 0.22580323309042688,
}),
'context': <ANY>,
'entity_id': 'media_player.test',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_media_player_idle.1
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test',
'supported_features': <MediaPlayerEntityFeature: 0>,
'volume_level': 0.22580323309042688,
}),
'context': <ANY>,
'entity_id': 'media_player.test',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_media_player_playing
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test',
'supported_features': <MediaPlayerEntityFeature: 0>,
'volume_level': 0.22580323309042688,
}),
'context': <ANY>,
'entity_id': 'media_player.test',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_sensors
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Test',
'supported_features': <MediaPlayerEntityFeature: 0>,
'volume_level': 0.22580323309042688,
}),
'context': <ANY>,
'entity_id': 'media_player.test',
'last_changed': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---

View File

@ -0,0 +1,46 @@
"""Test the Tessie media player platform."""
from datetime import timedelta
from freezegun.api import FrozenDateTimeFactory
from syrupy import SnapshotAssertion
from homeassistant.components.tessie.coordinator import TESSIE_SYNC_INTERVAL
from homeassistant.core import HomeAssistant
from .common import (
TEST_STATE_OF_ALL_VEHICLES,
TEST_VEHICLE_STATE_ONLINE,
setup_platform,
)
from tests.common import async_fire_time_changed
WAIT = timedelta(seconds=TESSIE_SYNC_INTERVAL)
MEDIA_INFO_1 = TEST_STATE_OF_ALL_VEHICLES["results"][0]["last_state"]["vehicle_state"][
"media_info"
]
MEDIA_INFO_2 = TEST_VEHICLE_STATE_ONLINE["vehicle_state"]["media_info"]
async def test_media_player_idle(
hass: HomeAssistant, freezer: FrozenDateTimeFactory, snapshot: SnapshotAssertion
) -> None:
"""Tests that the media player entity is correct when idle."""
assert len(hass.states.async_all("media_player")) == 0
await setup_platform(hass)
assert len(hass.states.async_all("media_player")) == 1
state = hass.states.get("media_player.test")
assert state == snapshot
# Trigger coordinator refresh since it has a different fixture.
freezer.tick(WAIT)
async_fire_time_changed(hass)
state = hass.states.get("media_player.test")
assert state == snapshot