66from dotenv import load_dotenv , find_dotenv
77
88from livekit import api , rtc
9- from db_meter import calculate_db_level , display_dual_db_meters
9+ from db_meter import calculate_db_level , display_single_db_meter
1010
1111
1212async def main () -> None :
@@ -29,46 +29,16 @@ async def main() -> None:
2929 mic = devices .open_input ()
3030 player = devices .open_output ()
3131
32- # Mixer for all remote audio streams
33- mixer = rtc .AudioMixer (sample_rate = 48000 , num_channels = 1 )
34-
35- # dB level monitoring
32+ # dB level monitoring (mic only)
3633 mic_db_queue = queue .Queue ()
37- room_db_queue = queue .Queue ()
38-
39- # Track stream bookkeeping for cleanup
40- streams_by_pub : dict [str , rtc .AudioStream ] = {}
41- streams_by_participant : dict [str , set [rtc .AudioStream ]] = {}
42-
43- # remove stream from mixer and close it
44- async def _remove_stream (
45- stream : rtc .AudioStream , participant_sid : str | None = None , pub_sid : str | None = None
46- ) -> None :
47- try :
48- mixer .remove_stream (stream )
49- except Exception :
50- pass
51- try :
52- await stream .aclose ()
53- except Exception :
54- pass
55- if participant_sid and participant_sid in streams_by_participant :
56- streams_by_participant .get (participant_sid , set ()).discard (stream )
57- if not streams_by_participant .get (participant_sid ):
58- streams_by_participant .pop (participant_sid , None )
59- if pub_sid is not None :
60- streams_by_pub .pop (pub_sid , None )
6134
6235 def on_track_subscribed (
6336 track : rtc .Track ,
6437 publication : rtc .RemoteTrackPublication ,
6538 participant : rtc .RemoteParticipant ,
6639 ):
6740 if track .kind == rtc .TrackKind .KIND_AUDIO :
68- stream = rtc .AudioStream (track , sample_rate = 48000 , num_channels = 1 )
69- streams_by_pub [publication .sid ] = stream
70- streams_by_participant .setdefault (participant .sid , set ()).add (stream )
71- mixer .add_stream (stream )
41+ player .add_track (track )
7242 logging .info ("subscribed to audio from %s" , participant .identity )
7343
7444 room .on ("track_subscribed" , on_track_subscribed )
@@ -78,37 +48,11 @@ def on_track_unsubscribed(
7848 publication : rtc .RemoteTrackPublication ,
7949 participant : rtc .RemoteParticipant ,
8050 ):
81- stream = streams_by_pub .get (publication .sid )
82- if stream is not None :
83- asyncio .create_task (_remove_stream (stream , participant .sid , publication .sid ))
84- logging .info ("unsubscribed from audio of %s" , participant .identity )
51+ asyncio .create_task (player .remove_track (track ))
52+ logging .info ("unsubscribed from audio of %s" , participant .identity )
8553
8654 room .on ("track_unsubscribed" , on_track_unsubscribed )
8755
88- def on_track_unpublished (
89- publication : rtc .RemoteTrackPublication , participant : rtc .RemoteParticipant
90- ):
91- stream = streams_by_pub .get (publication .sid )
92- if stream is not None :
93- asyncio .create_task (_remove_stream (stream , participant .sid , publication .sid ))
94- logging .info ("track unpublished: %s from %s" , publication .sid , participant .identity )
95-
96- room .on ("track_unpublished" , on_track_unpublished )
97-
98- def on_participant_disconnected (participant : rtc .RemoteParticipant ):
99- streams = list (streams_by_participant .pop (participant .sid , set ()))
100- for stream in streams :
101- # Best-effort discover publication sid
102- pub_sid = None
103- for k , v in list (streams_by_pub .items ()):
104- if v is stream :
105- pub_sid = k
106- break
107- asyncio .create_task (_remove_stream (stream , participant .sid , pub_sid ))
108- logging .info ("participant disconnected: %s" , participant .identity )
109-
110- room .on ("participant_disconnected" , on_participant_disconnected )
111-
11256 token = (
11357 api .AccessToken (api_key , api_secret )
11458 .with_identity ("local-audio" )
@@ -135,31 +79,15 @@ def on_participant_disconnected(participant: rtc.RemoteParticipant):
13579
13680 # Start dB meter display in a separate thread
13781 meter_thread = threading .Thread (
138- target = display_dual_db_meters ,
139- args = (mic_db_queue , room_db_queue , room .name ),
82+ target = display_single_db_meter ,
83+ args = (mic_db_queue ,),
84+ kwargs = {"label" : "Mic Level: " },
14085 daemon = True
14186 )
14287 meter_thread .start ()
14388
144- # Create a monitoring wrapper for the mixer that calculates dB levels
145- # while passing frames through to the player
146- async def monitored_mixer ():
147- try :
148- async for frame in mixer :
149- # Calculate dB level for room audio
150- samples = list (frame .data )
151- db_level = calculate_db_level (samples )
152- try :
153- room_db_queue .put_nowait (db_level )
154- except queue .Full :
155- pass # Drop if queue is full
156- # Yield the frame for playback
157- yield frame
158- except Exception :
159- pass
160-
161- # Start playing mixed remote audio with monitoring
162- asyncio .create_task (player .play (monitored_mixer ()))
89+ # Start playing mixed remote audio (tracks added via event handlers)
90+ await player .start ()
16391
16492 # Monitor microphone dB levels
16593 async def monitor_mic_db ():
@@ -191,7 +119,6 @@ async def monitor_mic_db():
191119 pass
192120 finally :
193121 await mic .aclose ()
194- await mixer .aclose ()
195122 await player .aclose ()
196123 try :
197124 await room .disconnect ()
0 commit comments