async def _process_down_queue(self):
        """This tasks process frames coming downstream from the pipeline. For
        example, heartbeat frames or an EndFrame which would indicate all
        processors have handled the EndFrame and therefore we can exit the task
        cleanly.

        """
        while True:
            frame = await self._down_queue.get()

            # Queue received frame to the idle queue so we can monitor idle
            # pipelines.
            await self._idle_queue.put(frame)

            if isinstance(frame, self._reached_downstream_types):
                await self._call_event_handler("on_frame_reached_downstream", frame)

            if isinstance(frame, StartFrame):
                await self._call_event_handler("on_pipeline_started", frame)
            elif isinstance(frame, EndFrame):
                await self._call_event_handler("on_pipeline_ended", frame)
                self._pipeline_end_event.set()
            elif isinstance(frame, StopFrame):
                await self._call_event_handler("on_pipeline_stopped", frame)
                self._pipeline_end_event.set()
            elif isinstance(frame, CancelFrame):
                await self._call_event_handler("on_pipeline_cancelled", frame)
            elif isinstance(frame, HeartbeatFrame):
                await self._heartbeat_queue.put(frame)
            self._down_queue.task_done()
This tasks process frames coming downstream from the pipeline. For
        example, heartbeat frames or an EndFrame which would indicate all
        processors have handled the EndFrame and therefore we can exit the task
        cleanly.

        
src/pipecat/pipeline/task.py