← All use cases

Live interpretation channel

Publish a translated voice track back into the room for listeners to switch to.

Cross-border town halls and creator streams where each viewer picks their own language. Run one Job per language and they coexist in the same room.

audio only · no video is composed One Job per language, all in the same room. Each source excludes every translator identity, otherwise the English interpreter starts translating the Japanese one.

The pipeline

Sources

  • livekit (audio)

Nodes

  • audio_mixer
  • translate

Sinks

  • livekit (translated track)
livekit(room, audio) → audio_mixer → translate → livekit sink (translated voice + text)

What makes it work

The parts that are not obvious from the JSON — usually because the naive approach does not do what you would expect.

Mix the room floor before translating

translate is a 1:1 node and an RTC room is 1:n, so the participants have to be folded into one stream first. Wiring the source straight into translate is rejected at submit time.

Speech out, not just text

The translate node emits speech in the target language plus avflow.translateText data events, so a listener can either switch audio tracks or read along.

Exclude the translators from the source

Interpreters publish into the room they listen to. Without excludeIdentities covering every translator identity, the English interpreter starts translating the Japanese interpreter — use select to break the loop.

Platform-managed only

The translate node uses an AVFlow-managed provider. Sending provider or providerConfig is rejected at submit time.

The Job

Replace tokens, URLs, and storage credentials, then submit it. This is the same file /examples/23-live-interpretation.json serves.

{
  "name": "interpretation-en",
  "sources": [
    {
      "name": "room",
      "type": "livekit",
      "config": {
        "serverUrl": "wss://your-project.livekit.cloud",
        "token": "<subscribe-token>",
        "select": {
          "mediaTypes": [
            "audio"
          ],
          "excludeIdentities": [
            "avflow-translator-en",
            "avflow-translator-ja",
            "avflow-translator-zh"
          ]
        }
      }
    }
  ],
  "nodes": [
    {
      "name": "floor",
      "type": "audio_mixer",
      "inputs": [
        "room"
      ],
      "config": {}
    },
    {
      "name": "interpreter",
      "type": "translate",
      "inputs": [
        "floor"
      ],
      "config": {
        "targetLanguage": "en"
      }
    }
  ],
  "sinks": [
    {
      "name": "to_room",
      "type": "livekit",
      "inputs": [
        "interpreter"
      ],
      "config": {
        "serverUrl": "wss://your-project.livekit.cloud",
        "token": "<publish-token>",
        "audioTrackName": "translation-en"
      }
    }
  ],
  "policies": {
    "maxDurationSec": 14400,
    "idleTimeoutSec": 120
  }
}

Submit it

curl -X POST "https://api.avflow.dev/v1/jobs" \
  -H "Authorization: Bearer ${AVFLOW_API_KEY}" \
  -H "Content-Type: application/json" \
  -d @23-live-interpretation.json

# check status
curl "https://api.avflow.dev/v1/jobs/interpretation-en" \
  -H "Authorization: Bearer ${AVFLOW_API_KEY}"

# stop
curl -X DELETE "https://api.avflow.dev/v1/jobs/interpretation-en" \
  -H "Authorization: Bearer ${AVFLOW_API_KEY}"