Skip to main content
This guide will walk you through creating a simple application that joins a Daily meeting and receives audio from other participants.

Prerequisites

Your First Application

Let’s build a simple audio receiver that joins a meeting and listens to other participants.
1

Initialize the SDK

Every Daily Python application must call Daily.init() before creating any clients or devices.
from daily import Daily, CallClient, EventHandler
import threading

# Initialize the Daily SDK
Daily.init()
You can pass optional parameters to init():
  • worker_threads: Number of worker threads (default: 2)
  • log_level: Logging level from LogLevel enum (default: LogLevel.Off)
2

Create an Event Handler

Event handlers let you respond to meeting events like participants joining or errors occurring.
class MyEventHandler(EventHandler):
    def on_participant_joined(self, participant):
        print(f"Participant joined: {participant.get('info', {}).get('userName', 'Unknown')}")
    
    def on_participant_left(self, participant, reason):
        print(f"Participant left: {participant.get('info', {}).get('userName', 'Unknown')}")
    
    def on_error(self, error):
        print(f"Error occurred: {error}")
3

Create a CallClient

The CallClient is your main interface for interacting with Daily meetings.
# Create event handler
event_handler = MyEventHandler()

# Create call client with event handler
client = CallClient(event_handler=event_handler)
4

Configure Subscription Settings

Set up subscription profiles to control what media you receive from other participants.
# Subscribe to audio only, not video
client.update_subscription_profiles({
    "base": {
        "camera": "unsubscribed",
        "microphone": "subscribed"
    }
})
5

Join a Meeting

Join a Daily meeting using a room URL. The join operation is asynchronous.
import threading

# Event to signal when join is complete
join_event = threading.Event()

def on_joined(data, error):
    if error:
        print(f"Failed to join: {error}")
    else:
        print("Successfully joined meeting!")
    join_event.set()

# Join the meeting
meeting_url = "https://your-domain.daily.co/room-name"
client.join(meeting_url, completion=on_joined)

# Wait for join to complete
join_event.wait()
6

Create a Virtual Speaker Device

To receive audio from the meeting, create a virtual speaker device.
SAMPLE_RATE = 16000
NUM_CHANNELS = 1

# Create virtual speaker
speaker = Daily.create_speaker_device(
    "my-speaker",
    sample_rate=SAMPLE_RATE,
    channels=NUM_CHANNELS
)

# Select it as the output device
Daily.select_speaker_device("my-speaker")
7

Read Audio Frames

Read audio data from the speaker device in a loop.
import sys

try:
    while True:
        # Read 100ms of audio frames
        buffer = speaker.read_frames(int(SAMPLE_RATE / 10))
        if len(buffer) > 0:
            # Process audio data (e.g., write to file)
            sys.stdout.buffer.write(buffer)
except KeyboardInterrupt:
    print("Stopping...")
8

Clean Up

Always clean up resources when done.
# Leave the meeting
client.leave()

# Release the client
client.release()

Complete Example

Here’s the complete audio receiver application:
audio_receiver.py
import sys
import threading
from daily import Daily, CallClient, EventHandler

SAMPLE_RATE = 16000
NUM_CHANNELS = 1

class MyEventHandler(EventHandler):
    def on_participant_joined(self, participant):
        print(f"Participant joined: {participant.get('info', {}).get('userName', 'Unknown')}")
    
    def on_participant_left(self, participant, reason):
        print(f"Participant left: {participant.get('info', {}).get('userName', 'Unknown')}")
    
    def on_error(self, error):
        print(f"Error: {error}")

class AudioReceiver:
    def __init__(self):
        self.speaker = Daily.create_speaker_device(
            "my-speaker",
            sample_rate=SAMPLE_RATE,
            channels=NUM_CHANNELS
        )
        Daily.select_speaker_device("my-speaker")
        
        self.client = CallClient(event_handler=MyEventHandler())
        self.client.update_subscription_profiles({
            "base": {"camera": "unsubscribed", "microphone": "subscribed"}
        })
        
        self.join_event = threading.Event()
        self.running = False
    
    def on_joined(self, data, error):
        if error:
            print(f"Failed to join: {error}")
        else:
            print("Successfully joined meeting!")
            self.running = True
        self.join_event.set()
    
    def join(self, meeting_url):
        self.client.join(meeting_url, completion=self.on_joined)
        self.join_event.wait()
    
    def receive_audio(self):
        if not self.running:
            return
        
        try:
            while self.running:
                buffer = self.speaker.read_frames(int(SAMPLE_RATE / 10))
                if len(buffer) > 0:
                    sys.stdout.buffer.write(buffer)
        except KeyboardInterrupt:
            print("\nStopping...", file=sys.stderr)
    
    def leave(self):
        self.running = False
        self.client.leave()
        self.client.release()

if __name__ == "__main__":
    Daily.init()
    
    receiver = AudioReceiver()
    
    # Replace with your meeting URL
    meeting_url = "https://your-domain.daily.co/room-name"
    
    receiver.join(meeting_url)
    receiver.receive_audio()
    receiver.leave()

Running the Example

1

Save the code

Save the code above to a file named audio_receiver.py
2

Update the meeting URL

Replace the placeholder URL with your actual Daily room URL:
meeting_url = "https://your-domain.daily.co/your-room-name"
3

Run the application

python audio_receiver.py > output.raw
This will join the meeting and save received audio to output.raw.
4

Stop the application

Press Ctrl+C to stop recording and leave the meeting.

Sending Audio

To send audio into a meeting, create a virtual microphone device instead:
audio_sender.py
import threading
from daily import Daily, CallClient

SAMPLE_RATE = 16000
NUM_CHANNELS = 1

class AudioSender:
    def __init__(self):
        # Create virtual microphone
        self.microphone = Daily.create_microphone_device(
            "my-mic",
            sample_rate=SAMPLE_RATE,
            channels=NUM_CHANNELS
        )
        
        self.client = CallClient()
        self.client.update_subscription_profiles({
            "base": {"camera": "unsubscribed", "microphone": "unsubscribed"}
        })
        
        self.join_event = threading.Event()
    
    def on_joined(self, data, error):
        if error:
            print(f"Failed to join: {error}")
        else:
            print("Joined! Sending audio...")
        self.join_event.set()
    
    def join(self, meeting_url):
        self.client.join(
            meeting_url,
            client_settings={
                "inputs": {
                    "camera": False,
                    "microphone": {
                        "isEnabled": True,
                        "settings": {"deviceId": "my-mic"}
                    }
                }
            },
            completion=self.on_joined
        )
        self.join_event.wait()
    
    def send_audio(self, audio_data):
        # Send audio frames to the meeting
        self.microphone.write_frames(audio_data)
    
    def leave(self):
        self.client.leave()
        self.client.release()

if __name__ == "__main__":
    Daily.init()
    
    sender = AudioSender()
    sender.join("https://your-domain.daily.co/room-name")
    
    # Send audio data (example: silence)
    import time
    silence = b'\x00' * (SAMPLE_RATE // 10 * NUM_CHANNELS * 2)  # 100ms of silence
    
    try:
        while True:
            sender.send_audio(silence)
            time.sleep(0.1)  # Send every 100ms
    except KeyboardInterrupt:
        sender.leave()

Next Steps

Now that you’ve built your first Daily Python application, explore more advanced features:

Event Handling

Learn about all available event callbacks

Audio Processing

Process and manipulate audio streams

Video Support

Send and receive video frames

Code Examples

Explore real-world examples in the demos directory

Common Patterns

Using Meeting Tokens

For private rooms, pass a meeting token:
client.join(
    meeting_url="https://your-domain.daily.co/private-room",
    meeting_token="your-meeting-token-here",
    completion=on_joined
)

Configuring Client Settings

Customize your participant settings when joining:
client.join(
    meeting_url,
    client_settings={
        "inputs": {
            "camera": False,
            "microphone": {"isEnabled": True}
        },
        "publishing": {
            "camera": False,
            "microphone": {"isEnabled": True}
        }
    },
    completion=on_joined
)

Error Handling

Always implement proper error handling:
class RobustEventHandler(EventHandler):
    def on_error(self, error):
        print(f"Daily error: {error}", file=sys.stderr)
        # Implement retry logic or cleanup
    
    def on_participant_left(self, participant, reason):
        if reason == "error":
            print(f"Participant left due to error: {participant}")
For production applications, consider implementing exponential backoff for reconnection attempts and comprehensive logging.

Build docs developers (and LLMs) love