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.
livekit room townhall
- speaker-ja
- host
- avflow-translator-en and filtered out of its own input this job publishes
- avflow-translator-ja not in this job’s input
- avflow-translator-zh not in this job’s input
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}"