ha_cloud_music/custom_components/ha_cloud_music/media_player.py

237 lines
8.4 KiB
Python

import logging, time, datetime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.event import track_time_interval
from homeassistant.components.media_player import MediaPlayerEntity, MediaPlayerDeviceClass
from homeassistant.components.media_player.const import (
SUPPORT_BROWSE_MEDIA,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
SUPPORT_VOLUME_STEP,
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE,
SUPPORT_SELECT_SOURCE,
SUPPORT_PLAY_MEDIA,
SUPPORT_PLAY,
SUPPORT_PAUSE,
SUPPORT_SEEK,
SUPPORT_CLEAR_PLAYLIST,
SUPPORT_SHUFFLE_SET,
SUPPORT_REPEAT_SET,
SUPPORT_NEXT_TRACK,
SUPPORT_PREVIOUS_TRACK,
MEDIA_TYPE_ALBUM,
MEDIA_TYPE_ARTIST,
MEDIA_TYPE_CHANNEL,
MEDIA_TYPE_EPISODE,
MEDIA_TYPE_MOVIE,
MEDIA_TYPE_PLAYLIST,
MEDIA_TYPE_SEASON,
MEDIA_TYPE_TRACK,
MEDIA_TYPE_TVSHOW,
)
from homeassistant.const import (
CONF_TOKEN,
CONF_URL,
CONF_NAME,
STATE_OFF,
STATE_ON,
STATE_PLAYING,
STATE_PAUSED,
STATE_IDLE,
STATE_UNAVAILABLE
)
from .manifest import manifest
DOMAIN = manifest.domain
_LOGGER = logging.getLogger(__name__)
SUPPORT_FEATURES = SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
SUPPORT_PLAY_MEDIA | SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_BROWSE_MEDIA | SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_REPEAT_SET
# 定时器时间
TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=2)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
media_player = CloudMusicMediaPlayer(hass, entry)
await hass.async_add_executor_job(track_time_interval, hass, media_player.interval, TIME_BETWEEN_UPDATES)
async_add_entities([ media_player ], True)
class CloudMusicMediaPlayer(MediaPlayerEntity):
def __init__(self, hass, entry):
self.hass = hass
self._attributes = {
'platform': 'cloud_music'
}
# fixed attribute
self._attr_media_image_remotely_accessible = True
self._attr_device_class = MediaPlayerDeviceClass.TV.value
self._attr_supported_features = SUPPORT_FEATURES
# default attribute
self.source_media_player = entry.options.get('media_player')
self._attr_name = manifest.name
self._attr_unique_id = manifest.documentation
self._attr_state = STATE_ON
self._attr_volume_level = 1
self._attr_repeat = 'all'
self._attr_shuffle = False
self.cloud_music = hass.data['cloud_music']
self.before_state = None
self.current_state = None
def interval(self, now):
# 暂停时不更新
if self._attr_state == STATE_PAUSED:
return
media_player = self.media_player
if media_player is not None:
attrs = media_player.attributes
self._attr_media_position = attrs.get('media_position', 0)
self._attr_media_duration = attrs.get('media_duration', 0)
self._attr_media_position_updated_at = datetime.datetime.now()
# 判断是否下一曲
if self.before_state is not None:
# 判断音乐总时长
if self.before_state['media_duration'] > 0 and self.before_state['media_duration'] - self.before_state['media_duration'] <= 5:
# 判断源音乐播放器状态
if self.before_state['state'] == STATE_PLAYING and self.current_state == STATE_IDLE:
self.hass.async_create_task(self.async_media_next_track())
self.before_state = None
return
# 源播放器空闲 & 当前正在播放
if self.before_state['media_duration'] == 0 and self.before_state['media_position'] == 0 and self.current_state == STATE_IDLE \
and self._attr_media_duration == 0 and self._attr_media_position == 0 and self._attr_state == STATE_PLAYING:
self.hass.async_create_task(self.async_media_next_track())
self.before_state = None
return
self.before_state = {
'media_position': self._attr_media_position,
'media_duration': self._attr_media_duration,
'state': self.current_state
}
self.current_state = media_player.state
if hasattr(self, 'playlist'):
music_info = self.playlist[self.playindex]
self._attr_app_name = music_info.singer
self._attr_media_image_url = music_info.thumbnail
self._attr_media_album_name = music_info.album
self._attr_media_title = music_info.song
self._attr_media_artist = music_info.singer
@property
def media_player(self):
if self.entity_id is not None and self.source_media_player is not None:
return self.hass.states.get(self.source_media_player)
@property
def device_info(self):
return {
'identifiers': {
(DOMAIN, manifest.documentation)
},
'name': self.name,
'manufacturer': 'shaonianzhentan',
'model': 'CloudMusic',
'sw_version': manifest.version
}
@property
def extra_state_attributes(self):
return self._attributes
async def async_browse_media(self, media_content_type=None, media_content_id=None):
return await self.cloud_music.async_browse_media(self, media_content_type, media_content_id)
async def async_volume_up(self):
await self.async_call('volume_up')
async def async_volume_down(self):
await self.async_call('volume_down')
async def async_mute_volume(self, mute):
await self.async_call('mute_volume', { 'mute': mute })
async def async_set_volume_level(self, volume: float):
self._attr_volume_level = volume
await self.async_call('volume_set', { 'volume_level': volume })
async def async_play_media(self, media_type, media_id, **kwargs):
self._attr_state = STATE_PAUSED
media_content_id = media_id
result = await self.cloud_music.async_play_media(self, self.cloud_music, media_id)
if result is not None:
if result == 'index':
# 播放当前列表指定项
media_content_id = self.playlist[self.playindex].url
elif result.startswith('http'):
# HTTP播放链接
media_content_id = result
else:
# 添加播放列表到播放器
media_content_id = self.playlist[self.playindex].url
self._attr_media_content_id = media_content_id
await self.async_call('play_media', {
'media_content_id': media_content_id,
'media_content_type': 'music'
})
self._attr_state = STATE_PLAYING
self.before_state = None
async def async_media_play(self):
self._attr_state = STATE_PLAYING
await self.async_call('media_play')
async def async_media_pause(self):
self._attr_state = STATE_PAUSED
await self.async_call('media_pause')
async def async_set_repeat(self, repeat):
self._attr_repeat = repeat
async def async_set_shuffle(self, shuffle):
self._attr_shuffle = shuffle
async def async_media_next_track(self):
self._attr_state = STATE_PAUSED
await self.cloud_music.async_media_next_track(self, self._attr_shuffle)
async def async_media_previous_track(self):
self._attr_state = STATE_PAUSED
await self.cloud_music.async_media_previous_track(self, self._attr_shuffle)
async def async_media_seek(self, position):
await self.async_call('media_seek', { 'seek_position': position })
async def async_media_stop(self):
await self.async_call('media_stop')
# 更新属性
async def async_update(self):
pass
# 调用服务
async def async_call(self, service, service_data={}):
media_player = self.media_player
if media_player is not None:
service_data.update({ 'entity_id': media_player.entity_id })
await self.hass.services.async_call('media_player', service, service_data)