Skip to content

Commit

Permalink
Merge pull request #324 from mang1985/main
Browse files Browse the repository at this point in the history
Provide compatibility support for existing configurations using manually created input entities and other fixes.
I'll test tonight, sorry for the delay. 
I really love to have you onboard!
  • Loading branch information
KoljaWindeler authored Apr 17, 2024
2 parents dff1148 + 476e940 commit 4cd3332
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 33 deletions.
27 changes: 20 additions & 7 deletions custom_components/ytube_music_player/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,21 @@ async def async_create_form(hass, user_input, page=1):
data_schema[vol.Required(CONF_CODE+"TT", default="https://www.google.com/device?user_code="+user_input[CONF_CODE]["user_code"])] = str # name of the component without domain
data_schema[vol.Required(CONF_NAME, default=user_input[CONF_NAME])] = str # name of the component without domain
elif(page == 2):
# Generate a list of excluded entities.
# This method is more reliable because it won't become invalid
# if users modify entity IDs, and it supports multiple instances.
_exclude_entities = []
for _ytm_player in hass.data[DOMAIN].values():
_LOGGER.warning(_ytm_player[DOMAIN_MP].entity_id)
_exclude_entities.append(_ytm_player[DOMAIN_MP].entity_id)

data_schema[vol.Required(CONF_RECEIVERS,default=user_input[CONF_RECEIVERS])] = selector({
"entity": {
"multiple": "true",
"filter": [{"domain": DOMAIN_MP}],
"exclude_entities": [DOMAIN_MP+"."+user_input[CONF_NAME]]
}
})
"exclude_entities": _exclude_entities
}
})
data_schema[vol.Required(CONF_API_LANGUAGE, default=user_input[CONF_API_LANGUAGE])] = selector({
"select": {
"options": languages,
Expand All @@ -214,8 +222,8 @@ async def async_create_form(hass, user_input, page=1):
"select": {
"options": ALL_SHUFFLE_MODES,
"mode": "dropdown"
}
})
}
})
data_schema[vol.Optional(CONF_LIKE_IN_NAME, default=user_input[CONF_LIKE_IN_NAME])] = vol.Coerce(bool) # default like_in_name, TRUE/FALSE
data_schema[vol.Optional(CONF_DEBUG_AS_ERROR, default=user_input[CONF_DEBUG_AS_ERROR])] = vol.Coerce(bool) # debug_as_error, TRUE/FALSE
data_schema[vol.Optional(CONF_LEGACY_RADIO, default=user_input[CONF_LEGACY_RADIO])] = vol.Coerce(bool) # default radio generation typ
Expand All @@ -225,8 +233,13 @@ async def async_create_form(hass, user_input, page=1):
"select": {
"options": ALL_DROPDOWNS,
"multiple": "true"
}
})
}
})
# add for the old inputs.
for _old_conf_input in OLD_INPUTS.values():
if user_input.get(_old_conf_input) is not None:
data_schema[vol.Optional(_old_conf_input, default=user_input[_old_conf_input])] = str

data_schema[vol.Optional(CONF_TRACK_LIMIT, default=user_input[CONF_TRACK_LIMIT])] = vol.Coerce(int)
data_schema[vol.Optional(CONF_MAX_DATARATE, default=user_input[CONF_MAX_DATARATE])] = vol.Coerce(int)
data_schema[vol.Optional(CONF_BRAND_ID, default=user_input[CONF_BRAND_ID])] = str # brand id
Expand Down
52 changes: 48 additions & 4 deletions custom_components/ytube_music_player/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CONF_PASSWORD,
STATE_PLAYING,
STATE_PAUSED,
STATE_ON,
STATE_OFF,
STATE_IDLE,
ATTR_COMMAND,
Expand All @@ -41,8 +42,16 @@
DOMAIN as DOMAIN_MP,
)

# add for old settings
from homeassistant.components.input_boolean import (
SERVICE_TURN_OFF as IB_OFF,
SERVICE_TURN_ON as IB_ON,
DOMAIN as DOMAIN_IB,
)

import homeassistant.components.select as select
import homeassistant.components.switch as switch
import homeassistant.components.input_select as input_select # add for old settings
import homeassistant.components.input_boolean as input_boolean # add for old settings

# Should be equal to the name of your component.
PLATFORMS = {"sensor", "select", "media_player" }
Expand Down Expand Up @@ -100,8 +109,7 @@
SERVICE_CALL_MOVE_TRACK = "move_track_within_queue"
SERVICE_CALL_APPEND_TRACK = "append_track_to_queue"


CONF_RECEIVERS = 'speakers' # list of speakers (media_players)
CONF_RECEIVERS = 'speakers' # list of speakers (media_players)
CONF_HEADER_PATH = 'header_path'
CONF_API_LANGUAGE = 'api_language'
CONF_SHUFFLE = 'shuffle'
Expand All @@ -124,6 +132,25 @@
CONF_PROXY_URL = 'proxy_url'
CONF_PROXY_PATH = 'proxy_path'

# add for old settings
CONF_SELECT_SOURCE = 'select_source'
CONF_SELECT_PLAYLIST = 'select_playlist'
CONF_SELECT_SPEAKERS = 'select_speakers'
CONF_SELECT_PLAYMODE = 'select_playmode'
CONF_SELECT_PLAYCONTINUOUS = 'select_playcontinuous'
OLD_INPUTS = {
"playlists": CONF_SELECT_PLAYLIST,
"speakers": CONF_SELECT_SPEAKERS,
"playmode": CONF_SELECT_PLAYMODE,
"radiomode": CONF_SELECT_SOURCE,
"repeatmode": CONF_SELECT_PLAYCONTINUOUS
}
DEFAULT_SELECT_PLAYCONTINUOUS = ""
DEFAULT_SELECT_SOURCE = ""
DEFAULT_SELECT_PLAYLIST = ""
DEFAULT_SELECT_PLAYMODE = ""
DEFAULT_SELECT_SPEAKERS = ""

DEFAULT_HEADER_FILENAME = 'header_'
DEFAULT_API_LANGUAGE = 'en'
DEFAULT_LIKE_IN_NAME = False
Expand Down Expand Up @@ -317,8 +344,25 @@ def ensure_config(user_input):
out[CONF_MAX_DATARATE] = DEFAULT_MAX_DATARATE

if user_input is not None:
# for the old shuffle_mode setting.
out.update(user_input)

if isinstance(_shuffle_mode := out[CONF_SHUFFLE_MODE], int):
if _shuffle_mode >= 1:
out[CONF_SHUFFLE_MODE] = ALL_SHUFFLE_MODES[_shuffle_mode - 1]
else:
out[CONF_SHUFFLE_MODE] = PLAYMODE_DIRECT
_LOGGER.error(f"shuffle_mode: {_shuffle_mode} is a deprecated value and has been replaced with '{out[CONF_SHUFFLE_MODE]}'.")

# If old input(s) exists,uncheck the new corresponding select(s).
# If the old input is set to a blank space character, then permanently delete this field.
for dropdown in ALL_DROPDOWNS:
if (_old_conf_input := out.get(OLD_INPUTS[dropdown])) is not None:
if _old_conf_input.replace(" ","") == "":
del out[OLD_INPUTS[dropdown]]
else:
if dropdown in out[CONF_INIT_DROPDOWNS]:
out[CONF_INIT_DROPDOWNS].remove(dropdown)
_LOGGER.warning(f"old {dropdown} input_select: {_old_conf_input} exists,uncheck the corresponding new select.")
return out


Expand Down
78 changes: 60 additions & 18 deletions custom_components/ytube_music_player/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class yTubeMusicComponent(MediaPlayerEntity):
def __init__(self, hass, config, name_add):
self.hass = hass
self._attr_unique_id = config.entry_id
self.hass.data[DOMAIN][self._attr_unique_id][DOMAIN_MP] = self
self._debug_log_concat = ""
self._debug_as_error = config.data.get(CONF_DEBUG_AS_ERROR, DEFAULT_DEBUG_AS_ERROR)
self._org_name = config.data.get(CONF_NAME, DOMAIN + name_add)
Expand All @@ -92,11 +93,20 @@ def __init__(self, hass, config, name_add):
# All entities are now automatically generated,will be registered in the async_update_selects method later.
# This should be helpful for multiple accounts.
self._selects = dict() # use a dict to store the dropdown entity_id should be more convenient.
self._selects['playlists'] = None
self._selects['playmode'] = None
self._selects['repeatmode'] = None # Previously, it was _select_playContinuous.
self._selects['speakers'] = None # Previously, it was _select_mediaPlayer.
self._selects['radiomode'] = None # Previously, it was _select_source.
# For old settings.
for k,v in OLD_INPUTS.items():
if v == CONF_SELECT_PLAYCONTINUOUS:
_domain = input_boolean.DOMAIN
else:
_domain = input_select.DOMAIN
try:
self._selects[k] = config.data.get(v)
except:
pass
if self._selects[k] is not None and self._selects[k].replace(" ","") != "":
self._selects[k] = _domain + "." + self._selects[k].replace(_domain + ".", "")
self.log_me('error', "Found old {} {}: {},Please consider using the new select entities.".format(_domain, k, self._selects[k] ))

self._like_in_name = config.data.get(CONF_LIKE_IN_NAME, DEFAULT_LIKE_IN_NAME)

self._attr_shuffle = config.data.get(CONF_SHUFFLE, DEFAULT_SHUFFLE)
Expand Down Expand Up @@ -487,9 +497,22 @@ async def async_set_repeat(self, repeat: str):
# Set repeat mode.
self._attr_repeat = repeat
if(self._selects['repeatmode'] is not None):
if self.hass.states.get(self._selects['repeatmode']).state != repeat:
data = {select.ATTR_OPTION: repeat, ATTR_ENTITY_ID: self._selects['repeatmode']}
await self.hass.services.async_call(select.DOMAIN, select.SERVICE_SELECT_OPTION, data)
if repeat == RepeatMode.ALL:
ib_repeat = STATE_ON
else:
ib_repeat = STATE_OFF
if (_state := self.hass.states.get(self._selects['repeatmode']).state) != repeat:
if input_boolean.DOMAIN in self._selects['repeatmode']:
if _state != ib_repeat:
data = {ATTR_ENTITY_ID: self._selects['repeatmode']}
if ib_repeat == STATE_ON:
await self.hass.services.async_call(input_boolean.DOMAIN, IB_ON, data)
else:
await self.hass.services.async_call(input_boolean.DOMAIN, IB_OFF, data)
else:
data = {select.ATTR_OPTION: repeat, ATTR_ENTITY_ID: self._selects['repeatmode']}
await self.hass.services.async_call(select.DOMAIN, select.SERVICE_SELECT_OPTION, data)

self.log_me('debug', f"[E] set_repeat: {repeat}")
self.async_schedule_update_ha_state()

Expand Down Expand Up @@ -1026,7 +1049,7 @@ async def async_update_selects(self, now=None):
self.log_me('debug', "[S] async_update_selects")
# -- Register dropdown(s). -- #
for dropdown in self._init_dropdowns:
if self._selects[dropdown] is None:
if not await self.async_check_entity_exists(self._selects[dropdown], unavailable_is_ok=False):
entity_id = self.hass.data[DOMAIN][self._attr_unique_id][f'select_{dropdown}'].entity_id
if await self.async_check_entity_exists(entity_id, unavailable_is_ok=False):
self._selects[dropdown] = entity_id
Expand Down Expand Up @@ -1071,9 +1094,18 @@ async def async_update_selects(self, now=None):
self._friendly_speakersList.update({a: friendly_name})
friendly_speakersList = list(self._friendly_speakersList.values())
if self._selects['speakers'] is not None:
await self.hass.data[DOMAIN][self._attr_unique_id]['select_speakers'].async_update(friendly_speakersList) # update speaker select
data = {select.ATTR_OPTION: friendly_speakersList[0], ATTR_ENTITY_ID: self._selects['speakers']} # select the first one in the list as the default player
await self.hass.services.async_call(select.DOMAIN, select.SERVICE_SELECT_OPTION, data)
if input_select.DOMAIN in self._selects['speakers']:
_select = input_select
else:
_select = select
data = {_select.ATTR_OPTIONS: friendly_speakersList, ATTR_ENTITY_ID: self._selects['speakers']}
if _select == input_select:
await self.hass.services.async_call(input_select.DOMAIN, input_select.SERVICE_SET_OPTIONS, data)
else:
await self.hass.data[DOMAIN][self._attr_unique_id]['select_speakers'].async_update(friendly_speakersList) # update speaker select

data = {_select.ATTR_OPTION: friendly_speakersList[0], ATTR_ENTITY_ID: self._selects['speakers']} # select the first one in the list as the default player
await self.hass.services.async_call(_select.DOMAIN, _select.SERVICE_SELECT_OPTION, data)

# finally call update playlist to fill the list .. if it exists
await self.async_update_playlists()
Expand Down Expand Up @@ -1142,7 +1174,12 @@ async def async_update_playlists(self, now=None):
# sort with case-ignore
playlists = sorted(list(self._playlist_to_index.keys()), key=str.casefold)
await self.async_update_extra_sensor('playlists', playlists_to_extra) # update extra sensor
await self.hass.data[DOMAIN][self._attr_unique_id]['select_playlists'].async_update() # update playlist select
if self._selects['playlists'] is not None: # update playlist select
if input_select.DOMAIN in self._selects['playlists']:
data = {input_select.ATTR_OPTIONS: list(playlists), ATTR_ENTITY_ID: self._selects["playlists"]}
await self.hass.services.async_call(input_select.DOMAIN, input_select.SERVICE_SET_OPTIONS, data)
else:
await self.hass.data[DOMAIN][self._attr_unique_id]['select_playlists'].async_update()
except:
self.exc()
msg = "Caught error while loading playlist. please log for details"
Expand Down Expand Up @@ -1191,14 +1228,15 @@ async def async_update_playmode(self, entity_id=None, old_state=None, new_state=
self.log_me('debug', "[S] async_update_playmode")
try:
if self._selects['repeatmode'] is not None:
await self.async_set_repeat(self.hass.states.get(self._selects['repeatmode']).state)
if (_state := self.hass.states.get(self._selects['repeatmode']).state) == STATE_ON:
_state = RepeatMode.ALL
await self.async_set_repeat(_state)
except:
self.log_me('debug', "- Selection field " + self._selects['repeatmode'] + " not found, skipping")

try:
if self._selects['playmode'] is not None:
_playmode = self.hass.states.get(self._selects['playmode']).state
if _playmode is not None:
if (_playmode := self.hass.states.get(self._selects['playmode']).state) is not None:
if _playmode in (PLAYMODE_SHUFFLE,PLAYMODE_DIRECT):
shuffle = False
else:
Expand Down Expand Up @@ -1572,8 +1610,12 @@ async def async_play_media(self, media_type, media_id, _player=None, **kwargs):
if _player is not None:
await self.async_update_remote_player(remote_player=_player)
if self._selects['speakers'] is not None:
data = {"option": _player, "entity_id": self._selects['speakers']}
await self.hass.services.async_call(select.DOMAIN, select.SERVICE_SELECT_OPTION, data)
if input_select.DOMAIN in self._selects['speakers']:
_select = input_select
else:
_select = select
data = {_select.ATTR_OPTION: _player, ATTR_ENTITY_ID: self._selects['speakers']}
await self.hass.services.async_call(_select.DOMAIN, _select.SERVICE_SELECT_OPTION, data)

# load Tracks depending on input
try:
Expand Down
14 changes: 12 additions & 2 deletions custom_components/ytube_music_player/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
"legacy_radio": "Create radio as watchlist of random playlist track",
"sort_browser": "Sort results in the media browser",
"extra_sensor": "Create sensor that provide extra information",
"dropdowns": "Create the dropdown(s) you want to use"
"dropdowns": "Create the dropdown(s) you want to use",
"select_speakers": "Entity id of input_select for speaker selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playmode": "Entity id of input_select for playmode selection(Deprecated. Leaving a space can permanently delete this field)",
"select_source": "Entity id of input_select for playlist/radio selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playlist": "Entity id of input_select for playlist selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playcontinuous": "Entity id of input_boolean for play continuous selection(Deprecated. Leaving a space can permanently delete this field)"
}
}
},
Expand Down Expand Up @@ -74,7 +79,12 @@
"legacy_radio": "Create radio as watchlist of random playlist track",
"sort_browser": "Sort results in the media browser",
"extra_sensor": "Create sensor that provide extra information",
"dropdowns": "Create the dropdown(s) you want to use"
"dropdowns": "Create the dropdown(s) you want to use",
"select_speakers": "Entity id of input_select for speaker selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playmode": "Entity id of input_select for playmode selection(Deprecated. Leaving a space can permanently delete this field)",
"select_source": "Entity id of input_select for playlist/radio selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playlist": "Entity id of input_select for playlist selection(Deprecated. Leaving a space can permanently delete this field)",
"select_playcontinuous": "Entity id of input_boolean for play continuous selection(Deprecated. Leaving a space can permanently delete this field)"
}
}
},
Expand Down
14 changes: 12 additions & 2 deletions custom_components/ytube_music_player/translations/zh-Hans.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@
"legacy_radio": "将随机播放列表曲目创建为收藏夹电台",
"sort_browser": "在媒体浏览器中对结果进行排序",
"extra_sensor": "创建提供额外信息的传感器实体",
"dropdowns": "创建你需要的下拉菜单实体"
"dropdowns": "创建你需要的下拉菜单实体",
"select_speakers": "播放设备下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playmode":"循环模式下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_source":"播放列表/电台选择下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playlist":"播放列表下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playcontinuous":"持续播放模式下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)"
}
}
},
Expand Down Expand Up @@ -74,7 +79,12 @@
"legacy_radio": "将随机播放列表曲目创建为收藏夹电台",
"sort_browser": "在媒体浏览器中对结果进行排序",
"extra_sensor": "创建提供额外信息的传感器实体",
"dropdowns": "创建你需要的下拉菜单实体"
"dropdowns": "创建你需要的下拉菜单实体",
"select_speakers": "播放设备下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playmode":"循环模式下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_source":"播放列表/电台选择下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playlist":"播放列表下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)",
"select_playcontinuous":"持续播放模式下拉菜单实体ID(已过期,留下一个空格字符可以永久删除这个字段)"
}
}
},
Expand Down

0 comments on commit 4cd3332

Please sign in to comment.