Add tests, improve frame timing

This commit is contained in:
Mark Backman
2025-05-24 10:43:09 -04:00
parent 0ed46f457e
commit 74827f983f
2 changed files with 301 additions and 32 deletions

View File

@@ -45,60 +45,81 @@ class DTMFAggregator(FrameProcessor):
**kwargs,
):
super().__init__(**kwargs)
self._aggregation = ""
self._idle_timeout = timeout
self._termination_digit = termination_digit
self._prefix = prefix
self._digit_event = asyncio.Event()
self._aggregation_task = None
self._aggregation_task: Optional[asyncio.Task] = None
def _create_aggregation_task(self) -> None:
"""Creates the aggregation task if it hasn't been created yet."""
if not self._aggregation_task:
self._aggregation_task = self.create_task(self._aggregation_task_handler())
async def _stop(self) -> None:
"""Stops and cleans up the aggregation task."""
if self._aggregation_task:
await self.cancel_task(self._aggregation_task)
self._aggregation_task = None
async def cleanup(self) -> None:
await super().cleanup()
if self._aggregation_task:
await self._stop()
async def process_frame(self, frame: Frame, direction: FrameDirection) -> None:
await super().process_frame(frame, direction)
if isinstance(frame, InputDTMFFrame):
await self._handle_dtmf_frame(frame, direction)
if isinstance(frame, (EndFrame, CancelFrame)):
# Flush any pending aggregation before stopping
if self._aggregation:
await self._flush_aggregation()
await self._stop()
await self.push_frame(frame, direction)
return
elif isinstance(frame, StartInterruptionFrame):
# Flush on interruption
if self._aggregation:
await self._flush_aggregation(direction)
elif isinstance(frame, (EndFrame, CancelFrame)):
# Flush any pending aggregation
if self._aggregation:
await self._flush_aggregation(direction)
await self._stop_aggregation_task()
await self._flush_aggregation()
await self.push_frame(frame, direction)
return
# Push frame
await self.push_frame(frame, direction)
async def _handle_dtmf_frame(self, frame: InputDTMFFrame, direction: FrameDirection):
# Start aggregation task if not running
if self._aggregation_task is None:
self._aggregation_task = self.create_task(self._aggregation_task_handler(direction))
if isinstance(frame, InputDTMFFrame):
await self._handle_dtmf_frame(frame)
async def _handle_dtmf_frame(
self,
frame: InputDTMFFrame,
):
# Create task on first DTMF input if needed
if not self._aggregation_task:
self._create_aggregation_task()
# Add digit to aggregation
digit_value = frame.button.value
self._aggregation += digit_value
# Check for immediate flush conditions
if frame.button == self._termination_digit:
await self._flush_aggregation(direction)
await self._flush_aggregation()
else:
# Signal new digit received
self._digit_event.set()
async def _aggregation_task_handler(self, direction: FrameDirection):
async def _aggregation_task_handler(self):
"""Background task that handles timeout-based flushing."""
while True:
try:
await asyncio.wait_for(self._digit_event.wait(), timeout=self._idle_timeout)
self._digit_event.clear()
except asyncio.TimeoutError:
if self._aggregation:
await self._flush_aggregation(direction)
await self._flush_aggregation()
finally:
self._digit_event.clear()
async def _flush_aggregation(self, direction: FrameDirection):
async def _flush_aggregation(self):
"""Flush the current aggregation as a TranscriptionFrame."""
if not self._aggregation:
return
@@ -113,16 +134,7 @@ class DTMFAggregator(FrameProcessor):
text=transcription_text, user_id="", timestamp=time_now_iso8601()
)
await self.push_frame(transcription_frame, direction)
await self.push_frame(transcription_frame)
# Reset aggregation
self._aggregation = ""
async def _stop_aggregation_task(self):
if self._aggregation_task:
await self.cancel_task(self._aggregation_task)
self._aggregation_task = None
async def cleanup(self) -> None:
await super().cleanup()
await self._stop_aggregation_task()