Skip to content

BaseOutputTransport: resolve media sender via type(self).MediaSender - #5176

Open
shivgargsquad wants to merge 1 commit into
pipecat-ai:mainfrom
shivgargsquad:mediasender-subclassable
Open

BaseOutputTransport: resolve media sender via type(self).MediaSender#5176
shivgargsquad wants to merge 1 commit into
pipecat-ai:mainfrom
shivgargsquad:mediasender-subclassable

Conversation

@shivgargsquad

Copy link
Copy Markdown

What

Resolve the media sender class from the transport's type at both construction sites in set_transport_ready() (base_output.py), instead of hardcoding BaseOutputTransport.MediaSender:

self._media_senders[destination] = type(self).MediaSender(...)

This lets a transport subclass provide its own sender implementation by shadowing the inner class — the same extension pattern the class already uses for destinations (register_audio_destination), per-destination mixers, and delivery (write_audio_frame). It completes the per-destination sender model introduced in #1697: the sender becomes a per-transport extension point.

Compatibility

Fully backward compatible. type(self).MediaSender resolves to BaseOutputTransport.MediaSender unless a subclass deliberately shadows the inner class; no behavior change for any existing transport.

class MyOutputTransport(FastAPIWebsocketOutputTransport):
    class MediaSender(FastAPIWebsocketOutputTransport.MediaSender):
        async def _bot_started_speaking(self):
            await super()._bot_started_speaking()
            ...  # transport-specific behavior

Use case

Telephony platform (FastAPI websocket transport, multiple carriers) where transport-specific behavior genuinely lives in the sender layer: per-carrier audio pacing interacting with the sender's clock task, clearing buffered audio on programmatic (non-VAD) interruption without the bot-stopped-speaking lifecycle (same need as #3872), and observability on bot speech start/stop from inside the audio task loop. Today the only way to express these is post-construction mutation of _media_senders, which #3872's workaround shows is already happening in the wild.

Tests

Added TestBaseOutputTransportCustomMediaSender covering:

  • default transports still get BaseOutputTransport.MediaSender
  • a subclass-shadowed MediaSender is used for both the default sender and per-destination senders

Closes #5166

Allows transport subclasses to provide a custom MediaSender
implementation by shadowing the inner class, completing the
per-destination sender model introduced in pipecat-ai#1697. Fully backward
compatible: resolves to BaseOutputTransport.MediaSender unless a
subclass deliberately shadows it.

Closes pipecat-ai#5166
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete the destinations refactor: allow transports to provide a custom MediaSender class

1 participant