diff --git a/custom_components/ytube_music_player/config_flow.py b/custom_components/ytube_music_player/config_flow.py index 91d53bd..7c1ac4e 100644 --- a/custom_components/ytube_music_player/config_flow.py +++ b/custom_components/ytube_music_player/config_flow.py @@ -222,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 @@ -233,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 diff --git a/custom_components/ytube_music_player/const.py b/custom_components/ytube_music_player/const.py index f8a973b..0654388 100644 --- a/custom_components/ytube_music_player/const.py +++ b/custom_components/ytube_music_player/const.py @@ -20,6 +20,7 @@ CONF_PASSWORD, STATE_PLAYING, STATE_PAUSED, + STATE_ON, STATE_OFF, STATE_IDLE, ATTR_COMMAND, @@ -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" } @@ -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' @@ -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 @@ -326,6 +353,16 @@ def ensure_config(user_input): 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 diff --git a/custom_components/ytube_music_player/media_player.py b/custom_components/ytube_music_player/media_player.py index 9e5a187..90c5d13 100644 --- a/custom_components/ytube_music_player/media_player.py +++ b/custom_components/ytube_music_player/media_player.py @@ -93,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) @@ -488,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() @@ -1027,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 @@ -1072,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() @@ -1143,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" @@ -1192,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: @@ -1573,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: diff --git a/custom_components/ytube_music_player/translations/en.json b/custom_components/ytube_music_player/translations/en.json index 2e9fffc..fb6a3fd 100644 --- a/custom_components/ytube_music_player/translations/en.json +++ b/custom_components/ytube_music_player/translations/en.json @@ -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)" } } }, @@ -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)" } } }, diff --git a/custom_components/ytube_music_player/translations/zh-Hans.json b/custom_components/ytube_music_player/translations/zh-Hans.json index 5e18027..cbbc255 100644 --- a/custom_components/ytube_music_player/translations/zh-Hans.json +++ b/custom_components/ytube_music_player/translations/zh-Hans.json @@ -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(已过期,留下一个空格字符可以永久删除这个字段)" } } }, @@ -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(已过期,留下一个空格字符可以永久删除这个字段)" } } },