找回密码
 立即注册
楼主: qqdwtypm

[经验分享] 小爱音箱dlna+网易云音乐插件实现自动替换vip歌曲和语音播放

  [复制链接]

0

主题

1

回帖

19

积分

新手上路

积分
19
金钱
18
HASS币
0
发表于 5 天前 | 显示全部楼层
这里更新下现在最新版本我如何解决这个问题,以及点击进度进度条后进度更新不及时问题的修改,还有需要重新操作两遍暂停再播放才能播放的问题。

之前是进度从dlna设备获取,导致播放进度更新不及时,基本上要10秒钟才更新一次(我的设备是这样的),所以导致播放进度不好把控,所以我改成了只要开始播放,就自动每秒钟+1秒进度。然后如果播放到最后1秒的时候,就切歌。然后计时器改为每秒触发一次。

另外还解决了不知道什么原因重新操作两遍暂停再播放才能播放的问题。没找到原因,直接点击播放的时候自动暂停播放暂停播放就行了

我觉得这个逻辑可能会更好一些,因为这种逻辑兼容性应该更强一些

import logging, time, datetime
import asyncio


from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.components.media_player import MediaPlayerEntity, MediaPlayerDeviceClass, MediaPlayerEntityFeature
from homeassistant.components.media_player.const import (
    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 | \
    MediaPlayerEntityFeature.BROWSE_MEDIA | SUPPORT_SEEK | SUPPORT_CLEAR_PLAYLIST | SUPPORT_SHUFFLE_SET | SUPPORT_REPEAT_SET

# 定时器时间
TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=1)
UNSUB_INTERVAL = None

async def async_setup_entry(
    hass: HomeAssistant,
    entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:

    entities = []
    for source_media_player in entry.options.get('media_player', []):
      entities.append(CloudMusicMediaPlayer(hass, source_media_player))

    def media_player_interval(now):
      for mp in entities:
        mp.interval(now)

    # 开启定时器
    global UNSUB_INTERVAL
    if UNSUB_INTERVAL is not None:
      UNSUB_INTERVAL()
    UNSUB_INTERVAL = async_track_time_interval(hass, media_player_interval, TIME_BETWEEN_UPDATES)

    async_add_entities(entities, True)

class CloudMusicMediaPlayer(MediaPlayerEntity):

    def __init__(self, hass, source_media_player):
        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 = source_media_player
        self._attr_name = f'{manifest.name} {source_media_player.split(".")[1]}'
        self._attr_unique_id = f'{manifest.domain}{source_media_player}'
        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
        self._last_seek_time = None

    def interval(self, now):
        _LOGGER.warning("播放状态:%s", self._attr_state)
        
        # 暂停时不更新
        if self._attr_state != STATE_PLAYING:
            return
    
        # 获取当前时间
        new_updated_at = datetime.datetime.now()
        
        # 如果是第一次调用或者需要重置计时
        if not hasattr(self, '_last_position_update') or self._last_position_update is None:
            self._last_position_update = new_updated_at
            self._attr_media_position = 0
        else:
            self._attr_media_position += 1  # 增加整数秒
            _LOGGER.warning("增加了一秒 %s", self._attr_media_position)
            self._last_position_update = new_updated_at
            self._attr_media_position_updated_at = datetime.datetime.now(datetime.timezone.utc)
        self.hass.loop.call_soon_threadsafe(self.async_write_ha_state)
        # 更新其他属性(从DLNA设备获取)
        media_player = self.media_player
        if media_player is not None:
            attrs = media_player.attributes
            self._attr_media_duration = attrs.get('media_duration', 0)

            # 判断是否下一曲
            if self.before_state is not None:
                # 判断音乐总时长
                if self.before_state['media_duration'] > 0:
                    delta = self._attr_media_duration - self._attr_media_position
                    _LOGGER.warning("差值:%s", delta)
                    if delta <= 1 and self._attr_media_duration > 1 and delta >= 0:
                        _LOGGER.warning("小于1")
                        self._attr_state = STATE_PAUSED
                        self.before_state = None
                        self.hass.loop.call_soon_threadsafe(
                            lambda: asyncio.create_task(self.async_media_next_track())
                        )
                        return
    
                # if (self.before_state['media_duration'] == 0 and 
                #     self.before_state['media_position'] == 0 and 
                #     self.current_state == STATE_PLAYING and
                #     self._attr_media_duration == 0 and 
                #     self._attr_media_position == 0 and 
                #     self._attr_state == STATE_PLAYING):
                #     time.sleep(10)
                #     if (self.before_state['media_duration'] == 0 and 
                #         self.before_state['media_position'] == 0 and 
                #         self.current_state == STATE_PLAYING and
                #         self._attr_media_duration == 0 and 
                #         self._attr_media_position == 0 and 
                #         self._attr_state == STATE_PLAYING):
                #         self.hass.loop.call_soon_threadsafe(
                #             lambda: asyncio.create_task(self.async_media_next_track())
                #         )
                #         self.before_state = None
                #         return
    
        # 更新状态记录
        self.before_state = {
            'media_position': int(self._attr_media_position),
            'media_duration': int(self._attr_media_duration),
            'state': self.current_state
        }
        self.current_state = media_player.state if media_player is not None else self._attr_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
        # self.hass.loop.call_soon_threadsafe(lambda: asyncio.create_task(self.async_write_ha_state()))

    @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):
        self._attr_is_volume_muted = mute
        await self.async_call('mute_volume', { 'is_volume_muted': 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):
        # 强制暂停一次
        await self.async_call('media_pause')
        await asyncio.sleep(0.1)
        
         # 强制暂停一次
        await self.async_call('media_play')
        await asyncio.sleep(0.1)
         # 强制暂停一次
        await self.async_call('media_pause')
        await asyncio.sleep(0.1)
        
        # 然后再播放
        await self.async_call('media_play')
        self._attr_state = STATE_PLAYING

    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)
        self._attr_media_position = 0
        self._attr_media_position_updated_at = datetime.datetime.now()

    async def async_media_previous_track(self):
        self._attr_state = STATE_PAUSED
        await self.cloud_music.async_media_previous_track(self, self._attr_shuffle)
        self._attr_media_position = 0
        self._attr_media_position_updated_at = datetime.datetime.now()

    async def async_media_seek(self, position):
        await self.async_call('media_seek', { 'seek_position': position })
        # 更新进度状态
        self._attr_media_position = position
        self._attr_media_position_updated_at = datetime.datetime.now()
        self._last_seek_time = datetime.datetime.now()
        # 通知前端更新 UI
        self.async_write_ha_state()
        # 立即执行一次 interval,防止延迟或卡住不切歌
        self._attr_state = STATE_PLAYING
        self.interval(datetime.datetime.now())
        

    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)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian

GMT+8, 2025-5-1 09:22 , Processed in 0.176185 second(s), 21 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表