update timestamps reset handling

This commit is contained in:
Ian Lee
2026-02-03 21:55:53 -08:00
parent cbe131636d
commit d10467e043
2 changed files with 43 additions and 5 deletions

View File

@@ -1 +0,0 @@
- Added support for Inworld TTS Websocket Auto Mode for improved latency

View File

@@ -499,6 +499,14 @@ class InworldTTSService(AudioContextWordTTSService):
self._context_id = None self._context_id = None
self._started = False self._started = False
# Track cumulative time across generations for monotonic timestamps within a turn.
# When auto_mode is enabled, the server controls generations and timestamps reset
# to 0 after each generation, as indicated by a "flushCompleted" message. We
# add _cumulative_time to maintain monotonically increasing timestamps.
self._cumulative_time = 0.0
# Track the end time of the last word in the current generation
self._generation_end_time = 0.0
self.set_voice(voice_id) self.set_voice(voice_id)
self.set_model_name(model) self.set_model_name(model)
@@ -559,27 +567,50 @@ class InworldTTSService(AudioContextWordTTSService):
await super().push_frame(frame, direction) await super().push_frame(frame, direction)
if isinstance(frame, (TTSStoppedFrame, InterruptionFrame)): if isinstance(frame, (TTSStoppedFrame, InterruptionFrame)):
self._started = False self._started = False
logger.trace(
f"{self}: Resetting timestamp tracking due to {type(frame).__name__} - "
f"cumulative_time was {self._cumulative_time}"
)
self._cumulative_time = 0.0
self._generation_end_time = 0.0
if isinstance(frame, TTSStoppedFrame): if isinstance(frame, TTSStoppedFrame):
await self.add_word_timestamps([("Reset", 0)]) await self.add_word_timestamps([("Reset", 0)])
def _calculate_word_times(self, timestamp_info: Dict[str, Any]) -> List[Tuple[str, float]]: def _calculate_word_times(self, timestamp_info: Dict[str, Any]) -> List[Tuple[str, float]]:
"""Calculate word timestamps from Inworld WebSocket API response. """Calculate word timestamps from Inworld WebSocket API response.
Adds cumulative time offset to maintain monotonically increasing timestamps
across multiple generations within an agent turn. Also tracks the generation
end time for updating cumulative time on flush.
Args: Args:
timestamp_info: The timestamp information from Inworld API. timestamp_info: The timestamp information from Inworld API.
Returns: Returns:
A list of (word, timestamp) tuples. List of (word, timestamp) tuples with cumulative offset applied.
""" """
word_times: List[Tuple[str, float]] = [] word_times: List[Tuple[str, float]] = []
alignment = timestamp_info.get("wordAlignment", {}) alignment = timestamp_info.get("wordAlignment", {})
words = alignment.get("words", []) words = alignment.get("words", [])
start_times = alignment.get("wordStartTimeSeconds", []) start_times = alignment.get("wordStartTimeSeconds", [])
end_times = alignment.get("wordEndTimeSeconds", [])
if words and start_times and len(words) == len(start_times): if words and start_times and len(words) == len(start_times):
for i, word in enumerate(words): for i, word in enumerate(words):
word_times.append((word, start_times[i])) word_start = self._cumulative_time + start_times[i]
word_times.append((word, word_start))
# Track cumulative end time for this generation
if end_times and len(end_times) > 0:
self._generation_end_time = self._cumulative_time + end_times[-1]
logger.trace(
f"{self}: Word timestamps - raw_start_times={start_times}, "
f"cumulative_offset={self._cumulative_time}, "
f"adjusted_times={[t for _, t in word_times]}, "
f"generation_end_time={self._generation_end_time}"
)
return word_times return word_times
@@ -604,6 +635,8 @@ class InworldTTSService(AudioContextWordTTSService):
self._context_id = None self._context_id = None
self._started = False self._started = False
self._cumulative_time = 0.0
self._generation_end_time = 0.0
logger.trace(f"{self}: Interruption handled, context reset to None") logger.trace(f"{self}: Interruption handled, context reset to None")
def _get_websocket(self): def _get_websocket(self):
@@ -693,6 +726,8 @@ class InworldTTSService(AudioContextWordTTSService):
self._started = False self._started = False
self._context_id = None self._context_id = None
self._websocket = None self._websocket = None
self._cumulative_time = 0.0
self._generation_end_time = 0.0
await self._call_event_handler("on_disconnected") await self._call_event_handler("on_disconnected")
async def _receive_messages(self): async def _receive_messages(self):
@@ -779,9 +814,13 @@ class InworldTTSService(AudioContextWordTTSService):
if "contextCreated" in result: if "contextCreated" in result:
logger.trace(f"{self}: Context created on server: {ctx_id}") logger.trace(f"{self}: Context created on server: {ctx_id}")
# Handle flush completion - context is still valid, just acknowledge it # Handle flush completion, which indicates the end of a generation
if "flushCompleted" in result: if "flushCompleted" in result:
logger.trace(f"{self}: Flush completed for context {ctx_id}") logger.trace(
f"{self}: Generation completed - updating cumulative_time: "
f"{self._cumulative_time} -> {self._generation_end_time}"
)
self._cumulative_time = self._generation_end_time
# Handle context closed - context no longer exists on server # Handle context closed - context no longer exists on server
if "contextClosed" in result: if "contextClosed" in result: