Transport Layer
All transports implement the same Transport interface — swap them without changing application logic.
Transport Interface
interface Transport {
connect(): Promise<void>;
disconnect(): void;
sendAudio(buffer: ArrayBuffer): void;
sendMessage(msg: object): void;
on(event: 'onAudio' | 'onMessage' | 'onStatusChange' | 'onError' | 'onTrack', handler): void;
}
WebSocketTransport
import { WebSocketTransport } from './sdk/transports/WebSocketTransport';
const t = new WebSocketTransport({
url: 'ws://localhost:8000/ws',
timeoutMs: 10_000,
});
t.on('onAudio', buf => { /* ArrayBuffer PCM16 */ });
t.on('onMessage', msg => { /* JSON control object */ });
t.on('onStatusChange', status => { /* idle|connecting|connected|failed|closed */ });
t.on('onError', err => { /* Error */ });
await t.connect();
t.sendAudio(pcmBuffer);
t.disconnect();
WebRTCTransport
Uses SmallWebRTC (Pipecat's lightweight signalling layer). Audio flows through a media track — sendAudio() is a no-op.
import { WebRTCTransport } from './sdk/transports/WebRTCTransport';
const t = new WebRTCTransport({
baseUrl: 'http://localhost:8000',
metadata: { session_type: 'default' },
iceTransportPolicy: 'all', // 'relay' forces TURN only
staticIceServers: [], // skip /ice-servers fetch
timeoutMs: 15_000,
});
t.on('onTrack', stream => {
const audio = document.createElement('audio');
audio.srcObject = stream;
audio.autoplay = true;
});
await t.connect();
iceTransportPolicy: 'relay' is useful for testing that TURN is correctly configured.
TransportSwitcher
TransportSwitcher wraps two transports and upgrades mid-conversation — start on WebSocket for fastest connection, seamlessly cut over to WebRTC for better audio quality.
Setup
import { WebSocketTransport } from './sdk/transports/WebSocketTransport';
import { WebRTCTransport } from './sdk/transports/WebRTCTransport';
import { TransportSwitcher } from './sdk/transports/TransportSwitcher';
const ws = new WebSocketTransport({ url: 'ws://localhost:8000/ws' });
const rtc = new WebRTCTransport({ baseUrl: 'http://localhost:8000' });
const switcher = new TransportSwitcher(ws, {
triggers: ['manual', 'qos'],
silenceGapMs: 300,
upgradeTimeoutMs: 10_000,
qosThresholds: {
rttUpgradeMs: 250, // upgrade when avg RTT > 250 ms
consecutiveBreachCount: 3, // require 3 consecutive breaches
},
});
await ws.connect();
Manual Upgrade
// Pre-connect WebRTC in the background — no audio interruption
await switcher.prepare(rtc);
// Cut over during the next silence gap
const result = await switcher.upgrade();
// { success: true, from: 'websocket', to: 'webrtc', gapMs: 12 }
Automatic QoS-Driven Upgrade
switcher.enableQoS(); // monitors primary; upgrades automatically when thresholds breach
Lifecycle Phases
idle → preparing → ready → switching → done / failed
After a successful upgrade, the switcher moves the secondary transport into the primary slot and restarts QoS monitoring on it.
Tear Down
switcher.destroy(); // cancels QoS, disconnects both transports