careful-iron-56445
09/17/2025, 12:03 PMtall-airline-16756
09/17/2025, 12:03 PMtall-airline-16756
09/17/2025, 12:04 PMcareful-iron-56445
09/17/2025, 12:05 PMcareful-iron-56445
09/17/2025, 12:06 PMtall-airline-16756
09/17/2025, 12:07 PMagent_state_changed event. This event is perfect for catching when the agent stops speaking and starts listening, which is exactly when you'd want to play your MP3.
2. Play MP3 Using AudioSource: You'll want to decode your MP3 into PCM audio frames (48kHz, mono). Using rtc.AudioSource and rtc.LocalAudioTrack, you can stream the audio directly into the room. Just make sure your MP3 is converted to the right format — tools like pydub or ffmpeg can help with this.
3. Publish and Unpublish the Track: When playing, publish the audio track to the room, and once it’s done, unpublish it to keep things running smoothly and free up resources.
Here’s a friendly example to illustrate how you might do this:
import asyncio
from livekit import rtc
from pydub import AudioSegment
async def play_mp3(file_path: str, room: rtc.Room):
audio = AudioSegment.from_mp3(file_path)
audio = audio.set_frame_rate(48000).set_channels(1)
audio_bytes = audio.raw_data
frame_length_ms = 20
bytes_per_frame = 48000 * 2 * 1 * frame_length_ms // 1000
source = rtc.AudioSource(48000, 1)
track = rtc.LocalAudioTrack.create_audio_track("background-music", source)
await room.local_participant.publish_track(track)
for i in range(0, len(audio_bytes), bytes_per_frame):
chunk = audio_bytes[i:i + bytes_per_frame]
frame = rtc.AudioFrame(
data=chunk,
sample_rate=48000,
num_channels=1,
samples_per_channel=len(chunk) // 2
)
await source.capture_frame(frame)
await asyncio.sleep(frame_length_ms / 1000)
await room.local_participant.unpublish_track(track)
To tie it all together, you can hook this function up to your state listener so the MP3 plays right when the agent switches to listening:
@agent_session.on("agent_state_changed")
async def on_agent_state_changed(ev):
if ev.old_state == "speaking" and ev.new_state == "listening":
asyncio.create_task(play_mp3("path/to/music.mp3", agent_session.room))
If you run into any questions or want to dive deeper into any part of this, just let me know — I’m here to help!
Sources: Events and error handling | LiveKit Docs | examples/primitives/echo-agent.pycareful-iron-56445
09/17/2025, 12:15 PMtall-airline-16756
09/17/2025, 12:16 PMqueue_size_ms parameter when creating the AudioSource. For example: `source = AudioSource(sample_rate, num_channels, queue_size_ms=2000)`This can help keep things running smoothly without dropping frames.
4. Final Chunk Padding: If the last chunk of audio data is smaller than expected, padding it with zeros can prevent timing hiccups during playback.
5. Playback Timing: Adding a little delay between sending frames helps maintain proper timing. For instance: `await asyncio.sleep(0.02)`This delay should match the frame duration (like 20ms for 48kHz audio).
6. Error Handling: Including detailed logging is a great way to catch and understand any issues. For example: `logging.error(f"Error playing audio file: {e}")`You might also want to log audio data size, frame size, and playback progress to keep track.
7. Resource Cleanup: Don’t forget to unpublish the audio track and close the audio source after playback to free up resources. For example: await room.local_participant.unpublish_track(audio_track)
Tackling these points should really boost the reliability and compatibility of your audio playback. If you have any questions or want to dive deeper into any of these, just let me know—I’m here to help!
Sources: Resolving "InvalidState - failed to capture frame" errors in AudioSource | agent_extensions/agent_extensions/utils/wav_player.py | livekit-rtc/livekit/rtc/track.py | Getting Help | livekit/livekit_composite | DeepWiki | Community & Support | livekit/livekit_composite | DeepWikicareful-iron-56445
09/17/2025, 12:20 PMtall-airline-16756
09/17/2025, 12:20 PMpydub or ffmpeg can make this pretty straightforward.
Here’s a friendly example of how you can resample your audio to 48kHz using `pydub`:
from pydub import AudioSegment
audio = AudioSegment.from_file("path/to/audio.mp3")
audio = audio.set_frame_rate(48000).set_channels(1).set_sample_width(2) # Convert to 48kHz, mono, 16-bit PCM
Once your audio is set up like this, you’ll be all set to publish it to the room. If you have any questions or need a hand with the next steps, just let me know—I’m here to help!careful-iron-56445
09/17/2025, 12:23 PMtall-airline-16756
09/17/2025, 12:23 PMRoomOutputOptions. To keep things smooth and consistent, the system standardizes audio output at 24kHz, which is the supported sample rate for RTC streaming and client playback.
If your project needs 16kHz output, no worries—you can resample the audio externally once it’s received on the client side. I’m here to help if you’d like some guidance on how to set that up, just let me know!
Sources: livekit-plugins/livekit-plugins-clova/livekit/plugins/clova/constants.py | agents/src/voice/agent_session.ts | Speech & Audio Processing | livekit/livekit_composite | DeepWikicareful-iron-56445
09/17/2025, 12:30 PMtall-airline-16756
09/17/2025, 12:31 PMcareful-iron-56445
09/17/2025, 12:32 PMtall-airline-16756
09/17/2025, 12:32 PMaudio_track variable you're passing to unpublish_track? It should be a valid string representing the track SID. Also, are you explicitly passing the stop_on_unpublish parameter? If so, I'd love to know what value you're using. I'm here to help you get this sorted out!
Sources: livekit-protocol/livekit/protocol/py.typed | packages/livekit-rtc/src/participant.tscareful-iron-56445
09/17/2025, 12:33 PMtall-airline-16756
09/17/2025, 12:34 PMunpublish_track method expecting a track SID (which is a string identifier for the track) instead of the LocalAudioTrack object itself. No worries though! To fix this, you just need to grab the track SID from your audio_track object before calling unpublish_track.
Here’s a simple way to do that:
# Retrieve the track SID from the published track
track_sid = audio_track.sid
# Use the track SID to unpublish the track
await room.local_participant.unpublish_track(track_sid)
If you find that audio_track.sid isn’t accessible right away, it might be because the track hasn’t been published to the room yet—remember, the SID gets assigned during publishing. Feel free to reach out if you want me to walk you through this or if you have any other questions!careful-iron-56445
09/17/2025, 12:48 PMtall-airline-16756
09/17/2025, 12:48 PMautoSubscribe option, which is turned on by default when you connect to a room.
If you’re not hearing all the tracks, here are a few things to check:
1. Make sure autoSubscribe isn’t set to false when connecting to the room.
2. Confirm that the other participants have properly published their remote tracks.
3. Check if your browser allows audio playback. Some browsers block autoplay unless you interact with the page first. In that case, try calling Room.startAudio() inside a user gesture handler to get the audio going.
If you have any questions or need a hand with this, please don’t hesitate to reach out. I’m here to help!
Sources: Rooms, participants, and tracks | LiveKit Docs | Subscribing to tracks | LiveKit Docs | README.md