mirror of
https://github.com/home-assistant/core.git
synced 2025-04-28 03:07:50 +00:00
360 lines
12 KiB
Python
360 lines
12 KiB
Python
"""Support for media players through the SmartThings cloud API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from pysmartthings import Attribute, Capability, Category, Command, SmartThings
|
|
|
|
from homeassistant.components.media_player import (
|
|
MediaPlayerDeviceClass,
|
|
MediaPlayerEntity,
|
|
MediaPlayerEntityFeature,
|
|
MediaPlayerState,
|
|
RepeatMode,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from . import FullDevice, SmartThingsConfigEntry
|
|
from .const import MAIN
|
|
from .entity import SmartThingsEntity
|
|
|
|
MEDIA_PLAYER_CAPABILITIES = (
|
|
Capability.AUDIO_MUTE,
|
|
Capability.AUDIO_VOLUME,
|
|
)
|
|
|
|
CONTROLLABLE_SOURCES = ["bluetooth", "wifi"]
|
|
|
|
DEVICE_CLASS_MAP: dict[Category | str, MediaPlayerDeviceClass] = {
|
|
Category.NETWORK_AUDIO: MediaPlayerDeviceClass.SPEAKER,
|
|
Category.SPEAKER: MediaPlayerDeviceClass.SPEAKER,
|
|
Category.TELEVISION: MediaPlayerDeviceClass.TV,
|
|
Category.RECEIVER: MediaPlayerDeviceClass.RECEIVER,
|
|
}
|
|
|
|
VALUE_TO_STATE = {
|
|
"buffering": MediaPlayerState.BUFFERING,
|
|
"paused": MediaPlayerState.PAUSED,
|
|
"playing": MediaPlayerState.PLAYING,
|
|
"stopped": MediaPlayerState.IDLE,
|
|
"fast forwarding": MediaPlayerState.BUFFERING,
|
|
"rewinding": MediaPlayerState.BUFFERING,
|
|
}
|
|
|
|
REPEAT_MODE_TO_HA = {
|
|
"all": RepeatMode.ALL,
|
|
"one": RepeatMode.ONE,
|
|
"off": RepeatMode.OFF,
|
|
}
|
|
|
|
HA_REPEAT_MODE_TO_SMARTTHINGS = {v: k for k, v in REPEAT_MODE_TO_HA.items()}
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: SmartThingsConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
"""Add media players for a config entry."""
|
|
entry_data = entry.runtime_data
|
|
|
|
async_add_entities(
|
|
SmartThingsMediaPlayer(entry_data.client, device)
|
|
for device in entry_data.devices.values()
|
|
if all(
|
|
capability in device.status[MAIN]
|
|
for capability in MEDIA_PLAYER_CAPABILITIES
|
|
)
|
|
)
|
|
|
|
|
|
class SmartThingsMediaPlayer(SmartThingsEntity, MediaPlayerEntity):
|
|
"""Define a SmartThings media player."""
|
|
|
|
_attr_name = None
|
|
|
|
def __init__(self, client: SmartThings, device: FullDevice) -> None:
|
|
"""Initialize the media_player class."""
|
|
super().__init__(
|
|
client,
|
|
device,
|
|
{
|
|
Capability.AUDIO_MUTE,
|
|
Capability.AUDIO_TRACK_DATA,
|
|
Capability.AUDIO_VOLUME,
|
|
Capability.MEDIA_INPUT_SOURCE,
|
|
Capability.MEDIA_PLAYBACK,
|
|
Capability.MEDIA_PLAYBACK_REPEAT,
|
|
Capability.MEDIA_PLAYBACK_SHUFFLE,
|
|
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE,
|
|
Capability.SWITCH,
|
|
},
|
|
)
|
|
self._attr_supported_features = self._determine_features()
|
|
self._attr_device_class = DEVICE_CLASS_MAP.get(
|
|
device.device.components[MAIN].user_category
|
|
or device.device.components[MAIN].manufacturer_category,
|
|
)
|
|
|
|
def _determine_features(self) -> MediaPlayerEntityFeature:
|
|
flags = (
|
|
MediaPlayerEntityFeature.VOLUME_SET
|
|
| MediaPlayerEntityFeature.VOLUME_STEP
|
|
| MediaPlayerEntityFeature.VOLUME_MUTE
|
|
)
|
|
if self.supports_capability(Capability.MEDIA_PLAYBACK):
|
|
playback_commands = self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK, Attribute.SUPPORTED_PLAYBACK_COMMANDS
|
|
)
|
|
if "play" in playback_commands:
|
|
flags |= MediaPlayerEntityFeature.PLAY
|
|
if "pause" in playback_commands:
|
|
flags |= MediaPlayerEntityFeature.PAUSE
|
|
if "stop" in playback_commands:
|
|
flags |= MediaPlayerEntityFeature.STOP
|
|
if "rewind" in playback_commands:
|
|
flags |= MediaPlayerEntityFeature.PREVIOUS_TRACK
|
|
if "fastForward" in playback_commands:
|
|
flags |= MediaPlayerEntityFeature.NEXT_TRACK
|
|
if self.supports_capability(Capability.SWITCH):
|
|
flags |= (
|
|
MediaPlayerEntityFeature.TURN_ON | MediaPlayerEntityFeature.TURN_OFF
|
|
)
|
|
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
|
flags |= MediaPlayerEntityFeature.SELECT_SOURCE
|
|
if self.supports_capability(Capability.MEDIA_PLAYBACK_SHUFFLE):
|
|
flags |= MediaPlayerEntityFeature.SHUFFLE_SET
|
|
if self.supports_capability(Capability.MEDIA_PLAYBACK_REPEAT):
|
|
flags |= MediaPlayerEntityFeature.REPEAT_SET
|
|
return flags
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
"""Turn the media player off."""
|
|
await self.execute_device_command(
|
|
Capability.SWITCH,
|
|
Command.OFF,
|
|
)
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
"""Turn the media player on."""
|
|
await self.execute_device_command(
|
|
Capability.SWITCH,
|
|
Command.ON,
|
|
)
|
|
|
|
async def async_mute_volume(self, mute: bool) -> None:
|
|
"""Mute volume."""
|
|
await self.execute_device_command(
|
|
Capability.AUDIO_MUTE,
|
|
Command.SET_MUTE,
|
|
argument="muted" if mute else "unmuted",
|
|
)
|
|
|
|
async def async_set_volume_level(self, volume: float) -> None:
|
|
"""Set volume level."""
|
|
await self.execute_device_command(
|
|
Capability.AUDIO_VOLUME,
|
|
Command.SET_VOLUME,
|
|
argument=int(volume * 100),
|
|
)
|
|
|
|
async def async_volume_up(self) -> None:
|
|
"""Increase volume."""
|
|
await self.execute_device_command(
|
|
Capability.AUDIO_VOLUME,
|
|
Command.VOLUME_UP,
|
|
)
|
|
|
|
async def async_volume_down(self) -> None:
|
|
"""Decrease volume."""
|
|
await self.execute_device_command(
|
|
Capability.AUDIO_VOLUME,
|
|
Command.VOLUME_DOWN,
|
|
)
|
|
|
|
async def async_media_play(self) -> None:
|
|
"""Play media."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK,
|
|
Command.PLAY,
|
|
)
|
|
|
|
async def async_media_pause(self) -> None:
|
|
"""Pause media."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK,
|
|
Command.PAUSE,
|
|
)
|
|
|
|
async def async_media_stop(self) -> None:
|
|
"""Stop media."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK,
|
|
Command.STOP,
|
|
)
|
|
|
|
async def async_media_previous_track(self) -> None:
|
|
"""Previous track."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK,
|
|
Command.REWIND,
|
|
)
|
|
|
|
async def async_media_next_track(self) -> None:
|
|
"""Next track."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK,
|
|
Command.FAST_FORWARD,
|
|
)
|
|
|
|
async def async_select_source(self, source: str) -> None:
|
|
"""Select source."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_INPUT_SOURCE,
|
|
Command.SET_INPUT_SOURCE,
|
|
argument=source,
|
|
)
|
|
|
|
async def async_set_shuffle(self, shuffle: bool) -> None:
|
|
"""Set shuffle mode."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK_SHUFFLE,
|
|
Command.SET_PLAYBACK_SHUFFLE,
|
|
argument="enabled" if shuffle else "disabled",
|
|
)
|
|
|
|
async def async_set_repeat(self, repeat: RepeatMode) -> None:
|
|
"""Set repeat mode."""
|
|
await self.execute_device_command(
|
|
Capability.MEDIA_PLAYBACK_REPEAT,
|
|
Command.SET_PLAYBACK_REPEAT_MODE,
|
|
argument=HA_REPEAT_MODE_TO_SMARTTHINGS[repeat],
|
|
)
|
|
|
|
@property
|
|
def media_title(self) -> str | None:
|
|
"""Title of current playing media."""
|
|
if (
|
|
not self.supports_capability(Capability.AUDIO_TRACK_DATA)
|
|
or (
|
|
track_data := self.get_attribute_value(
|
|
Capability.AUDIO_TRACK_DATA, Attribute.AUDIO_TRACK_DATA
|
|
)
|
|
)
|
|
is None
|
|
):
|
|
return None
|
|
return track_data.get("title", None)
|
|
|
|
@property
|
|
def media_artist(self) -> str | None:
|
|
"""Artist of current playing media."""
|
|
if (
|
|
not self.supports_capability(Capability.AUDIO_TRACK_DATA)
|
|
or (
|
|
track_data := self.get_attribute_value(
|
|
Capability.AUDIO_TRACK_DATA, Attribute.AUDIO_TRACK_DATA
|
|
)
|
|
)
|
|
is None
|
|
):
|
|
return None
|
|
return track_data.get("artist")
|
|
|
|
@property
|
|
def state(self) -> MediaPlayerState | None:
|
|
"""State of the media player."""
|
|
if self.supports_capability(Capability.SWITCH):
|
|
if not self.supports_capability(Capability.MEDIA_PLAYBACK):
|
|
if (
|
|
self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH)
|
|
== "on"
|
|
):
|
|
return MediaPlayerState.ON
|
|
return MediaPlayerState.OFF
|
|
if self.get_attribute_value(Capability.SWITCH, Attribute.SWITCH) == "on":
|
|
if (
|
|
self.source is not None
|
|
and self.source in CONTROLLABLE_SOURCES
|
|
and self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK, Attribute.PLAYBACK_STATUS
|
|
)
|
|
in VALUE_TO_STATE
|
|
):
|
|
return VALUE_TO_STATE[
|
|
self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK, Attribute.PLAYBACK_STATUS
|
|
)
|
|
]
|
|
return MediaPlayerState.ON
|
|
return MediaPlayerState.OFF
|
|
return VALUE_TO_STATE[
|
|
self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK, Attribute.PLAYBACK_STATUS
|
|
)
|
|
]
|
|
|
|
@property
|
|
def is_volume_muted(self) -> bool:
|
|
"""Returns if the volume is muted."""
|
|
return (
|
|
self.get_attribute_value(Capability.AUDIO_MUTE, Attribute.MUTE) == "muted"
|
|
)
|
|
|
|
@property
|
|
def volume_level(self) -> float:
|
|
"""Volume level."""
|
|
return self.get_attribute_value(Capability.AUDIO_VOLUME, Attribute.VOLUME) / 100
|
|
|
|
@property
|
|
def source(self) -> str | None:
|
|
"""Input source."""
|
|
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
|
return self.get_attribute_value(
|
|
Capability.MEDIA_INPUT_SOURCE, Attribute.INPUT_SOURCE
|
|
)
|
|
if self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
|
return self.get_attribute_value(
|
|
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE, Attribute.INPUT_SOURCE
|
|
)
|
|
return None
|
|
|
|
@property
|
|
def source_list(self) -> list[str] | None:
|
|
"""List of input sources."""
|
|
if self.supports_capability(Capability.MEDIA_INPUT_SOURCE):
|
|
return self.get_attribute_value(
|
|
Capability.MEDIA_INPUT_SOURCE, Attribute.SUPPORTED_INPUT_SOURCES
|
|
)
|
|
if self.supports_capability(Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE):
|
|
return self.get_attribute_value(
|
|
Capability.SAMSUNG_VD_AUDIO_INPUT_SOURCE,
|
|
Attribute.SUPPORTED_INPUT_SOURCES,
|
|
)
|
|
return None
|
|
|
|
@property
|
|
def shuffle(self) -> bool | None:
|
|
"""Returns if shuffle mode is set."""
|
|
if self.supports_capability(Capability.MEDIA_PLAYBACK_SHUFFLE):
|
|
return (
|
|
self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK_SHUFFLE, Attribute.PLAYBACK_SHUFFLE
|
|
)
|
|
== "enabled"
|
|
)
|
|
return None
|
|
|
|
@property
|
|
def repeat(self) -> RepeatMode | None:
|
|
"""Returns if repeat mode is set."""
|
|
if self.supports_capability(Capability.MEDIA_PLAYBACK_REPEAT):
|
|
return REPEAT_MODE_TO_HA[
|
|
self.get_attribute_value(
|
|
Capability.MEDIA_PLAYBACK_REPEAT, Attribute.PLAYBACK_REPEAT_MODE
|
|
)
|
|
]
|
|
return None
|