In my room three participants participant1 and age...
# ask-ai
l
In my room three participants participant1 and agent and web_participant2. I want to stop the agent participant to publish audio to the room when web_participant2 joins
t
Thanks for reaching out, Rajadurai. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I understand you want the agent to stop publishing its audio whenever
web_participant2
joins the room. I’m happy to help you implement that. Quick summary of the approach: • The agent itself must stop/unpublish its audio track when it detects
web_participant2
has joined. This requires adding a handler in the agent code for the room’s participant-join event and unpublishing (or muting/stopping) the agent’s audio track when the joining participant’s identity matches
web_participant2
. Before I give a concrete snippet tailored to your environment, a couple quick clarifying questions: 1. Which agent SDK are you using: Node.js or Python (or another)? 2. Is the agent running as a LiveKit Agent (using the agent runtime) or as a regular client participant? 3. Do you want the audio permanently unpublished while
web_participant2
is present, or should it be re-published automatically after they leave? If you’re using Node.js or Python, here are concise examples you can adapt. Node.js (conceptual) 1. Listen for the participant-connected event on the room. 2. If the joining participant’s identity === 'web_participant2', call
unpublishTrack()
(or `stop()`/`mute()` depending on how you manage the track) for the agent’s audio. Python (conceptual) 1. Register a
participant_connected
handler on the room. 2. When identity is 'web_participant2', find the agent’s published audio track and call
unpublish_track()
(or stop/mute it). Implementation notes and options: • Unpublish vs mute/stop: ◦ Unpublishing removes the track publication from the room; other participants will see the publication removed. ◦ Muting or stopping the local audio track keeps the publication but stops audio data; other participants still see the publication (useful if you want to re-enable quickly). • Re-enable behavior: ◦ If you want audio to resume automatically after
web_participant2
leaves, listen for the participant-left event and re-publish (or unmute) the audio when they depart. • Identity matching: ◦ Use the participant’s
identity
field (exact match to
web_participant2
) to trigger the action. If you tell me: • which SDK (Node.js or Python), • whether you prefer unpublish or mute behavior, • and whether you want automatic re-enable on leave, I’ll provide a ready-to-use code snippet tailored to your setup. Sources: Agent session | LiveKit Documentation | client-sdk-flutter | livekit/livekit_composite | DeepWiki
l
I'm using python framework
t
Great — thanks for confirming you’re using the Python framework. Below is a concise, practical way to stop the agent’s audio when
web_participant2
joins, plus options for mute vs unpublish and how to automatically re-enable audio when they leave. Summary of the approach • Add handlers on the agent’s room for participant-connected and participant-disconnected events. • When a participant with identity
web_participant2
connects, either unpublish the agent’s audio track or mute/stop it. • When that participant disconnects, re-publish or unmute the audio if you want audio to resume automatically. Python example (agent runtime, conceptual) • This uses the room event handlers and the agent’s local participant to control its audio track.
Copy code
# Example: agent entrypoint using LiveKit Python SDK / agent runtime
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
    # Helper: find local audio publication or track
    def get_local_audio_publication():
        for pub in ctx.room.local_participant.publications:
            if pub.kind == "audio":
                return pub
        return None

    async def stop_audio_by_unpublish():
        pub = get_local_audio_publication()
        if pub and pub.track:
            # Unpublish the audio track so others no longer receive a publication
            await ctx.room.local_participant.unpublish_track(pub.track)
            <http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")

    async def mute_local_audio():
        pub = get_local_audio_publication()
        if pub and pub.track:
            # Stop or mute the track depending on your track API
            # If using track.stop(): stops capture; if mute API available, use that.
            try:
                await pub.track.stop()
            except Exception:
                # Fallback: set enabled/disabled if supported
                if hasattr(pub.track, "enabled"):
                    pub.track.enabled = False
            <http://ctx.logger.info|ctx.logger.info>("Agent audio muted/stopped (web_participant2 joined)")

    async def restore_audio_by_publish():
        # Re-publish logic depends on how you originally created the track
        # Example: recreate an audio track and publish it
        audio_track = await create_local_audio_track_somehow()
        await ctx.room.local_participant.publish_track(audio_track)
        <http://ctx.logger.info|ctx.logger.info>("Agent audio re-published (web_participant2 left)")

    async def unmute_local_audio():
        pub = get_local_audio_publication()
        if pub and pub.track:
            try:
                # If track.stop() was used earlier, you might need to recreate the track.
                if hasattr(pub.track, "enabled"):
                    pub.track.enabled = True
            except Exception:
                pass
            <http://ctx.logger.info|ctx.logger.info>("Agent audio unmuted (web_participant2 left)")

    @ctx.room.on("participant_connected")
    async def on_participant_connected(participant: RemoteParticipant):
        if participant.identity == "web_participant2":
            # Choose one of these actions depending on desired behavior:
            # 1) Unpublish (removes the publication)
            await stop_audio_by_unpublish()

            # OR 2) Mute/stop the track but keep publication
            # await mute_local_audio()

    @ctx.room.on("participant_disconnected")
    async def on_participant_disconnected(participant: RemoteParticipant):
        if participant.identity == "web_participant2":
            # If you want audio to come back when they leave, restore or unmute:
            # await restore_audio_by_publish()
            # OR
            # await unmute_local_audio()
            pass

    # Connect the agent to the room (example flags; adapt to your setup)
    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
    # Keep your agent work running...
Notes and implementation details • Unpublish vs mute: ◦ Unpublish removes the track publication for others to see. Use when you want the publication gone. ◦ Muting/stopping keeps the publication but stops sending audio data. Use when you want to resume quickly and keep signal that an audio track exists. • Re-creating audio tracks: ◦ If you use
track.stop()
, many SDKs require creating a new local track to re-publish. Muting via
track.enabled = False
(if supported) lets you toggle without re-creating. • Finding the audio publication: ◦ The snippet uses
local_participant.publications
to locate the audio publication. If your SDK exposes a helper like
get_track_publication(Track.Kind.Audio)
, you can use that. • Threading / lifecycle: ◦ Ensure the handlers are registered before connecting or immediately after connecting so you won’t miss the join event. • Identities: ◦ Match exact
participant.identity
string
web_participant2
. If identities vary, use whatever identifier your app sets. If you’d like, I can: • Provide a version that explicitly recreates and re-publishes the audio track after stop, • Show code that mutes/unmutes using a specific track API if you share how you create the local audio track, • Or adapt this to a concrete AgentSession or background task pattern you’re using. Which behavior do you prefer — Unpublish or Mute/Stop? Also, how are you creating the agent’s local audio track now (so I can give the exact publish/unpublish code)?
l
AttributeError: 'LocalParticipant' object has no attribute 'publications'
t
Thanks — that error explains why your previous code failed. You tried to access
publications
, but the LiveKit Python SDK exposes the agent’s published tracks via
published_tracks
(a dict of track SID →
LocalTrackPublication
), not
publications
. Below is a corrected, minimal Python example that: • Listens for participants joining, • Unpublishes the agent’s audio when
web_participant2
joins, • Optionally re-publishes (or unmutes) when they leave. Adjust the reconnect/publish logic to match how you create your local audio track.
Copy code
import asyncio
from livekit import rtc  # adjust import to your setup
from livekit.rtc import TrackKind

@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
    async def unpublish_audio():
        # published_tracks is a dict of sid -> LocalTrackPublication
        for pub in ctx.room.local_participant.published_tracks.values():
            # publication.track may be None if already unpublished
            if pub.track and pub.track.kind == TrackKind.KIND_AUDIO:
                await ctx.room.local_participant.unpublish_track(pub.track)
                <http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")
                return

    async def republish_audio(create_audio_track_fn):
        # create_audio_track_fn: your function to create a new LocalAudioTrack
        audio_track = await create_audio_track_fn()
        await ctx.room.local_participant.publish_track(audio_track)
        <http://ctx.logger.info|ctx.logger.info>("Agent audio re-published (web_participant2 left)")

    @ctx.room.on("participant_connected")
    async def on_participant_connected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            # Choose action: unpublish or mute. Example: unpublish
            await unpublish_audio()

            # If you prefer mute instead of unpublish, implement muting here
            # (muting may require toggling a property on the track or stopping capture)

    @ctx.room.on("participant_disconnected")
    async def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            # If you want audio to resume automatically, republish or unmute here.
            # Example placeholder: await republish_audio(create_local_audio_track)
            pass

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)

    # keep agent running...
Important notes: • Use
ctx.room.local_participant.published_tracks
(not
.publications
). •
unpublish_track()
expects a
LocalTrack
object (use
pub.track
from
published_tracks
). • If you call
track.stop()
to mute, you may need to recreate the track to republish later. Muting by toggling
track.enabled
(if available) avoids re-creation. • Ensure handlers are registered before or immediately after
connect()
so you don't miss the join event. If you share: • the LiveKit Python SDK version you’re using, and • how you currently create the agent’s local audio track (e.g., a helper function name), I’ll give a ready-to-paste snippet that includes the exact republish workflow or a mute/unmute implementation. Which behavior do you prefer — Unpublish or Mute? Sources: client-sdk-unity | livekit/livekit_composite | DeepWiki | Building an Automated IVR Menu Caller | LiveKit Documentation | server-sdk-js | livekit/livekit_composite | DeepWiki
l
for pub in ctx.room.local_participant.published_tracks.values(): ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ AttributeError: 'LocalParticipant' object has no attribute 'published_tracks'. Did you mean: 'publish_track'?
t
Thanks — I can see why that AttributeError is frustrating. You tried several property names that don’t exist on
LocalParticipant
in your SDK version. Let’s fix that and give you a working pattern. What to use • The correct property on
LocalParticipant
to inspect published tracks is
track_publications
(a mapping of track SID → TrackPublication). • Each publication may have a
.track
(the LocalTrack) and a
.kind
(audio/video). Use those to find the audio publication and call
unpublish_track()
with the
LocalTrack
object. Minimal working pattern 1. Register the participant-connected handler. 2. When
participant.identity == "web_participant2"
, iterate
ctx.room.local_participant.track_publications.values()
to find the audio publication. 3. Call
await ctx.room.local_participant.unpublish_track(pub.track)
. Example (adapt to your imports / agent runtime):
Copy code
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
    async def unpublish_audio():
        for pub in ctx.room.local_participant.track_publications.values():
            # pub.track can be None if already unpublished
            if pub.track and pub.kind == rtc.TrackKind.KIND_AUDIO:
                await ctx.room.local_participant.unpublish_track(pub.track)
                <http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")
                return

    @ctx.room.on("participant_connected")
    async def on_participant_connected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            await unpublish_audio()

    @ctx.room.on("participant_disconnected")
    async def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            # If you want audio to resume automatically, implement republish/unmute here.
            pass

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
    # keep agent running...
Notes and choices • Unpublish vs mute/stop: ◦ Unpublish removes the publication; other participants no longer see it. ◦ Stopping/muting the track (e.g.,
track.stop()
or toggling
track.enabled
) keeps the publication but stops audio data. If you call
track.stop()
, you may need to recreate the LocalTrack to republish later. • If you prefer mute/unmute instead of unpublish, tell me how you create the audio track (function name or code) and I’ll show the exact mute/unmute flow. • If you still get AttributeError, please share: ◦ the LiveKit Python SDK version you’re using, and ◦ the exact code you use to create/publish the local audio track. If you want, I can prepare a copy-pasteable snippet that also handles automatic re-publishing when
web_participant2
leaves — tell me whether you want unpublish or mute behavior and the SDK version. Sources: livekit/livekit_composite | DeepWiki
l
Traceback (most recent call last): File "/home/azureuser/dasboard-agent/entrypoint.py", line 127, in remove_participant await unpublish_audio(ctx) File "/home/azureuser/dasboard-agent/src/utils/room_utils.py", line 76, in unpublish_audio await ctx.room.local_participant.unpublish_track(pub.track) File "/home/azureuser/dasboard-agent/.venv/lib/python3.12/site-packages/livekit/rtc/participant.py", line 720, in unpublish_track req.unpublish_track.track_sid = track_sid ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TypeError: bad argument type for built-in operation
t
Thanks for the traceback — that made the problem clear. The TypeError happens because you passed the wrong argument type to
unpublish_track()
. In this LiveKit Python SDK,
unpublish_track()
expects either a track SID string or the
LocalTrackPublication
object — not the
LocalTrack
instance (i.e., not
pub.track
). What to change • Find the audio publication via
ctx.room.local_participant.track_publications
. • Call
unpublish_track(pub)
or
unpublish_track(pub.track_sid)
— do NOT pass
pub.track
. Working example to replace your current unpublish logic:
Copy code
async def unpublish_audio(ctx):
    lp = ctx.room.local_participant

    for pub in lp.track_publications.values():
        # pub.track can be None if already unpublished
        if pub.kind == rtc.TrackKind.KIND_AUDIO:
            # Correct: pass the publication or the SID, not pub.track
            await lp.unpublish_track(pub)           # OR: await lp.unpublish_track(pub.track_sid)
            <http://ctx.logger.info|ctx.logger.info>(f"Unpublished audio track: {pub.track_sid}")
            return

    <http://ctx.logger.info|ctx.logger.info>("No audio track found to unpublish")
How to wire it into the join handler:
Copy code
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
    if participant.identity == "web_participant2":
        await unpublish_audio(ctx)
Additional notes • If you previously used
track.stop()
to mute, you may need to recreate the local track to re-publish later. Unpublishing removes the publication; muting keeps the publication but stops audio data. • If you want automatic resume when
web_participant2
leaves, add a
participant_disconnected
handler that re-publishes (or unmutes) using your track-creation routine. • If you still get errors, please share: ◦ The LiveKit Python SDK version (
pip show livekit
), and ◦ The code you use to create/publish the local audio track. I can then provide a copy‑paste snippet that includes safe re-publish logic or a mute/unmute flow tailored to your SDK version. Which behavior do you prefer — unpublish or mute? Sources: client-sdk-android | livekit/livekit_composite | DeepWiki | client-sdk-unity | livekit/livekit_composite | DeepWiki | An open source stack for real-time multimodal AI
l
uv pip list |grep "livekit" livekit 1.0.23 livekit-agents 1.3.10 livekit-api 1.1.0 livekit-blingfire 1.1.0 livekit-plugins-azure 1.3.10 livekit-plugins-noise-cancellation 0.2.5 livekit-plugins-openai 1.3.10 livekit-plugins-silero 1.3.10 livekit-plugins-turn-detector 1.3.10 livekit-protocol 1.1.1
t
Thanks — the package list is helpful. Your versions are recent and compatible with the patterns we've discussed, so the fix is to call
unpublish_track()
with the publication or the publication's SID (not the
LocalTrack
object). Below is a concise, ready-to-use example tailored to your SDK versions that: • Listens for
web_participant2
joining, • Unpublishes the agent's audio by passing the
LocalTrackPublication
(or its
track_sid
), • Optionally re-publishes when they leave (two approaches shown: unpublish/recreate or mute/unmute). Paste this into your agent entrypoint (adapt imports and your audio-track creation function):
Copy code
import asyncio
from livekit import rtc
from livekit.rtc import TrackKind, AutoSubscribe

# Example: replace with how you create a LocalAudioTrack in your app
async def create_local_audio_track():
    # Example placeholder — replace with your actual creation code
    # e.g., return await rtc.create_local_audio_track(...) or your helper
    raise NotImplementedError("Provide your audio track creation code here")

@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
    async def unpublish_audio():
        lp = ctx.room.local_participant
        for pub in lp.track_publications.values():
            # pub.track can be None if already unpublished
            if pub.kind == TrackKind.KIND_AUDIO:
                # Correct: pass the publication or the SID (not pub.track)
                await lp.unpublish_track(pub)           # OR: await lp.unpublish_track(pub.track_sid)
                <http://ctx.logger.info|ctx.logger.info>(f"Unpublished audio track: {pub.track_sid}")
                return
        <http://ctx.logger.info|ctx.logger.info>("No audio track found to unpublish")

    async def republish_audio():
        lp = ctx.room.local_participant
        # Recreate and publish a new local audio track (depends on your creation function)
        audio_track = await create_local_audio_track()
        await lp.publish_track(audio_track)
        <http://ctx.logger.info|ctx.logger.info>("Re-published agent audio")

    # Alternative: mute/unmute approach (keeps publication visible)
    async def mute_audio_keep_publication():
        lp = ctx.room.local_participant
        for pub in lp.track_publications.values():
            if pub.kind == TrackKind.KIND_AUDIO and pub.track:
                # If the track has an 'enabled' flag, toggle it; otherwise stop() and recreate later
                if hasattr(pub.track, "enabled"):
                    pub.track.enabled = False
                    <http://ctx.logger.info|ctx.logger.info>("Muted audio (kept publication)")
                else:
                    await pub.track.stop()
                    <http://ctx.logger.info|ctx.logger.info>("Stopped audio track (must recreate to republish)")
                return

    async def unmute_audio_keep_publication():
        lp = ctx.room.local_participant
        for pub in lp.track_publications.values():
            if pub.kind == TrackKind.KIND_AUDIO and pub.track:
                if hasattr(pub.track, "enabled"):
                    pub.track.enabled = True
                    <http://ctx.logger.info|ctx.logger.info>("Unmuted audio")
                else:
                    <http://ctx.logger.info|ctx.logger.info>("Track was stopped; recreate to republish")
                return

    @ctx.room.on("participant_connected")
    async def on_participant_connected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            # Choose one action:
            await unpublish_audio()               # removes publication
            # OR: await mute_audio_keep_publication()  # keep publication but stop audio

    @ctx.room.on("participant_disconnected")
    async def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity == "web_participant2":
            # If you used unpublish and want to resume:
            # await republish_audio()
            # If you used mute:
            # await unmute_audio_keep_publication()
            pass

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)

    # Keep the agent running (your existing loop/work)
Checklist to avoid the previous errors • Use
lp.track_publications
to iterate publications. • Pass a publication (
pub
) or
pub.track_sid
into
unpublish_track()
— do not pass
pub.track
. • If you call
track.stop()
, expect to recreate the track to republish later. • If you prefer quick toggle without recreating, use
pub.track.enabled = False/True
if that attribute exists for your LocalTrack. Next steps • Try the code above (replace
create_local_audio_track()
with your actual audio-track creation). • Tell me which behavior you want: fully remove the publication (unpublish) or keep the publication but mute (mute/stop). I can then provide a copy-paste-ready
create_local_audio_track()
example for your environment or a full mute/unmute flow.