### Install Stream Python AI SDK with uv Source: https://getstream.io/video/docs/python-ai/basics/quickstart Installs the Stream Python AI SDK, including the 'webrtc' dependency for audio/video streaming, using the 'uv' package manager. It also installs the Stream OpenAI plugin. ```sh uv add "getstream[webrtc]" --prerelease=allow uv add getstream-plugins-openai>=0.1.0 ``` -------------------------------- ### Initialize Stream Client from Environment Variables Source: https://getstream.io/video/docs/python-ai/basics/quickstart Loads environment variables using 'python-dotenv' and then initializes the Stream client. This client is essential for interacting with Stream services and requires API credentials to be set. ```python from dotenv import load_dotenv from getstream import Stream # Load environment variables load_dotenv() # Initialize Stream client from environment variables client = Stream.from_env() ``` -------------------------------- ### Create Users and Generate Tokens with Stream SDK Source: https://getstream.io/video/docs/python-ai/basics/quickstart Demonstrates creating new users and generating authentication tokens for them using the Stream client. This is necessary before users can join calls or interact within the Stream platform. ```python from getstream.models import UserRequest from uuid import uuid4 # Generates a new user ID and creates a new user user_id = f"user-{uuid4()}" client.upsert_users(UserRequest(id=user_id, name="My User")) # We create a token we'll use later to join the call user_token = client.create_token(user_id, expiration=3600) # Generate a user ID for the OpenAI bot we'll add bot_user_id = f"openai-realtime-speech-to-speech-bot-{uuid4()}" client.upsert_users(UserRequest(id=bot_user_id, name="OpenAI Realtime Speech to Speech Bot")) ``` -------------------------------- ### Open Stream Call in Web Browser Source: https://getstream.io/video/docs/python-ai/basics/quickstart Constructs a URL to join a Stream call and opens it in the default web browser. This requires the base URL for the example application to be set as an environment variable. ```python import webbrowser from urllib.parse import urlencode import os base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" ``` -------------------------------- ### Install Dependencies with uv Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Installs the necessary Stream SDK, AI plugins (Silero, Deepgram, OpenAI, ElevenLabs), and dotenv for environment variable management using the 'uv' package manager. ```bash uv add "getstream[webrtc]" --prerelease=allow uv add getstream-plugins-silero>=0.1.0 uv add getstream-plugins-deepgram>=0.1.1 uv add getstream-plugins-openai>=0.1.0 uv add getstream-plugins-elevenlabs>=0.1.0 uv add python-dotenv>=1.1.1 ``` -------------------------------- ### Initialize OpenAI Speech-to-Speech Bot (Python) Source: https://getstream.io/video/docs/python-ai/basics/quickstart Initializes the OpenAIRealtime plugin for speech-to-speech conversations. Allows configuration of the OpenAI model, custom instructions for the bot, and a default voice. The API key is fetched from environment variables by default. ```python from getstream.plugins.openai.sts import OpenAIRealtime sts_bot = OpenAIRealtime( model="gpt-4o-realtime-preview", instructions="You are a friendly assistant; reply in a concise manner.", voice="alloy", ) ``` -------------------------------- ### Install SDK and STT Plugin using uv Source: https://getstream.io/video/docs/python-ai/guides/add-audio-moderation Installs the necessary Stream SDK and Deepgram STT plugin using the 'uv' package manager. Ensure you have 'getstream' version 2.3.0a4 or higher and 'getstream-plugins-deepgram' version 0.1.1 or higher. ```bash uv add getstream>=2.3.0a4 getstream-plugins-deepgram>=0.1.1 ``` -------------------------------- ### Connect to Call and Listen to Events (Python) Source: https://getstream.io/video/docs/python-ai/reference/events Example of joining a video call and setting up listeners for participant events, audio data, and connection state changes using the Stream Video Python SDK. This code requires the `getstream` library and assumes a `call` object and `user_id` are available. ```python from getstream.video import rtc # Connect to a call async with await rtc.join(call, user_id) as connection: # Listen for participant events @connection.on("participant_joined") async def on_participant_joined(participant): print(f"Participant {participant.user_id} joined") # Listen for audio data @connection.on("audio") async def on_audio(pcm_data, user): print(f"Received audio from {user}") # Listen for connection state changes @connection.on("connection.state_changed") async def on_connection_state_changed(event): print(f"Connection state: {event['old']} -> {event['new']}") # Wait for the connection to end await connection.wait() ``` -------------------------------- ### Run the Python Application Source: https://getstream.io/video/docs/python-ai/guides/add-audio-moderation This command executes the Python application using `uvicorn`. Ensure `main.py` contains the necessary setup for the GetStream client and the event handlers. ```bash uv run main.py ``` -------------------------------- ### Connect and Interact with OpenAI Bot in Call (Python) Source: https://getstream.io/video/docs/python-ai/basics/quickstart Connects the initialized OpenAI bot to a Stream video call and sends a message from the user. Uses an async context manager for the connection and handles potential exceptions. Cleans up by deleting users in the 'finally' block. ```python # Assuming 'call', 'agent_user_id', 'client', 'user_id' are defined try: async with await sts_bot.connect(call, agent_user_id=bot_user_id) as connection: await sts_bot.send_user_message("Give a very short greeting to the user.") except Exception as e: print(f"An error occurred: {e}") finally: client.delete_users([user_id, bot_user_id]) ``` -------------------------------- ### Set Up Environment Variables for Stream and OpenAI Source: https://getstream.io/video/docs/python-ai/basics/quickstart Defines and configures environment variables required for the Stream Python AI SDK and OpenAI integration. This includes API keys, secrets, and base URLs, typically stored in a .env file. ```sh STREAM_API_KEY=your-stream-api-key STREAM_API_SECRET=your-stream-api-secret STREAM_BASE_URL=https://video.stream-io-api.com/ OPENAI_API_KEY=sk-your-openai-api-key ``` -------------------------------- ### Start Composite Call Recording (Python) Source: https://getstream.io/video/docs/python-ai/guides/recording-calls Starts composite audio recording, combining audio from all participants into a single file. This is simpler for playback and sharing. Requires specifying the output directory. ```python recording_params = { "recording_types": [RecordingType.COMPOSITE], "output_dir": "recordings/composite", } recorder_connection.start_recording(**recording_params) ``` -------------------------------- ### Create a Stream Video Call Source: https://getstream.io/video/docs/python-ai/basics/quickstart Creates a new video call instance within the Stream platform. It generates a unique call ID and uses the client to signal the backend to create the call, specifying the creator of the call. ```python from uuid import uuid4 # Create a call with a new generated ID call_id = str(uuid4()) call = client.video.call("default", call_id) call.get_or_create(data={"created_by_id": bot_user_id}) ``` -------------------------------- ### Open Browser to Stream Video Call (Python) Source: https://getstream.io/video/docs/python-ai/basics/quickstart Opens the default web browser to a generated Stream video call URL. It constructs the URL with API key, user token, and skip lobby parameter. Handles potential exceptions during browser opening and provides a fallback manual URL. ```python from urllib.parse import urlencode # Assuming client and base_url are already defined # client.api_key, user_token, base_url params = {"api_key": client.api_key, "token": user_token, "skip_lobby": "true"} url = f"{base_url}{call_id}?{urlencode(params)}" try: webbrowser.open(url) except Exception as e: print(f"Failed to open browser: {e}") print(f"Please manually open this URL: {url}") ``` -------------------------------- ### Add Project Dependencies - Bash Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Installs necessary Python packages for the project, including Stream components, Deepgram and ElevenLabs plugins, fastmcp, OpenAI, httpx, and python-dotenv. Uses the 'uv add' command for package management. ```bash uv add \ getstream[webrtc]>=2.3.0a4 \ getstream-plugins-deepgram>=0.1.1 \ getstream-plugins-elevenlabs>=0.1.0 \ fastmcp>=2.10.1 \ openai>=1.93.0 \ httpx>=0.28.1 \ python-dotenv>=1.1.1 ``` -------------------------------- ### Recording Manager Events Source: https://getstream.io/video/docs/python-ai/reference/events Track the lifecycle of recordings, including when they start and stop, with detailed information about recording types and users. ```APIDOC ## Recording Manager Events ### Description Events related to the recording system, providing comprehensive tracking for both per-user and composite recordings. ### Method N/A (Event Listeners) ### Endpoint N/A (Internal SDK Events) ### Events - **`recording_started`**: Emitted when recording begins. Contains recording types, user IDs, and the output directory. - **`recording_stopped`**: Emitted when recording ends. Contains recording types, user IDs, and the duration. - **`user_recording_started`**: Emitted when recording starts for a specific user. Contains the user ID, track type, and filename. - **`user_recording_stopped`**: Emitted when recording stops for a specific user. Contains the user ID, track type, filename, and duration. ``` -------------------------------- ### Connect Recorder Bot to a Call (Python) Source: https://getstream.io/video/docs/python-ai/guides/recording-calls Connects the pre-initialized recorder bot to an ongoing call. This is a prerequisite for starting any recording operation. The connection is established asynchronously. ```python async with await rtc.join(call, recorder_user_id) as recorder_connection: # Implement call recording - look at the next sections ``` -------------------------------- ### Configure Moderation Policy Source: https://getstream.io/video/docs/python-ai/guides/add-audio-moderation Sets up a moderation policy configuration in Stream using the Moderation API. This example creates a policy with the key 'custom:python-ai-test' that flags text identified as 'INSULT'. ```python client = Stream.from_env() client.moderation.upsert_config( key="custom:python-ai-test", ai_text_config={ "rules": [{"label": "INSULT", "action": "flag"}], }, ) ``` -------------------------------- ### Start Single-Track Call Recording (Python) Source: https://getstream.io/video/docs/python-ai/guides/recording-calls Initiates single-track audio recording for a specific participant during a call. This method saves each participant's audio separately. Requires specifying the output directory and the user ID to filter. ```python recording_params = { "recording_types": [RecordingType.TRACK], "output_dir": "recordings/tracks", "user_id_filter": "user_id_to_record", } recorder_connection.start_recording(**recording_params) ``` -------------------------------- ### Set Environment Variables - Dotenv Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Configures environment variables required for the application, including Stream API keys, Deepgram, ElevenLabs, and OpenAI API keys, as well as an example base URL. This is typically stored in a '.env' file. ```dotenv STREAM_API_KEY=your_stream_api_key STREAM_API_SECRET=your_stream_api_secret EXAMPLE_BASE_URL=https://pronto.getstream.io DEEPGRAM_API_KEY=your_deepgram_api_key ELEVENLABS_API_KEY=your_elevenlabs_api_key OPENAI_API_KEY=your_openai_api_key ``` -------------------------------- ### Reconnection Manager Events Source: https://getstream.io/video/docs/python-ai/reference/events Get notified about the success or failure of reconnection attempts with details on the outcome. ```APIDOC ## Reconnection Manager Events ### Description Events triggered by the Reconnection Manager, indicating the status of reconnection attempts. ### Method N/A (Event Listeners) ### Endpoint N/A (Internal SDK Events) ### Events - **`reconnection_failed`**: Emitted when reconnection attempts fail. Contains the reason for failure (e.g., timeout, error). - **`reconnection_success`**: Emitted when a reconnection attempt is successful. Contains the strategy used and the duration of the reconnection process. ``` -------------------------------- ### Initialize Logger and Stream Client Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Configures logging and initializes the Stream client using environment variables. It loads .env files for configuration. ```python logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") load_dotenv() CLIENT = Stream.from_env() ``` -------------------------------- ### Create Project Folder - Bash Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Creates a new directory for the project and navigates into it using the 'mkdir' and 'cd' commands. ```bash mkdir mcp-bot && cd mcp-bot ``` -------------------------------- ### Create MCP Server - Python Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Initializes an MCP server using the `fastmcp` library and defines a `get_forecast` tool that fetches weather data using `httpx`. This server will be run to handle MCP requests. ```python # server.py from fastmcp import FastMCP import httpx mcp = FastMCP("Stream MCP Server") # Define a tool that can be used by your MCP Client @mcp.tool() def get_forecast(city: str) -> str: """Return today's weather for . """ data = httpx.get(f"https://wttr.in/{city}?format=%C+%t").text return data.strip() if __name__ == "__main__": mcp.run() ``` -------------------------------- ### Join Call and Send Welcome Message Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Connects the bot to a Stream video call and adds the audio track for speaking. It then sends an initial welcome message to the participants using the configured Text-to-Speech plugin. ```python from getstream.video.rtc.track_util import PcmData import time from typing import Any async with await rtc.join(call, bot_id) as connection: # Add the audio track to the call so the bot can speak await connection.add_tracks(audio=audio) # Send a welcome message await tts.send("Welcome, my friend. How's it going today?") ``` -------------------------------- ### Initialize Stream Client and Load Environment Variables Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Loads environment variables from a .env file using python-dotenv and initializes the Stream client. This makes API keys and secrets available for Stream SDK operations. ```python from dotenv import load_dotenv from getstream import Stream # Load the contents of the .env file load_dotenv() # Create a Stream client using the env vars that were loaded client = Stream.from_env() ``` -------------------------------- ### Main Function to Orchestrate Bot Execution Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling The main asynchronous function that sets up the call, creates users, generates tokens, opens the browser, and runs the bot. It ensures cleanup by deleting users. ```python async def main(): call_id = str(uuid.uuid4()) human_id = f"user-{uuid.uuid4()}" bot_id = f"mcp-bot-{uuid.uuid4()}" _create_user(human_id, "Human") _create_user(bot_id, "MCP Bot") token = CLIENT.create_token(human_id, expiration=3600) call = CLIENT.video.call("default", call_id) call.get_or_create(data={"created_by_id": bot_id}) _open_browser(call_id, token) try: await _run_bot(call, bot_id) finally: CLIENT.delete_users([human_id, bot_id]) if __name__ == "__main__": asyncio.run(main()) ``` -------------------------------- ### Define Utility Methods for User and Call Management Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Provides helper functions to create users via the Stream client and to generate a URL for joining a call. The URL includes necessary parameters like API key and token. ```python def _create_user(user_id: str, name: str): CLIENT.upsert_users(UserRequest(id=user_id, name=name)) def _open_browser(call_id: str, token: str): base = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": CLIENT.api_key, "token": token, "skip_lobby": "true"} url = f"{base}{call_id}?{urlencode(params)}" webbrowser.open(url) ``` -------------------------------- ### Initialize Recorder Bot for Call Recording (Python) Source: https://getstream.io/video/docs/python-ai/guides/recording-calls Initializes the Stream client and creates a recorder bot user. This is the first step before connecting the bot to a call for recording purposes. It requires the 'getstream' library and environment variables for authentication. ```python from uuid import uuid4 from getstream.models import UserRequest from getstream.stream import Stream # Load environment variables load_dotenv() # Initialize Stream client client = Stream.from_env() # ...add your call creation and other initialisations here # Create recorder bot recorder_user_id = f"bot-recorder-{str(uuid4())[:8]}" client.upsert_users(UserRequest(id=recorder_user_id, name="Recorder Bot")) ``` -------------------------------- ### Initialize AI Plugins for Video Call Bot Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Initializes the necessary AI plugins for the video call bot, including Voice Activity Detection (VAD), Speech-to-Text (STT), Text-to-Speech (TTS), and an OpenAI client. It also sets up an audio stream track and connects the TTS output to this track. ```python import os from openai import OpenAI from getstream.plugins.silero import SileroVAD from getstream.plugins.deepgram import DeepgramSTT from getstream.plugins.elevenlabs import ElevenLabsTTS from getstream.video.rtc import audio_track # Initialize the plugin classes audio = audio_track.AudioStreamTrack(framerate=16000) vad = SileroVAD() stt = DeepgramSTT() tts = ElevenLabsTTS(voice_id="VR6AewLTigWG4xSOukaG") # Using a default ElevenLabs voice code available to all users openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) # Connect TTS output to the audio track tts.set_output_track(audio) ``` -------------------------------- ### Process Incoming Audio with VAD Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Sets up an event listener that triggers when audio is received by the Python SDK. It passes the incoming audio data to the Silero VAD plugin for speech detection. ```python @connection.on("audio") async def on_audio(pcm: PcmData, user): # Process incoming audio through VAD await vad.process_audio(pcm, user) ``` -------------------------------- ### Create Stream Users and a Video Call Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Defines a function to create Stream users and then proceeds to create a user for the developer and a bot user. It also establishes a video call instance, associating it with the bot user. ```python import uuid from getstream.models import UserRequest # This function creates a Stream user def create_user(client: Stream, id: str, name: str) -> None: """Create a user with a unique Stream ID.""" from getstream.models import UserRequest user_request = UserRequest(id=id, name=name) client.upsert_users(user_request) # Create a user for you to join the call user_id = f"example-user-{str(uuid.uuid4())}" create_user(client, user_id, "Me") user_token = client.create_token(user_id, expiration=3600) # Allows you to authenticate yourself and join the call # Create a bot user that will join the call bot_id = f"llm-audio-bot-{str(uuid.uuid4())}" create_user(client, bot_id, "LLM Bot") # Create the call call_id = str(uuid.uuid4()) call = client.video.call("default", call_id) call.get_or_create(data={"created_by_id": bot_id}) ``` -------------------------------- ### LLM Agent with Tool Integration - Python Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Defines an LLM assistant that can interact with defined tools. It sets up the OpenAI API key, a system prompt for the assistant's behavior, and a `chat_with_tools` function to handle conversation flow, tool calls, and responses. ```python # agent.py import json, re, os, logging, openai, fastmcp openai.api_key = os.environ["OPENAI_API_KEY"] logging.basicConfig(level=logging.INFO) # The system prompt that will be passed in to the LLM SYSTEM = """ You are a helpful assistant. When the user needs real-world weather data, call the get_forecast tool. If you call a tool, use tool_name["arg":"value"] on a single line. You can chain up to 3 tool calls. """ def _chat(messages): resp = openai.chat.completions.create(model="gpt-4o", messages=messages) return resp.choices[0].message.content async def chat_with_tools(prompt: str, client: fastmcp.Client) -> str: tools = await client.list_tools() tool_names = {t.name for t in tools} history = [ {"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}, ] while True: reply = _chat(history) m = re.match(r"^(\w+)\[(.*)\]$", reply.strip()) if not (m and m.group(1) in tool_names): return reply name, args_json = m.groups() args = json.loads(f"{{{args_json}}}") result = await client.call_tool(name, args) history.append({"role": "assistant", "content": str(result.data)}) ``` -------------------------------- ### Peer Connection Events Source: https://getstream.io/video/docs/python-ai/reference/events Track audio data reception and media track lifecycle changes within the WebRTC peer connection. ```APIDOC ## Peer Connection Events ### Description Events related to the WebRTC peer connection, including audio data and media track management. ### Method N/A (Event Listeners) ### Endpoint N/A (Internal SDK Events) ### Events - **`audio`**: Emitted when audio PCM data is received from a track. Contains PCM data and user metadata. - **`track_added`**: Emitted when a new media track is added to the connection. Contains track and user metadata. ``` -------------------------------- ### Import Bot Logic Components - Python Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Imports necessary modules and classes for the main bot logic, including environment variable handling, Stream SDK components for video and RTC, Deepgram STT, ElevenLabs TTS, the agent module for LLM interaction, and the fastmcp library. ```python import os, uuid, asyncio, logging, webbrowser from urllib.parse import urlencode from dotenv import load_dotenv from getstream.stream import Stream from getstream.models import UserRequest from getstream.video import rtc from getstream.video.rtc import audio_track from getstream.video.rtc.track_util import PcmData from getstream.plugins.deepgram.stt import DeepgramSTT from getstream.plugins.elevenlabs.tts import ElevenLabsTTS from agent import chat_with_tools import fastmcp ``` -------------------------------- ### Query Review Queue for Moderated Content (Python) Source: https://getstream.io/video/docs/python-ai/guides/add-audio-moderation This Python code snippet demonstrates how to query the moderation review queue using the GetStream client. It retrieves flagged audio transcripts, their recommended actions, and associated metadata. It requires the `getstream` library and an initialized `client` object. ```python from getstream.models import QueryReviewQueueResponse, ReviewQueueItemResponse # Assuming 'client' is an initialized GetStream client object response: QueryReviewQueueResponse = client.moderation.query_review_queue().data item: ReviewQueueItemResponse for item in response.items: print("--------------------------------") print(f"Flagged audio transcript with ID: {item.id}") print(f"Recommended action: {item.recommended_action}") if item.recommended_action != "keep": print(f"Transcript: {item.moderation_payload.texts[0]}") print(f"Created at: {item.created_at.isoformat()}") ``` -------------------------------- ### Implement Bot Logic for Real-time Call Interaction Source: https://getstream.io/video/docs/python-ai/guides/add-mcp-client-function-calling Sets up the core bot functionality within a real-time call. It integrates Speech-to-Text (STT), Text-to-Speech (TTS), and a chat client for processing messages and generating replies. ```python async def _run_bot(call, bot_id): stt = DeepgramSTT() tts = ElevenLabsTTS() track = audio_track.AudioStreamTrack(framerate=16000) tts.set_output_track(track) async with await rtc.join(call, bot_id) as conn: await conn.add_tracks(audio=track) async with fastmcp.Client("server.py") as mcp_client: @conn.on("audio") async def on_audio(pcm: PcmData, user): await stt.process_audio(pcm, user) @stt.on("transcript") async def on_transcript(text, user, meta): if not meta.get("is_final", False): return answer = await chat_with_tools(text, mcp_client) if answer: await tts.send(answer) await conn.wait() ``` -------------------------------- ### Initialize Stream and Deepgram Clients Source: https://getstream.io/video/docs/python-ai/guides/add-audio-moderation Initializes the Stream client using environment variables for API keys and secrets. It also initializes the Deepgram Speech-to-Text (STT) client, which relies on the DEEPGRAM_API_KEY from the environment. ```python from dotenv import load_dotenv import os, uuid from getstream.stream import Stream from getstream.video import rtc from getstream.plugins.deepgram import DeepgramSTT load_dotenv() client = Stream.from_env() # initialises with STREAM_API_KEY / SECRET stt = DeepgramSTT() # uses DEEPGRAM_API_KEY from env ``` -------------------------------- ### Open Stream Video Call in Browser Source: https://getstream.io/video/docs/python-ai/tutorials/build-an-ai-conversation-pipeline Opens the Stream video call in the default web browser using a generated URL. This is useful for testing and visualizing the call. It requires environment variables for the base URL and uses an API key and token for authentication. ```python from urllib.parse import urlencode import webbrowser, os base_url = f"{os.getenv('EXAMPLE_BASE_URL')}/join/" params = {"api_key": client.api_key, "token": user_token, "skip_lobby": "true"} url = f"{base_url}{call_id}?{urlencode(params)}" print(f"Opening browser to: {url}") webbrowser.open(url) ``` -------------------------------- ### Connection Manager Events Source: https://getstream.io/video/docs/python-ai/reference/events Listen to events related to the connection state and forwarded lower-level events, including SFU events, from the ConnectionManager. ```APIDOC ## Connection Manager Events ### Description The `ConnectionManager` class is the central point for listening to most SDK and SFU events. It aggregates events from various components and forwards them, providing a unified interface for real-time updates. ### Method N/A (Event Listeners) ### Endpoint N/A (Internal SDK Events) ### Events - **`connection.state_changed`**: Emitted when the connection state changes. Contains `old` and `new` state values. - **Forwarded Events**: Events emitted by lower-level SDK components and SFU events are automatically forwarded by the `ConnectionManager`. ```