diff --git a/CHANGELOG.md b/CHANGELOG.md index 51a2a8600..d8ca93cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- `ElevenLabsTTSService` now supports additional runtime changes to the `model`, + `language`, and `voice_settings` parameters. + - Added `MistralLLMService`, using Mistral's chat completion API. - Added the ability to retry executing a chat completion after a timeout period diff --git a/src/pipecat/services/elevenlabs/tts.py b/src/pipecat/services/elevenlabs/tts.py index 37302c96f..792601031 100644 --- a/src/pipecat/services/elevenlabs/tts.py +++ b/src/pipecat/services/elevenlabs/tts.py @@ -370,13 +370,49 @@ class ElevenLabsTTSService(AudioContextWordTTSService): await self._connect() async def _update_settings(self, settings: Mapping[str, Any]): - """Update service settings and reconnect if voice changed.""" + """Update service settings and reconnect if voice, model, or language changed.""" + # Track previous values for settings that require reconnection prev_voice = self._voice_id + prev_model = self.model_name + prev_language = self._settings.get("language") + # Create snapshot of current voice settings to detect changes after update + prev_voice_settings = self._voice_settings.copy() if self._voice_settings else None + await super()._update_settings(settings) - if not prev_voice == self._voice_id: - logger.info(f"Switching TTS voice to: [{self._voice_id}]") + + # Update voice settings for the next context creation + self._voice_settings = self._set_voice_settings() + + # Check if URL-level settings changed (these require reconnection) + url_changed = ( + prev_voice != self._voice_id + or prev_model != self.model_name + or prev_language != self._settings.get("language") + ) + + # Check if only voice settings changed (speed, stability, etc.) + voice_settings_changed = prev_voice_settings != self._voice_settings + + if url_changed: + # These settings are in the WebSocket URL, so we need to reconnect + logger.debug( + f"URL-level setting changed (voice/model/language), reconnecting WebSocket" + ) await self._disconnect() await self._connect() + elif voice_settings_changed and self._context_id: + # Voice settings can be updated by closing current context + # so new one gets created with updated voice settings + logger.debug(f"Voice settings changed, closing current context to apply changes") + try: + if self._websocket: + await self._websocket.send( + json.dumps({"context_id": self._context_id, "close_context": True}) + ) + except Exception as e: + logger.warning(f"Error closing context for voice settings update: {e}") + self._context_id = None + self._started = False async def start(self, frame: StartFrame): """Start the ElevenLabs TTS service. diff --git a/src/pipecat/services/tts_service.py b/src/pipecat/services/tts_service.py index 9be28f319..556276494 100644 --- a/src/pipecat/services/tts_service.py +++ b/src/pipecat/services/tts_service.py @@ -258,7 +258,7 @@ class TTSService(AIService): self._settings[key] = self.language_to_service_language(value) elif key == "model": self.set_model_name(value) - elif key == "voice": + elif key == "voice" or key == "voice_id": self.set_voice(value) elif key == "text_filter": for filter in self._text_filters: