Is there a way to play a pre recorded audio when t...
# ask-ai
m
Is there a way to play a pre recorded audio when the user picks up a call ? and then the agent will continue ? is webm supported ? how can i play the audio ?
✅ 1
t
Thanks for reaching out, Ayush. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m happy to help with this. You can play a pre-recorded audio clip automatically when a call is answered, then let the agent continue the conversation. Below I’ll cover how to do it, whether WebM is supported, and practical implementation steps. Acknowledgement • You asked: 1) Can a pre-recorded audio play when the user picks up? 2) Is WebM supported? 3) How do you play the audio? Short answer • Yes — play the pre-recorded audio on answer using the agent/session API (use
session.say()
with an audio generator that yields
rtc.AudioFrame
). • WebM is supported only when the audio inside the WebM is Opus-encoded (Opus is the codec WebRTC/LiveKit expects). Converting to WAV can simplify decoding and playback. How to implement (high level) 1. Store the file (local disk or cloud). If remote, download it on the server before playback. 2. Decode the audio file into raw frames and create an
rtc.AudioFrame
(or equivalent structure used by your SDK). You can decode WebM/Opus with ffmpeg/PyAV, or load WAV directly with
wave
for ease. 3. Use
session.say()
and pass an async generator that yields the
rtc.AudioFrame
. Set
text=None
so only the audio plays. 4. After playback, let the agent continue (call
generate_reply()
or otherwise start the agent’s normal speech-to-text/response flow). Practical notes and code-patterns • Playing audio only (no TTS): ◦ Use
session.say(text=None, audio=audio_generator())
, where
audio_generator()
yields
rtc.AudioFrame
frames. • WebM support: ◦ WebM is supported if the contained audio is Opus. If your WebM uses Opus, decode it server-side to produce frames for
rtc.AudioFrame
. If you prefer simpler handling, convert to WAV and load the WAV frames directly. • Recommended approach: ◦ For reliability and simpler decoding on the backend, convert files to WAV (if feasible). WAV is straightforward to load and convert into audio frames. • Example patterns (pseudo/Python): a. Decode file into frames (WAV example shown conceptually):
Copy code
with wave.open(str(audio_path), 'rb') as wav_file:
         frames = wav_file.readframes(wav_file.getnframes())
         sample_rate = wav_file.getframerate()
         num_channels = wav_file.getnchannels()

     audio_frame = rtc.AudioFrame(
         data=frames,
         sample_rate=sample_rate,
         num_channels=num_channels,
         samples_per_channel=wav_file.getnframes()
     )
1. Yield frames in an async generator and call `session.say()`:
Copy code
async def audio_generator():
         yield audio_frame

     await session.say(text=None, audio=audio_generator())
1. After playback, start the agent conversation:
Copy code
await session.generate_reply()
Questions to narrow your setup • Which SDK and language are you using (e.g., Python, Node, JS)? I can give a concrete code example for that environment. • Is your pre-recorded file currently WebM/Opus, WAV, or MP3? If WebM, do you know whether it’s Opus-encoded? • Where do you store the audio (local server, S3, other)? That affects whether you need to download/stream and how you decode. If you tell me your SDK/language and file format/storage, I’ll provide a short, copy-paste example that fits your stack and sample code to decode WebM (Opus) or play WAV. Sources: Playing Audio | LiveKit Documentation | Universal Egress | Agent speech and audio | LiveKit Documentation
m
Copy code
async def _duration_guard() -> None:
        await asyncio.sleep(CALL_DURATION)
        <http://logger.info|logger.info>("Duration limit reached — call_id=%s, saying goodbye", call_id)
        try:
            await asyncio.wait_for(
                session.say(
                    "Thank you so much for your time. Goodbye!",
                    allow_interruptions=False,
                ),
                timeout=10.0,
            )
        except asyncio.TimeoutError:
            logger.warning("Goodbye timed out — call_id=%s, forcing hangup", call_id)
        except Exception as exc:
            logger.warning("Goodbye error — call_id=%s: %s", call_id, exc)
        await _hangup(ctx)
        await session.aclose()

    asyncio.create_task(_duration_guard())
this is a function i wrote now this successflly cuts the call butt the session.say does not execute, i can see the logs it says duration limit reacehd but the agent continues, i am using session.generate reply for the agent to start speaking.
t
Thanks — this is clear and solvable. You’re seeing the duration guard log but the goodbye audio doesn’t play because the agent’s ongoing speech/control flow (your
session.generate_reply()
usage) is likely conflicting with
session.say()
. I’ll walk through why that happens and give a few targeted changes you can try. Quick summary of the issue I’m responding to • You call
session.say()
inside _duration_guard(), see the log, but no audio plays and the agent keeps talking. You also use
session.generate_reply()
elsewhere to start the agent speaking. Why this is happening • Only one speech operation can be active at a time. If
generate_reply()
is in progress (or the agent is still considered “speaking” or has queued output), a subsequent
session.say()
can be ignored/overridden. • If the session is closed or
session.aclose()
runs before playback finishes, audio won’t be heard. • Using
allow_interruptions=False
prevents interruptions of the new speech, but it doesn’t forcibly stop any speech already in progress unless you explicitly interrupt/cancel it first. • Awaiting
session.say()
may not guarantee playout completed — you should wait for a playout/completion handle if available. Concrete changes to try 1) Interrupt any active agent speech before calling `say()`:
Copy code
session.interrupt()   # stop current speech and clear queue
handle = session.say("Thank you so much for your time. Goodbye!", allow_interruptions=False)
await handle.wait_for_playout()  # ensure audio finishes
2) If
interrupt()
is not available in your SDK version, explicitly cancel the task that runs
generate_reply()
(or await its completion) before calling `say()`: • Ensure no
generate_reply()
call is active or queued when your duration guard runs. 3) Make sure you do NOT call
session.aclose()
until after the goodbye playout completes: • In your code you call
session.aclose()
immediately after
_hangup(ctx)
. Move that after you wait for playout. 4) Use explicit timeouts only to avoid hanging, but ensure you allow enough time for playout:
Copy code
try:
    handle = session.say("Thank you ...", allow_interruptions=False)
    await asyncio.wait_for(handle.wait_for_playout(), timeout=10.0)
except asyncio.TimeoutError:
    logger.warning("Goodbye timed out — call_id=%s, forcing hangup", call_id)
Additional checks • Confirm the session is still connected when
_duration_guard()
runs. • Confirm the SDK you’re on exposes a
SpeechHandle
or similar interface; if not, confirm how
session.say()
reports completion in your version. • If
generate_reply()
is invoked in a loop or via events, verify those loops stop when you interrupt or when the guard runs. If you want, share: • How you call
session.generate_reply()
(a short snippet of that flow). • Which SDK and version (Python / Node / other). • Whether
session.interrupt()
or
SpeechHandle.wait_for_playout()
are available in your SDK. Tell me your SDK/version and I’ll provide a concrete, copy‑paste snippet that fits your stack and ensures the goodbye plays before hangup. Sources: examples/voice_agents | livekit/livekit_composite | DeepWiki | How to detect when an agent has finished speaking | LiveKit | Agent speech and audio | LiveKit Documentation | Turns overview | LiveKit Documentation
m
Can i buy an outbound number from livekit itself ?
t
Thanks — I’m glad you asked. Short answer: LiveKit does not currently sell dedicated outbound phone numbers. What is available now • You can purchase US local or toll‑free numbers from LiveKit for inbound calling (the dashboard/CLI lets you provision these). • LiveKit supports outbound calling functionality, but LiveKit does not currently offer a product that sells outbound‑only numbers for initiating calls. How you can make outbound calls today • Use a third‑party SIP provider (for example Twilio, Telnyx, Plivo) and connect it to LiveKit via the SIP integration. That lets you originate outbound calls using numbers you rent from those providers while keeping your voice routing and agent flows in LiveKit. • If you already have a LiveKit‑provisioned inbound number, confirm whether your use case can route outbound calls through a SIP provider or your telephony setup — LiveKit’s telephony integration is built to interoperate with those providers. If you’d like I can help with next steps • Recommend SIP providers and show an example of how to wire one into LiveKit for outbound calling. • Walk through the CLI/dashboard steps to purchase a US local/toll‑free number from LiveKit (if you want an inbound number). • Help design a quick flow: “outbound via Twilio → SIP trunk → LiveKit agent”. Which of those would be most helpful right now? Sources: Introducing LiveKit Phone Numbers: zero to ringing in 60 seconds | LiveKit | LiveKit Phone Numbers | Connect voice agents to phone lines | LiveKit | Agents telephony integration | LiveKit Documentation, with interactive elements