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.
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)
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 } " )
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)
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"
}
})
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()
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" )
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..." )
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:
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 ( " \n Stopping..." , 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
Save the code
Save the code above to a file named audio_receiver.py
Update the meeting URL
Replace the placeholder URL with your actual Daily room URL: meeting_url = "https://your-domain.daily.co/your-room-name"
Run the application
python audio_receiver.py > output.raw
This will join the meeting and save received audio to output.raw.
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:
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.