How can I disconnect remote participant after some...
# ask-ai
b
How can I disconnect remote participant after some time of inactivity? I want to prevent the user sitting in the room if they are AFK
g
It seems likely that you can handle participant disconnection after inactivity by setting a timer and using the
disconnect
method. According to LiveKit documentation, you can programmatically disconnect a participant by calling the
disconnect
method on the room object. You might set an async timer to track inactivity and trigger this method when the timer expires. Would you like more details on implementing this? Sources: - https://livekit-users.slack.com/archives/C07FVFM5NA1/p1731495431.174829 - https://livekit-users.slack.com/archives/C07FVFM5NA1/p1729704710.265049 👍 0 button 👎 0 button 💡 Hint: Mention @runllm in the thread for followups.
b
@millions-hair-24956 yeah more details on implementing this?
g
My understanding is that you can implement participant disconnection after a period of inactivity by using a combination of event listeners and timers. Here's a step-by-step breakdown: 1. Set Up Event Listeners: Use event listeners to track participant activity. You can listen for events such as
participant_connected
,
participant_disconnected
, and any other relevant events that indicate activity, like
track_published
or
track_unpublished
. 2. Track Activity: Maintain a record of the last activity time for each participant. Update this record whenever an activity event is detected. 3. Inactivity Timer: Set up a timer that periodically checks the last activity time for each participant. If the current time exceeds the last activity time by a predefined threshold (e.g., 5 minutes), consider the participant inactive. 4. Disconnect Inactive Participants: For participants deemed inactive, call the
disconnect
method on the room object to remove them from the room. This can be done using the LiveKit SDK's
disconnect
method, as described in the LiveKit documentation. Here's a basic example in Python:
Copy code
import asyncio
from datetime import datetime, timedelta

# Dictionary to track last activity time for each participant
last_activity = {}

# Function to update activity
def update_activity(participant_id):
    last_activity[participant_id] = datetime.now()

# Function to check for inactivity
async def check_inactivity(room, inactivity_threshold):
    while True:
        now = datetime.now()
        for participant_id, last_active in list(last_activity.items()):
            if now - last_active > inactivity_threshold:
                # Disconnect inactive participant
                await room.disconnect(participant_id)
                del last_activity[participant_id]
        await asyncio.sleep(60)  # Check every minute

# Example usage
inactivity_threshold = timedelta(minutes=5)
room = ...  # Assume room is initialized
asyncio.create_task(check_inactivity(room, inactivity_threshold))
This code sets up a loop that checks every minute for participants who have been inactive for more than the specified threshold and disconnects them. Would you like further clarification or assistance with a specific part of this implementation? Sources: - https://livekit-users.slack.com/archives/C07FVFM5NA1/p1731495431.174829 - LiveKit documentation 👍 0 button 👎 0 button 💡 Hint: Mention @runllm in the thread for followups.
b
@millions-hair-24956 how would you implement this in minimal assistant example https://github.com/livekit/agents/blob/main/examples/voice-pipeline-agent/minimal_assistant.py
g
To implement participant disconnection after inactivity in the minimal assistant example, you can modify the code to include a mechanism for tracking participant activity and disconnecting them after a period of inactivity. Here's how you can do it: 1. Track Participant Activity: Use event listeners to update the last activity time whenever a participant speaks or performs any action. 2. Inactivity Timer: Set up a periodic task to check for inactivity and disconnect participants who have been inactive for a specified duration. Here's a modified version of the minimal assistant example with these changes:
Copy code
import asyncio
from datetime import datetime, timedelta
from livekit import rtc
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli, llm
from livekit.agents.voice_assistant import VoiceAssistant
from livekit.plugins import deepgram, openai, silero

# Dictionary to track last activity time for each participant
last_activity = {}

# Function to update activity
def update_activity(participant_id):
    last_activity[participant_id] = datetime.now()

# Function to check for inactivity
async def check_inactivity(room, inactivity_threshold):
    while True:
        now = datetime.now()
        for participant_id, last_active in list(last_activity.items()):
            if now - last_active > inactivity_threshold:
                # Disconnect inactive participant
                await room.disconnect(participant_id)
                del last_activity[participant_id]
        await asyncio.sleep(60)  # Check every minute

async def entrypoint(ctx: JobContext):
    initial_ctx = llm.ChatContext().append(
        role="system",
        text=(
            "You are a voice assistant created by LiveKit. Your interface with users will be voice. "
            "You should use short and concise responses, avoiding unpronounceable punctuation."
        ),
    )

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

    assistant = VoiceAssistant(
        vad=silero.VAD.load(),
        stt=deepgram.STT(),
        llm=openai.LLM(),
        tts=openai.TTS(),
        chat_ctx=initial_ctx,
    )

    assistant.start(ctx.room)

    # Listen for participant activity
    @ctx.room.on("participant_connected")
    def on_participant_connected(participant: rtc.RemoteParticipant):
        update_activity(participant.identity)

    @ctx.room.on("participant_disconnected")
    def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity in last_activity:
            del last_activity[participant.identity]

    @assistant.on("user_speech_committed")
    def on_user_speech_committed(*args):
        participant = args[0]
        update_activity(participant.identity)

    # Start inactivity check
    inactivity_threshold = timedelta(minutes=5)
    asyncio.create_task(check_inactivity(ctx.room, inactivity_threshold))

    await assistant.say("Hey, how can I help you today?", allow_interruptions=True)

if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
This code sets up event listeners to update the last activity time for each participant and periodically checks for inactivity to disconnect participants who have been inactive for more than the specified threshold. Let me know if you need further assistance! Sources: - https://livekit-users.slack.com/archives/C07FY8WHGPM/p1732026501.742139 - https://livekit-users.slack.com/archives/C07FY8WHGPM/p1726233645.449819 👍 0 button 👎 0 button 💡 Hint: Mention @runllm in the thread for followups.
b
@millions-hair-24956 Hmm.. are you sure the args[0] is the participant? It seems ERROR livekit - failed to emit event user_speech_committed AttributeError: 'ChatMessage' object has no attribute 'identity'
g
It seems likely that the
user_speech_committed
event provides a
ChatMessage
object rather than a participant object. To correctly track participant activity, you should use the participant's identity from the context where the message is received. Here's how you can adjust the code: 1. Track Activity Using Participant Identity: Ensure you have access to the participant's identity when updating activity. 2. Correct Event Handling: Use the correct attributes from the
ChatMessage
or related context to update activity. Here's the revised code snippet:
Copy code
import asyncio
from datetime import datetime, timedelta
from livekit import rtc
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli, llm
from livekit.agents.voice_assistant import VoiceAssistant
from livekit.plugins import deepgram, openai, silero

# Dictionary to track last activity time for each participant
last_activity = {}

# Function to update activity
def update_activity(participant_id):
    last_activity[participant_id] = datetime.now()

# Function to check for inactivity
async def check_inactivity(room, inactivity_threshold):
    while True:
        now = datetime.now()
        for participant_id, last_active in list(last_activity.items()):
            if now - last_active > inactivity_threshold:
                # Disconnect inactive participant
                participant = room.get_participant(participant_id)
                if participant:
                    await participant.disconnect()
                del last_activity[participant_id]
        await asyncio.sleep(60)  # Check every minute

async def entrypoint(ctx: JobContext):
    initial_ctx = llm.ChatContext().append(
        role="system",
        text=(
            "You are a voice assistant created by LiveKit. Your interface with users will be voice. "
            "You should use short and concise responses, avoiding unpronounceable punctuation."
        ),
    )

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

    assistant = VoiceAssistant(
        vad=silero.VAD.load(),
        stt=deepgram.STT(),
        llm=openai.LLM(),
        tts=openai.TTS(),
        chat_ctx=initial_ctx,
    )

    assistant.start(ctx.room)

    # Listen for participant activity
    @ctx.room.on("participant_connected")
    def on_participant_connected(participant: rtc.RemoteParticipant):
        update_activity(participant.identity)

    @ctx.room.on("participant_disconnected")
    def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity in last_activity:
            del last_activity[participant.identity]

    @assistant.on("user_speech_committed")
    def on_user_speech_committed(msg: llm.ChatMessage):
        # Assuming msg has a participant identity attribute
        participant_id = msg.participant_identity
        update_activity(participant_id)

    # Start inactivity check
    inactivity_threshold = timedelta(minutes=5)
    asyncio.create_task(check_inactivity(ctx.room, inactivity_threshold))

    await assistant.say("Hey, how can I help you today?", allow_interruptions=True)

if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
This code assumes that the
ChatMessage
object or the context in which it is used provides a way to access the participant's identity. Adjust the
participant_id
extraction based on the actual attributes available in your environment. Let me know if you need further clarification! Sources: - https://docs.livekit.io/components-android/livekit-compose-components/io.livekit.android.compose.flow/-data-message/participant.html - https://docs.livekit.io/components-android/livekit-compose-components/io.livekit.android.compose.chat/-chat-message/participant.html 👍 0 button 👎 0 button 💡 Hint: Mention @runllm in the thread for followups.
b
@millions-hair-24956 no the ChageMessage doesn't have the participant identity as per https://docs.livekit.io/python/livekit/agents/llm/chat_context.html#livekit.agents.llm.chat_context.ChatMessage think harder how to implement this properly
g
It seems likely that the
ChatMessage
object does not directly provide participant identity. Instead, you can track participant activity by using the
participant_connected
and
participant_disconnected
events to manage a mapping of participant identities to their last activity timestamps. Here's a refined approach: 1. Track Activity on Connection: Update the last activity timestamp when a participant connects. 2. Use Speech Events: Use speech-related events to update activity timestamps, assuming you can access participant identity through the room context. 3. Inactivity Timer: Periodically check for inactivity and disconnect participants as needed. Here's how you can implement this:
Copy code
import asyncio
from datetime import datetime, timedelta
from livekit import rtc
from livekit.agents import AutoSubscribe, JobContext, WorkerOptions, cli, llm
from livekit.agents.voice_assistant import VoiceAssistant
from livekit.plugins import deepgram, openai, silero

# Dictionary to track last activity time for each participant
last_activity = {}

# Function to update activity
def update_activity(participant_id):
    last_activity[participant_id] = datetime.now()

# Function to check for inactivity
async def check_inactivity(room, inactivity_threshold):
    while True:
        now = datetime.now()
        for participant_id, last_active in list(last_activity.items()):
            if now - last_active > inactivity_threshold:
                # Disconnect inactive participant
                participant = room.get_participant(participant_id)
                if participant:
                    await participant.disconnect()
                del last_activity[participant_id]
        await asyncio.sleep(60)  # Check every minute

async def entrypoint(ctx: JobContext):
    initial_ctx = llm.ChatContext().append(
        role="system",
        text=(
            "You are a voice assistant created by LiveKit. Your interface with users will be voice. "
            "You should use short and concise responses, avoiding unpronounceable punctuation."
        ),
    )

    await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_ONLY)

    assistant = VoiceAssistant(
        vad=silero.VAD.load(),
        stt=deepgram.STT(),
        llm=openai.LLM(),
        tts=openai.TTS(),
        chat_ctx=initial_ctx,
    )

    assistant.start(ctx.room)

    # Listen for participant activity
    @ctx.room.on("participant_connected")
    def on_participant_connected(participant: rtc.RemoteParticipant):
        update_activity(participant.identity)

    @ctx.room.on("participant_disconnected")
    def on_participant_disconnected(participant: rtc.RemoteParticipant):
        if participant.identity in last_activity:
            del last_activity[participant.identity]

    @assistant.on("user_started_speaking")
    def on_user_started_speaking(participant: rtc.RemoteParticipant):
        update_activity(participant.identity)

    # Start inactivity check
    inactivity_threshold = timedelta(minutes=5)
    asyncio.create_task(check_inactivity(ctx.room, inactivity_threshold))

    await assistant.say("Hey, how can I help you today?", allow_interruptions=True)

if __name__ == "__main__":
    cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
This code uses the
user_started_speaking
event to update the last activity timestamp, assuming you can access the participant identity from the event context. Adjust the event handling as needed based on your specific setup. Let me know if you need further assistance! Sources: - https://docs.livekit.io/client-sdk-flutter/livekit_client/Participant/identity.html - https://docs.livekit.io/home/concepts/api-primitives/ 👍 0 button 👎 0 button 💡 Hint: Mention @runllm in the thread for followups.