# Plivo

> Phone calls over Plivo audio streaming with PlivoFrameSerializer, used with FastAPIWebsocketTransport.

Source: https://polvallverdu.github.io/pipedog/docs/serializers/plivo/



## Overview [#overview]

**`PlivoFrameSerializer`** speaks Plivo's audio streaming WebSocket protocol. Paired with
[`FastAPIWebsocketTransport`](https://polvallverdu.github.io/pipedog/docs/transports/websocket.md), it lets a bot answer phone calls:

* Caller audio (`media` events, μ-law at 8 kHz) becomes `InputAudioRawFrame`s at the pipeline's
  input rate.
* Bot audio is resampled to 8 kHz, μ-law encoded and sent as `playAudio` events.
* Touch-tone digits become `InputDTMFFrame`s.
* An `InterruptionFrame` sends `clearAudio`.
* An `EndFrame` or `CancelFrame` hangs up the call through Plivo's REST API (`autoHangUp`).

See [How phone calls connect](https://polvallverdu.github.io/pipedog/docs/telephony.md) for the overall flow.

<Cards>
  <Card title="Upstream page" href="https://docs.pipecat.ai/api-reference/server/services/serializers/plivo">
    Pipecat's documentation for the same serializer.
  </Card>

  <Card title="Source" href="https://github.com/polvallverdu/pipedog/tree/main/packages/serializer-plivo/src">
    `packages/serializer-plivo/src/plivo.ts`.
  </Card>

  <Card title="Plivo docs" href="https://www.plivo.com/docs/voice/xml/the-stream-element">
    The Stream XML element and audio streaming.
  </Card>
</Cards>

## Installation [#installation]

```bash
bun add @pipedog/serializer-plivo @pipedog/transport-websocket @pipedog/runner
```

```ts
import { PlivoFrameSerializer } from "@pipedog/serializer-plivo/plivo";
```

## Prerequisites [#prerequisites]

* A Plivo account and a number whose answer XML has a bidirectional
  `<Stream bidirectional="true" keepCallAlive="true">wss://your.host/ws</Stream>`.
* Your auth ID and auth token, for auto hang-up. The examples read them from `PLIVO_AUTH_ID` and
  `PLIVO_AUTH_TOKEN`.
* The stream ID and call ID, which Plivo sends in its `start` message.

## PlivoFrameSerializer [#plivoframeserializer]

### Configuration [#configuration]

| Option | Type | Required | Description |
|---|---|---|---|
| `streamId` | `string` | yes | The Plivo Stream ID. |
| `callId` | `string \| null \| undefined` | no | The associated Plivo Call ID (optional, but required for auto hang-up). |
| `authId` | `string \| null \| undefined` | no | Plivo auth ID (required for auto hang-up). |
| `authToken` | `string \| null \| undefined` | no | Plivo auth token (required for auto hang-up). |
| `params` | `PlivoFrameSerializerInputParams \| null \| undefined` | no | Configuration parameters. |
| `name` | `string \| null \| undefined` | no | Custom name; defaults to `ClassName#count`. |
| `taskManager` | `BaseTaskManager \| null \| undefined` | no | Task manager for handling tasks. |

### InputParams [#inputparams]

`params` is a `PlivoFrameSerializer.InputParams` (`PlivoFrameSerializerInputParams`):

| Param                     | Type             | Default | Description                                                                                    |
| ------------------------- | ---------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `plivoSampleRate`         | `number`         | `8000`  | Sample rate Plivo uses, in Hz.                                                                 |
| `sampleRate`              | `number \| null` | `null`  | Override for the pipeline input rate. `null` uses the pipeline's.                              |
| `autoHangUp`              | `boolean`        | `true`  | Hang up the call on `EndFrame` or `CancelFrame`.                                               |
| `resamplerClearAfterSecs` | `number \| null` | `0.2`   | Seconds of silence after which the stream resamplers clear their history. `null` never clears. |
| `ignoreRtviMessages`      | `boolean`        | `true`  | Drop RTVI messages instead of sending them to Plivo.                                           |

### Usage [#usage]

```ts title="plivo.ts"
import { parseTelephonyWebsocket } from "@pipedog/runner/utils";
import { PlivoFrameSerializer } from "@pipedog/serializer-plivo/plivo";
import {
  FastAPIWebsocketParams,
  FastAPIWebsocketTransport,
} from "@pipedog/transport-websocket/fastapi";
import { websocketFetchHandler } from "@pipedog/transport-websocket/fetch";
import type { WebsocketConnection } from "@pipedog/transport-websocket/socket";

async function runCall(connection: WebsocketConnection): Promise<void> {
  // Plivo's "start" message carries the stream and call IDs.
  const [, callData] = await parseTelephonyWebsocket(connection);

  const serializer = new PlivoFrameSerializer({
    streamId: callData.streamId ?? "",
    callId: callData.callId,
    authId: process.env.PLIVO_AUTH_ID,
    authToken: process.env.PLIVO_AUTH_TOKEN,
  });

  const transport = new FastAPIWebsocketTransport(
    connection,
    new FastAPIWebsocketParams({
      audioInEnabled: true,
      audioOutEnabled: true,
      addWavHeader: false,
      serializer,
    }),
  );
  // ... build and run the pipeline with transport.input() and transport.output()
}

// The <Stream> in your Plivo XML points here.
const handler = websocketFetchHandler(runCall);
```

Without auto hang-up, only the stream ID is needed:

```ts
const noHangUp = new PlivoFrameSerializer({
  streamId: "stream-id",
  params: new PlivoFrameSerializer.InputParams({ autoHangUp: false }),
});
```

### Notes [#notes]

* With `autoHangUp` on (the default), the constructor throws if `callId`, `authId` or `authToken`
  is missing. The hang-up is a `DELETE` of
  `https://api.plivo.com/v1/Account/{authId}/Call/{callId}/` with Basic auth, attempted once. 204
  and 404 (already ended) are logged at debug level; other failures are logged as errors and don't
  throw.
* Outbound audio is `playAudio` with `contentType: "audio/x-mulaw"` and the Plivo sample rate.
* Set `addWavHeader: false` on the transport params.
* An invalid DTMF digit is logged as a warning and ignored.

## Runtime notes [#runtime-notes]

| Runtime            | Works | What it needs |
| ------------------ | ----- | ------------- |
| Bun                | ✅ yes | Nothing.      |
| Node.js            | ✅ yes | Nothing.      |
| Deno               | ✅ yes | Nothing.      |
| Cloudflare Workers | ✅ yes | Nothing.      |

Generated from `pipecat.runtimes` and `pipecat.compat` in `packages/serializer-plivo/package.json`: ✅ means the compat smoke test passes on that runtime.

Runs on Bun, Node, Deno and Cloudflare Workers (`fetch` for the hang-up, core's WASM resampler for
the audio). The package's golden tests replay vectors recorded from upstream: at 8 kHz the wire
output is byte-identical.
