BaseOutputTransport/DailyTransport: allow sending DTMF keypress

This commit is contained in:
Aleix Conchillo Flaqué
2025-05-28 18:03:33 -07:00
parent 513221d9fd
commit 5676920a6a
3 changed files with 36 additions and 0 deletions

View File

@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- It is now possible to push `OutputDTMFFrame` or `OutputDTMFUrgentFrame` with
`DailyTransport`. This will be sent properly if a Daily dial-out connection
has been established.
- Added `OutputDTMFUrgentFrame` to send a DTMF keypress quickly. The previous
`OutputDTMFFrame` queues the keypress with the rest of data frames.
@@ -16,6 +20,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`DailyTransport.stop_transcription()` to be able to start and stop Daily
transcription dynamically (maybe with different settings).
### Deprecated
- `DailyTransport.send_dtmf()` is deprecated, push an `OutputDTMFFrame` or an
`OutputDTMFUrgentFrame` instead.
### Fixed
- In `AWSBedrockLLMService`, worked around a possible bug in AWS Bedrock where

View File

@@ -25,6 +25,8 @@ from pipecat.frames.frames import (
Frame,
MixerControlFrame,
OutputAudioRawFrame,
OutputDTMFFrame,
OutputDTMFUrgentFrame,
OutputImageRawFrame,
SpriteFrame,
StartFrame,
@@ -140,6 +142,9 @@ class BaseOutputTransport(FrameProcessor):
async def write_raw_audio_frames(self, frames: bytes, destination: Optional[str] = None):
pass
async def write_dtmf(self, frame: OutputDTMFFrame | OutputDTMFUrgentFrame):
pass
async def send_audio(self, frame: OutputAudioRawFrame):
await self.queue_frame(frame, FrameDirection.DOWNSTREAM)
@@ -171,6 +176,8 @@ class BaseOutputTransport(FrameProcessor):
await self._handle_frame(frame)
elif isinstance(frame, TransportMessageUrgentFrame):
await self.send_message(frame)
elif isinstance(frame, OutputDTMFUrgentFrame):
await self.write_dtmf(frame)
elif isinstance(frame, SystemFrame):
await self.push_frame(frame, direction)
# Control frames.
@@ -425,6 +432,8 @@ class BaseOutputTransport(FrameProcessor):
await self._set_video_images(frame.images)
elif isinstance(frame, TransportMessageFrame):
await self._transport.send_message(frame)
elif isinstance(frame, OutputDTMFFrame):
await self._transport.write_dtmf(frame)
def _next_frame(self) -> AsyncGenerator[Frame, None]:
async def without_mixer(vad_stop_secs: float) -> AsyncGenerator[Frame, None]:

View File

@@ -22,6 +22,8 @@ from pipecat.frames.frames import (
Frame,
InterimTranscriptionFrame,
OutputAudioRawFrame,
OutputDTMFFrame,
OutputDTMFUrgentFrame,
OutputImageRawFrame,
SpriteFrame,
StartFrame,
@@ -1220,6 +1222,14 @@ class DailyOutputTransport(BaseOutputTransport):
async def register_audio_destination(self, destination: str):
await self._client.register_audio_destination(destination)
async def write_dtmf(self, frame: OutputDTMFFrame | OutputDTMFUrgentFrame):
await self._client.send_dtmf(
{
"sessionId": frame.transport_destination,
"tones": frame.button.value,
}
)
async def write_raw_audio_frames(self, frames: bytes, destination: Optional[str] = None):
await self._client.write_raw_audio_frames(frames, destination)
@@ -1372,6 +1382,14 @@ class DailyTransport(BaseTransport):
await self._client.stop_dialout(participant_id)
async def send_dtmf(self, settings):
import warnings
with warnings.catch_warnings():
warnings.simplefilter("always")
warnings.warn(
"`DailyTransport.send_dtmf()` is deprecated, push an `OutputDTMFFrame` or an `OutputDTMFUrgentFrame` instead.",
DeprecationWarning,
)
await self._client.send_dtmf(settings)
async def sip_call_transfer(self, settings):