> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pyai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Live Translation with Plivo

> Connect two human Plivo calls using bidirectional 16 kHz Audio Streams and PyAI Live Translation.

Plivo can stream linear PCM at 16 kHz, which reduces audio conversion in a
PyAI Live Translation bridge. Give each human a separate controlled call and
Audio Stream, paired to one PyAI interpretation session.

> [!NOTE]
>
> The SDK includes `PlivoInterpretationTransport` and signed stream admission,
> reusing the two-human room described in the
> [shared integration guide](/guides/live-translation-telephony.md).
> The `plivo-live-translation` example supplies a bounded browser-call host.

## Recordings and transcripts in Console

Open [Live Translation in Console](https://console.pyai.com/live-translation),
select your team's live project, and start a new conversation or call. Confirm
that everyone agrees to recording before starting. For a carrier call, the
Console explicitly shows whether the connected host supports recording.

Choose **End conversation** or **End call** and wait for saving to finish. In
**Call history**, select the call to play its recording, download the audio, or
read and download the original and translated transcripts. Other signed-in
members of the same project can open the same history. Invitation links and
upload tokens do not grant access to stored recordings.

Original audio is on the left channel and translated audio is on the right.
Browser recordings contain the microphone and the translation rendered by the
browser. Carrier recordings contain source audio accepted by the bridge and
translated audio sent to the call; a cancelled or interrupted translation can
sound different at the listener. A recording is not proof that someone heard
all sent audio. Agent replacement retains the same call record and transcript.

Earlier tests have no recoverable recording. An incomplete upload is labelled
**Incomplete**, and any successfully saved transcript remains available.
Recordings stay private; playback and download require current project access.

## Try two-person calling in Console

Open the [Plivo calling test](https://console.pyai.com/live-translation#plivo-preview)
and sign in with a verified saaslabs.co account. Console identifies the carrier
currently available for an internal rehearsal, together with its room cap and
expiry. The permanent Plivo tester is still undergoing end-to-end qualification.
When Plivo is available, select an active live project to create a Hindi ↔
English or Punjabi ↔ English room. The selected project controls operator access.
Preview calls use a shared internal test allowance and do not debit that
project's balance. This internal preview is not a customer production test.

1. Create a test room and send the customer and English-speaking agent their separate invitations.
2. Each participant signs in and selects **Join call** to grant microphone access. Participants do not need access to the operator's project. Use separate devices and headphones.
3. Watch original and translated captions in the operator's room. The bridge retains source and translation records with the call identifiers.
4. Select **Invite replacement agent** and share that invitation. The internal transfer completes when the replacement agent joins; the customer stays in the same room.
5. Select **End call** and wait for carrier cleanup confirmation. Leaving a participant page disconnects that browser's call.

Each invitation is short-lived and single-use. The Console page shows the
current preview's room cap and expiry. No production concurrency guarantee is
made by this internal preview. Keep participant links private.

Punjabi captions apply a narrow spelling correction for the weekday “Friday.”
When a caption changes, **View original recognition** shows the unmodified source.
The durable transcript preserves raw text, source offsets and correction metadata.
This display correction does not alter translation input or claim improved
recognition accuracy.

## Route browser calls through a dedicated application

Create a dedicated Plivo Voice application and separate customer, agent and
replacement-agent endpoints. Configure the application's answer and status
webhooks on your bridge. Issue short-lived browser access tokens on your server
using Plivo's token API; keep account credentials on the server.

The browser token's `app` claim selects the dedicated outgoing application.
Pass the server-returned bare application ID to `client.call()` as an opaque
destination marker. The example requires a 16–32 digit ID, outside the E.164
phone-number limit. The SDK sends this marker through its registered
`phone.plivo.com` route; direct `app.plivo.com` SIP admission is a separate flow.
Include the single-use `X-PH-JoinGrant` header from the join response. Validate
the signed webhook, expected source endpoint and exact marker, accepting only
its bare or normalized `phone.plivo.com` SIP forms before admitting the call.
The answer XML returns only a Stream, so it creates no PSTN or second SIP leg.
See [Plivo's browser call flow](https://docs.plivo.com/docs/voice/sdk/browser/overview).

## Connect each person's call

Return this XML from the customer's authenticated answer webhook. This address
is an endpoint you implement on your bridge:

```xml theme={null}
<Response>
  <Stream bidirectional="true" keepCallAlive="true"
          audioTrack="inbound" contentType="audio/x-l16;rate=16000">
    wss://bridge.example.com/plivo/media/one-time-server-token
  </Stream>
</Response>
```

Create the agent's separate call with the same stream configuration and
`leg=agent`. Validate Plivo's V3 signatures and nonce using the official SDK on
answer/status requests and the WebSocket handshake. Bind the account,
`start.callId`, stream and expected participant to server-owned room state.
Treat `extraHeaders` as metadata, not permission to join a conversation.

The example uses SDK 0.4.5's explicit `signatureProtocol: "http"` setting for
the canonical upgrade URI validated by the official helper. The media transport
remains encrypted WSS. Answer/status signatures still use HTTPS. Fix the signing
URI to your configured host and exact one-time path; do not derive it from
forwarded headers or accept alternate signatures on failure. Existing SDK
callers retain the HTTPS signing default unless they select HTTP explicitly.

Plivo also serves an older stream protocol: its start sequence is zero and the
account field can contain an internal numeric identifier. The example establishes
account ownership through the official account-token signature over the exact
WSS URL and a single-use permit bound to the signed call, participant and room.
Attachment consumes that saved admission proof once before constructing the
transport. Missing, expired, replayed or mismatched proofs close the socket.
The transport still validates the expected call, stream and audio format, and
drops audio until verification completes. Do not treat the numeric start field
as proof of account ownership.

Bidirectional streams use the inbound track. Do not select outbound or both
tracks, which can reintroduce generated speech. See
[Stream XML](https://www.plivo.com/docs/voice/xml/audio-streaming) and
[WebSocket authentication](https://docs.plivo.com/docs/voice-agents/audio-streaming/concepts/audio-streaming-guide).

## Forward 16 kHz audio

Inspect the actual `start.mediaFormat` before accepting media. Validate mono
16 kHz linear PCM and signed little-endian sample framing; do not assume that
an unexpected format is compatible.

Wrap each participant's incoming PCM in PyAI `audio.append` with the
acknowledged epoch and contiguous per-leg sequence. Use 20 ms frames where
practical: 640 bytes of PCM16 before base64 encoding. Send generated PCM only
to its current `target_leg`:

```json theme={null}
{"event":"playAudio","media":{"contentType":"audio/x-l16","sampleRate":16000,"payload":"<base64 raw PCM16>"}}
```

## Confirm playback and clearing

After an utterance's final audio, send a unique checkpoint on the listener's
stream:

```json theme={null}
{"event":"checkpoint","streamId":"target-stream-id","name":"unique-playback-boundary"}
```

Associate that checkpoint with the PyAI utterance, participant epoch, output
generation and original sample count. Require a matching `playedStream` or
legacy `played` receipt on that stream before reporting the complete utterance
as played. Each checkpoint can be consumed only once.
Correlate these receipts by exact stream and checkpoint name independently of the
media sequence; a receipt never advances the strictly ordered media cursor.

When PyAI cancels output, invalidate older checkpoints, stop local output and
send:

```json theme={null}
{"event":"clearAudio","streamId":"target-stream-id"}
```

Serialize clear requests per stream. Send a fresh, unique checkpoint immediately
after `clearAudio`. A sequenced `clearedAudio` receipt or that exact checkpoint
can complete the current clear barrier. Ignore uncorrelated legacy zero-sequence
clear receipts, old checkpoints and duplicate events. Only then acknowledge
PyAI `playback.cleared` for the current identity. A received PCM chunk, successful
send or generation EOF does not establish playback. See the
[Audio Streams protocol](https://www.plivo.com/docs/voice-agents/audio-streaming/concepts/audio-streaming-reference)
and [legacy audio-stream reference](https://old-support.plivo.com/hc/en-us/sections/32252450281369-Audio-Stream)
for these message types.

## Keep the call record and transfers together

Store Plivo call/stream IDs with PyAI's session ID, source and translated
caption ledgers, and the stable conversation ID. Your application performs the
actual Plivo transfer. Replace the interpreter participant using `leg.replace`,
then adopt both updated epochs/generations and clear both output queues before
acknowledging them.

Drop microphone frames while `input.gated` is active and resume only after
`input.resumed`. Do not buffer gated audio for later replay.

On hangup or terminal error, end the PyAI session and close the room's streams.
Before launch, test both directions, actual playback, clear/late-checkpoint
races, strict echo injection, participant replacement and normal cleanup.

For a strict echo check, inject translated output into the listener browser's
microphone during playback and its tail. Confirm those frames are dropped before
recognition and do not create reverse transcripts or translations. Keep original
captions and translated captions attributable to their current participants
through the transfer. Provider playback acknowledgments and browser audio capture
do not establish physical speaker audibility, room acoustics, human speech
quality, PSTN routing or concurrent-call capacity.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.