Fixing an issue where the BotOutputTransport was discarding the UninterruptibleFrames.
This commit is contained in:
@@ -48,6 +48,7 @@ from pipecat.observers.base_observer import BaseObserver, FrameProcessed, FrameP
|
|||||||
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
|
from pipecat.processors.metrics.frame_processor_metrics import FrameProcessorMetrics
|
||||||
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
from pipecat.utils.asyncio.task_manager import BaseTaskManager
|
||||||
from pipecat.utils.base_object import BaseObject
|
from pipecat.utils.base_object import BaseObject
|
||||||
|
from pipecat.utils.frame_queue import FrameQueue
|
||||||
|
|
||||||
|
|
||||||
class FrameDirection(Enum):
|
class FrameDirection(Enum):
|
||||||
@@ -228,7 +229,7 @@ class FrameProcessor(BaseObject):
|
|||||||
# called. To resume processing frames we need to call
|
# called. To resume processing frames we need to call
|
||||||
# `resume_processing_frames()` which will wake up the event.
|
# `resume_processing_frames()` which will wake up the event.
|
||||||
self.__should_block_frames = False
|
self.__should_block_frames = False
|
||||||
self.__process_queue = asyncio.Queue()
|
self.__process_queue = FrameQueue(frame_getter=lambda item: item[0])
|
||||||
self.__process_event: Optional[asyncio.Event] = None
|
self.__process_event: Optional[asyncio.Event] = None
|
||||||
self.__process_frame_task: Optional[asyncio.Task] = None
|
self.__process_frame_task: Optional[asyncio.Task] = None
|
||||||
self.__process_current_frame: Optional[Frame] = None
|
self.__process_current_frame: Optional[Frame] = None
|
||||||
@@ -818,9 +819,14 @@ class FrameProcessor(BaseObject):
|
|||||||
async def _start_interruption(self):
|
async def _start_interruption(self):
|
||||||
"""Start handling an interruption by cancelling current tasks."""
|
"""Start handling an interruption by cancelling current tasks."""
|
||||||
try:
|
try:
|
||||||
if isinstance(self.__process_current_frame, UninterruptibleFrame):
|
current_is_uninterruptible = isinstance(
|
||||||
# We don't want to cancel UninterruptibleFrame, so we simply
|
self.__process_current_frame, UninterruptibleFrame
|
||||||
# cleanup the queue.
|
)
|
||||||
|
if current_is_uninterruptible or self.__process_queue.has_uninterruptible:
|
||||||
|
# We don't want to cancel an UninterruptibleFrame (either the
|
||||||
|
# one currently being processed or one waiting in the queue),
|
||||||
|
# so we simply cleanup the queue keeping only
|
||||||
|
# UninterruptibleFrames.
|
||||||
self.__reset_process_queue()
|
self.__reset_process_queue()
|
||||||
else:
|
else:
|
||||||
# Cancel and re-create the process task.
|
# Cancel and re-create the process task.
|
||||||
@@ -920,22 +926,7 @@ class FrameProcessor(BaseObject):
|
|||||||
|
|
||||||
def __reset_process_queue(self):
|
def __reset_process_queue(self):
|
||||||
"""Reset non-system frame processing queue."""
|
"""Reset non-system frame processing queue."""
|
||||||
# Create a new queue to insert UninterruptibleFrame frames.
|
self.__process_queue.reset()
|
||||||
new_queue = asyncio.Queue()
|
|
||||||
|
|
||||||
# Process current queue and keep UninterruptibleFrame frames.
|
|
||||||
while not self.__process_queue.empty():
|
|
||||||
item = self.__process_queue.get_nowait()
|
|
||||||
frame = item[0]
|
|
||||||
if isinstance(frame, UninterruptibleFrame):
|
|
||||||
new_queue.put_nowait(item)
|
|
||||||
self.__process_queue.task_done()
|
|
||||||
|
|
||||||
# Put back UninterruptibleFrame frames into our process queue.
|
|
||||||
while not new_queue.empty():
|
|
||||||
item = new_queue.get_nowait()
|
|
||||||
self.__process_queue.put_nowait(item)
|
|
||||||
new_queue.task_done()
|
|
||||||
|
|
||||||
async def __cancel_process_task(self):
|
async def __cancel_process_task(self):
|
||||||
"""Cancel the non-system frame processing task."""
|
"""Cancel the non-system frame processing task."""
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from pipecat.frames.frames import (
|
|||||||
)
|
)
|
||||||
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
from pipecat.processors.frame_processor import FrameDirection, FrameProcessor
|
||||||
from pipecat.transports.base_transport import TransportParams
|
from pipecat.transports.base_transport import TransportParams
|
||||||
|
from pipecat.utils.frame_queue import FrameQueue
|
||||||
from pipecat.utils.time import nanoseconds_to_seconds
|
from pipecat.utils.time import nanoseconds_to_seconds
|
||||||
|
|
||||||
BOT_VAD_STOP_SECS = 0.35
|
BOT_VAD_STOP_SECS = 0.35
|
||||||
@@ -518,14 +519,20 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
_: The start interruption frame (unused).
|
_: The start interruption frame (unused).
|
||||||
"""
|
"""
|
||||||
# Cancel tasks.
|
# Cancel tasks.
|
||||||
await self._cancel_audio_task()
|
|
||||||
await self._cancel_clock_task()
|
await self._cancel_clock_task()
|
||||||
await self._cancel_video_task()
|
await self._cancel_video_task()
|
||||||
|
|
||||||
|
if self._audio_queue.has_uninterruptible:
|
||||||
|
# Keep the audio task running but drain all interruptible frames
|
||||||
|
# so the pending UninterruptibleFrames are still delivered.
|
||||||
|
self._audio_queue.reset()
|
||||||
|
else:
|
||||||
|
await self._cancel_audio_task()
|
||||||
|
self._create_audio_task()
|
||||||
|
|
||||||
# Create tasks.
|
# Create tasks.
|
||||||
self._create_video_task()
|
self._create_video_task()
|
||||||
self._create_clock_task()
|
self._create_clock_task()
|
||||||
self._create_audio_task()
|
|
||||||
|
|
||||||
# Let's send a bot stopped speaking if we have to.
|
# Let's send a bot stopped speaking if we have to.
|
||||||
await self._bot_stopped_speaking()
|
await self._bot_stopped_speaking()
|
||||||
@@ -609,7 +616,7 @@ class BaseOutputTransport(FrameProcessor):
|
|||||||
def _create_audio_task(self):
|
def _create_audio_task(self):
|
||||||
"""Create the audio processing task."""
|
"""Create the audio processing task."""
|
||||||
if not self._audio_task:
|
if not self._audio_task:
|
||||||
self._audio_queue = asyncio.Queue()
|
self._audio_queue = FrameQueue()
|
||||||
self._audio_task = self._transport.create_task(self._audio_task_handler())
|
self._audio_task = self._transport.create_task(self._audio_task_handler())
|
||||||
|
|
||||||
async def _cancel_audio_task(self):
|
async def _cancel_audio_task(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user