Add SIP transfer and SIP REFER frames to Daily transport
Add write_transport_frame() hook to BaseOutputTransport so subclasses can handle custom frame types that flow through the audio queue. Add DailySIPTransferFrame and DailySIPReferFrame as DataFrame subclasses that queue with audio, ensuring SIP operations execute only after the bot finishes its current utterance. Override write_transport_frame in DailyOutputTransport to dispatch these frames to the existing sip_call_transfer() and sip_refer() client methods. Also switch DailyOutputTransport.send_message error handling from logger.error to push_error for consistency.
This commit is contained in:
@@ -237,6 +237,18 @@ class BaseOutputTransport(FrameProcessor):
|
||||
else:
|
||||
await self._write_dtmf_audio(frame)
|
||||
|
||||
async def write_transport_frame(self, frame: Frame):
|
||||
"""Handle a queued frame after preceding audio has been sent.
|
||||
|
||||
Override in transport subclasses to handle custom frame types that
|
||||
flow through the audio queue. Called by the media sender after the
|
||||
frame has waited for any preceding audio to finish.
|
||||
|
||||
Args:
|
||||
frame: The frame to handle.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _supports_native_dtmf(self) -> bool:
|
||||
"""Override in transport implementations that support native DTMF.
|
||||
|
||||
@@ -681,6 +693,8 @@ class BaseOutputTransport(FrameProcessor):
|
||||
await self._transport.send_message(frame)
|
||||
elif isinstance(frame, OutputDTMFFrame):
|
||||
await self._transport.write_dtmf(frame)
|
||||
else:
|
||||
await self._transport.write_transport_frame(frame)
|
||||
|
||||
def _next_frame(self) -> AsyncGenerator[Frame, None]:
|
||||
"""Generate the next frame for audio processing.
|
||||
|
||||
@@ -15,7 +15,7 @@ import asyncio
|
||||
import time
|
||||
from concurrent.futures import CancelledError as FuturesCancelledError
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Awaitable, Callable, Dict, Mapping, Optional, Tuple
|
||||
|
||||
import aiohttp
|
||||
@@ -26,6 +26,7 @@ from pipecat.audio.vad.vad_analyzer import VADAnalyzer, VADParams
|
||||
from pipecat.frames.frames import (
|
||||
CancelFrame,
|
||||
ControlFrame,
|
||||
DataFrame,
|
||||
EndFrame,
|
||||
Frame,
|
||||
InputAudioRawFrame,
|
||||
@@ -182,6 +183,36 @@ class DailyInputTransportMessageUrgentFrame(DailyInputTransportMessageFrame):
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailySIPTransferFrame(DataFrame):
|
||||
"""SIP call transfer frame for transport queuing.
|
||||
|
||||
A SIP call transfer that will be queued. The transfer will happen after any
|
||||
preceding audio finishes playing, allowing the bot to complete its current
|
||||
utterance before the transfer occurs.
|
||||
|
||||
Parameters:
|
||||
settings: SIP call transfer settings.
|
||||
"""
|
||||
|
||||
settings: Mapping[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailySIPReferFrame(DataFrame):
|
||||
"""SIP REFER frame for transport queuing.
|
||||
|
||||
A SIP REFER that will be queued. The REFER will happen after any preceding
|
||||
audio finishes playing, allowing the bot to complete its current utterance
|
||||
before the REFER occurs.
|
||||
|
||||
Parameters:
|
||||
settings: SIP REFER settings.
|
||||
"""
|
||||
|
||||
settings: Mapping[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyUpdateRemoteParticipantsFrame(ControlFrame):
|
||||
"""Frame to update remote participants in Daily calls.
|
||||
@@ -1968,7 +1999,7 @@ class DailyOutputTransport(BaseOutputTransport):
|
||||
"""
|
||||
error = await self._client.send_message(frame)
|
||||
if error:
|
||||
logger.error(f"Unable to send message: {error}")
|
||||
await self.push_error(f"{self}: Unable to send message: {error}")
|
||||
|
||||
async def register_video_destination(self, destination: str):
|
||||
"""Register a video output destination.
|
||||
@@ -2011,6 +2042,21 @@ class DailyOutputTransport(BaseOutputTransport):
|
||||
"""
|
||||
return await self._client.write_video_frame(frame)
|
||||
|
||||
async def write_transport_frame(self, frame: Frame):
|
||||
"""Handle queued SIP frames after preceding audio has been sent.
|
||||
|
||||
Args:
|
||||
frame: The frame to handle.
|
||||
"""
|
||||
if isinstance(frame, DailySIPTransferFrame):
|
||||
error = await self._client.sip_call_transfer(frame.settings)
|
||||
if error:
|
||||
await self.push_error(f"{self}: Unable to transfer SIP call: {error}")
|
||||
elif isinstance(frame, DailySIPReferFrame):
|
||||
error = await self._client.sip_refer(frame.settings)
|
||||
if error:
|
||||
await self.push_error(f"{self}: Unable to perform SIP REFER: {error}")
|
||||
|
||||
def _supports_native_dtmf(self) -> bool:
|
||||
"""Daily supports native DTMF via telephone events.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user