@@ -504,8 +504,6 @@ def _render_pending_extra_replies_by_origin(
504504from utils .native_voice_registry import (
505505 is_free_preset_voice_id ,
506506 resolve_native_voice_for_routing ,
507- should_block_free_native_voice ,
508- should_block_free_voice_for_route ,
509507)
510508from utils .api_config_loader import (
511509 get_livestream_config ,
@@ -761,7 +759,7 @@ def __init__(self, sync_message_queue, lanlan_name, lanlan_prompt):
761759 self .core_api_type = realtime_config .get ('api_type' , '' ) or self ._config_manager .get_core_config ().get ('CORE_API_TYPE' , '' )
762760 self .memory_server_port = MEMORY_SERVER_PORT
763761 self .audio_api_key = self ._config_manager .get_core_config ()['AUDIO_API_KEY' ] # 用于CosyVoice自定义音色
764- self ._apply_voice_id_for_route (realtime_config . get ( 'base_url' , '' ) )
762+ self ._apply_voice_id_for_route ()
765763 # 注意:use_tts 会在 start_session 中根据 input_mode 重新设置
766764 self .use_tts = False
767765 self .generation_config = {} # Qwen暂时不用
@@ -3377,13 +3375,22 @@ async def _init_renew_status(self):
33773375 # auto-start 不被误清),但 end_session 语义就是整轮收尾,必须强制清场。
33783376 await self .state .reset (force = True )
33793377
3378+ def _realtime_base_url (self ) -> str :
3379+ """读取 realtime 线路 base_url,供 native voice 路由的 host 重映射
3380+ (海外免费 free→free_intl)使用。读不到时返回空串,按非 lanlan.app 处理。"""
3381+ try :
3382+ return str ((self ._config_manager .get_model_api_config ('realtime' ) or {}).get ('base_url' ) or '' )
3383+ except Exception :
3384+ return ''
3385+
33803386 def _has_custom_tts (self ) -> bool :
33813387 """判断当前会话是否使用自定义 TTS(克隆音色或自定义 TTS URL)。"""
33823388 core_config = self ._config_manager .get_core_config ()
33833389 _ , uses_provider_native_voice = resolve_native_voice_for_routing (
33843390 self .core_api_type ,
33853391 self .voice_id ,
33863392 self ._config_manager .voice_id_exists_in_any_storage ,
3393+ realtime_base_url = self ._realtime_base_url (),
33873394 )
33883395 if uses_provider_native_voice :
33893396 return False
@@ -3722,17 +3729,12 @@ def _resolve_session_use_tts(
37223729 logger .info (f"{ log_prefix } 🎙️ livestream 模式:使用服务端原生语音,跳过外部 TTS" )
37233730 return False
37243731 base_url = realtime_config .get ('base_url' , '' )
3725- if should_block_free_native_voice (
3726- self .core_api_type , self .voice_id , base_url ,
3732+ _ , uses_provider_native_voice = resolve_native_voice_for_routing (
3733+ self .core_api_type ,
3734+ self .voice_id ,
37273735 self ._config_manager .voice_id_exists_in_any_storage ,
3728- ):
3729- uses_provider_native_voice = False
3730- else :
3731- _ , uses_provider_native_voice = resolve_native_voice_for_routing (
3732- self .core_api_type ,
3733- self .voice_id ,
3734- self ._config_manager .voice_id_exists_in_any_storage ,
3735- )
3736+ realtime_base_url = base_url ,
3737+ )
37363738 if uses_provider_native_voice :
37373739 logger .info (f"{ log_prefix } 🔊 { self .core_api_type } 原生音色 '{ self .voice_id } ' 将直接传入 RealtimeClient" )
37383740 return False
@@ -3760,24 +3762,23 @@ def _get_voice_id(self) -> str:
37603762 # 比较 / route gating / is_free_preset_voice_id 之类的 callee 失配。
37613763 return (raw or '' ).strip ()
37623764
3763- def _apply_voice_id_for_route (self , realtime_base_url : str ) -> None :
3765+ def _apply_voice_id_for_route (self ) -> None :
37643766 """按当前 route 把角色卡里的 voice_id 解析进 self.voice_id /
37653767 self._is_free_preset_voice。
37663768
37673769 __init__ / start_session / _background_prepare_pending_session 三处
3768- 共用:读取 _get_voice_id() → 海外 free 路由屏蔽 → 校正 free preset
3769- 与 core_api_type 的匹配关系。集中在这里避免规则漂移。
3770+ 共用:读取 _get_voice_id() → 校正 free preset 与 core_api_type 的匹配
3771+ 关系。集中在这里避免规则漂移。
3772+
3773+ 历史上这里还按"海外 lanlan.app 会硬覆盖成 Leda"屏蔽 voice 下发;
3774+ 现在海外免费统一走 www.lanlan.app 透传 voice(Gemini 全量 + yui,由
3775+ free_intl provider 认领),不再屏蔽——stale 的阶跃/free 预设音色在海外
3776+ 路由下不会命中 free_intl catalog,自然 fall through,不需要预清。
3777+
3778+ 空 voice_id 保持空:海外免费下"空 → 默认音色"的映射交给服务端
3779+ (www.lanlan.app)处理,客户端不再注入兜底音色。
37703780 """
37713781 raw_voice_id = self ._get_voice_id ()
3772- if should_block_free_voice_for_route (
3773- self .core_api_type ,
3774- raw_voice_id ,
3775- realtime_base_url ,
3776- self ._config_manager .voice_id_exists_in_any_storage ,
3777- ):
3778- self .voice_id = ''
3779- self ._is_free_preset_voice = False
3780- return
37813782 self .voice_id = raw_voice_id
37823783 self ._is_free_preset_voice = is_free_preset_voice_id (raw_voice_id )
37833784 # free preset 选了但当前非 free 模式 → 不下发,避免把 preset id 透给别的 provider。
@@ -3799,20 +3800,16 @@ def _resolve_realtime_voice(self, realtime_config: dict):
37993800 (绕过 free_voices preset gate,base_url 已被派生不含 lanlan.tech)
38003801 3. 否则保留原逻辑:仅在角色 voice 是 free preset、core_api_type='free'
38013802 且 base_url 仍指向 lanlan.tech 域时下发,避免把 preset id 透给非
3802- lanlan 服务(lanlan.app 的屏蔽由 should_block_free_voice_for_route 兜底)
3803+ lanlan 服务。海外免费(free + *.lanlan.app)的 yui / Gemini 音色由
3804+ resolve_native_voice_for_routing 经 free_intl 重映射在第 1 步直接命中。
38033805 """
38043806 base_url = realtime_config .get ('base_url' , '' )
3805- if should_block_free_native_voice (
3806- self .core_api_type , self .voice_id , base_url ,
3807+ voice_name , uses_provider_native_voice = resolve_native_voice_for_routing (
3808+ self .core_api_type ,
3809+ self .voice_id ,
38073810 self ._config_manager .voice_id_exists_in_any_storage ,
3808- ):
3809- voice_name , uses_provider_native_voice = self .voice_id , False
3810- else :
3811- voice_name , uses_provider_native_voice = resolve_native_voice_for_routing (
3812- self .core_api_type ,
3813- self .voice_id ,
3814- self ._config_manager .voice_id_exists_in_any_storage ,
3815- )
3811+ realtime_base_url = base_url ,
3812+ )
38163813 if uses_provider_native_voice :
38173814 return voice_name
38183815 if self ._is_livestream_active ():
@@ -4177,7 +4174,7 @@ async def start_session(self, websocket: WebSocket, new=False, input_mode='audio
41774174 # 重新读取角色配置以获取最新的voice_id(支持角色切换后的音色热更新)
41784175 _ , _ , _ , self .lanlan_basic_config , _ , _ , _ , _ , _ = await self ._config_manager .aget_character_data ()
41794176 old_voice_id = self .voice_id
4180- self ._apply_voice_id_for_route (realtime_config . get ( 'base_url' , '' ) )
4177+ self ._apply_voice_id_for_route ()
41814178
41824179 # 如果角色没有设置 voice_id,尝试使用自定义API配置的 TTS_VOICE_ID 作为回退
41834180 if not self .voice_id :
@@ -4866,7 +4863,7 @@ async def _background_prepare_pending_session(self):
48664863 # 重新读取角色配置以获取最新的voice_id(支持角色切换后的音色热更新)
48674864 _ , _ , _ , self .lanlan_basic_config , _ , _ , _ , _ , _ = await self ._config_manager .aget_character_data ()
48684865 old_voice_id = self .voice_id
4869- self ._apply_voice_id_for_route (realtime_config . get ( 'base_url' , '' ) )
4866+ self ._apply_voice_id_for_route ()
48704867
48714868 # 如果角色没有设置 voice_id,尝试使用自定义API配置的 TTS_VOICE_ID 作为回退
48724869 if not self .voice_id :
0 commit comments