Handle done message from Rime TTS to avoid stop-frame timeout

Rime's WebSocket API sends a done message when synthesis completes.
Handle it to stop TTFB metrics, push TTSStoppedFrame, and remove the
audio context immediately instead of relying on the 3-second
stop_frame_timeout_s fallback.
This commit is contained in:
Mark Backman
2026-03-27 12:37:03 -04:00
parent 70df9d3fe4
commit 1808b447c9

View File

@@ -24,13 +24,11 @@ from pipecat.frames.frames import (
EndFrame, EndFrame,
ErrorFrame, ErrorFrame,
Frame, Frame,
InterruptionFrame,
StartFrame, StartFrame,
TTSAudioRawFrame, TTSAudioRawFrame,
TTSStartedFrame, TTSStartedFrame,
TTSStoppedFrame, TTSStoppedFrame,
) )
from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven from pipecat.services.settings import NOT_GIVEN, TTSSettings, _NotGiven
from pipecat.services.tts_service import ( from pipecat.services.tts_service import (
InterruptibleTTSService, InterruptibleTTSService,
@@ -273,7 +271,6 @@ class RimeTTSService(WebsocketTTSService):
text_aggregation_mode=text_aggregation_mode, text_aggregation_mode=text_aggregation_mode,
aggregate_sentences=aggregate_sentences, aggregate_sentences=aggregate_sentences,
push_text_frames=False, push_text_frames=False,
push_stop_frames=True,
pause_frame_processing=True, pause_frame_processing=True,
append_trailing_space=True, append_trailing_space=True,
sample_rate=sample_rate, sample_rate=sample_rate,
@@ -521,9 +518,8 @@ class RimeTTSService(WebsocketTTSService):
async def on_audio_context_completed(self, context_id: str): async def on_audio_context_completed(self, context_id: str):
"""Clear server-side state and stop metrics after the Rime context finishes playing. """Clear server-side state and stop metrics after the Rime context finishes playing.
Rime does not send a server-side completion signal (e.g. ``done`` / ``end_of_stream`` / Sends a ``clear`` message to clean up any residual server-side state
``audio_end``), so we explicitly send a ``clear`` message to clean up once all audio has been delivered.
any residual server-side state once all audio has been delivered.
""" """
await self._close_context(context_id) await self._close_context(context_id)
await super().on_audio_context_completed(context_id) await super().on_audio_context_completed(context_id)
@@ -600,6 +596,13 @@ class RimeTTSService(WebsocketTTSService):
self._cumulative_time = ends[-1] + self._cumulative_time self._cumulative_time = ends[-1] + self._cumulative_time
logger.debug(f"Updated cumulative time to: {self._cumulative_time}") logger.debug(f"Updated cumulative time to: {self._cumulative_time}")
elif msg["type"] == "done":
await self.stop_ttfb_metrics()
await self.append_to_audio_context(
context_id, TTSStoppedFrame(context_id=context_id)
)
await self.remove_audio_context(context_id)
elif msg["type"] == "error": elif msg["type"] == "error":
await self.push_frame(TTSStoppedFrame()) await self.push_frame(TTSStoppedFrame())
await self.stop_all_metrics() await self.stop_all_metrics()