Skip to main content

why2_chat/network/voice/client/
mod.rs

1/*
2This is part of WHY2
3Copyright (C) 2022-2026 Václav Šmejkal
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19//MODULES
20pub mod device;
21pub mod sfx;
22
23use std::
24{
25    thread,
26    net::{ UdpSocket, TcpStream },
27    collections::{ HashMap, VecDeque },
28    time::
29    {
30        SystemTime,
31        Duration,
32        UNIX_EPOCH,
33    },
34    sync::
35    {
36        Arc,
37        Mutex,
38        LazyLock,
39        mpsc::Sender,
40        atomic::{ AtomicUsize, Ordering },
41    },
42};
43
44use cpal::
45{
46    Host,
47    Stream,
48    Device,
49    StreamConfig,
50    SupportedStreamConfig,
51    SupportedStreamConfigRange,
52    traits::
53    {
54        DeviceTrait,
55        HostTrait,
56        StreamTrait,
57    },
58};
59
60use audiopus::
61{
62    Channels,
63    SampleRate,
64    Application,
65    TryFrom,
66    coder::{ Encoder, Decoder },
67};
68
69use ringbuf::
70{
71    HeapRb,
72    HeapCons,
73    HeapProd,
74    traits::
75    {
76        Split,
77        Producer,
78        Consumer,
79    },
80};
81
82use nnnoiseless::DenoiseState;
83
84use gag::Gag;
85
86use crate::
87{
88    config,
89    command::{ self, Command },
90    options as chat_options,
91    network::
92    {
93        client::{ ClientEvent, VoiceUser },
94        voice::
95        {
96            consts,
97            self,
98            options,
99            VoiceCode,
100            VoicePacket,
101            client::sfx::SoundEffect,
102        },
103    },
104};
105
106//STRUCTS
107struct LocalStream
108{
109    _input: Stream,
110    _output: Stream,
111}
112
113struct StreamGuard
114{
115    generation: usize,
116}
117
118struct RemoteStream
119{
120    consumer: HeapCons<f32>,   //RINGBUFFER READER
121    resample_pos: f32,         //POSITION IN BETWEEN SAMPLES
122    current_sample: f32,       //CURRENT SAMPLE FOR INTERPOLATION
123    next_sample: f32,          //NEXT SAMPLE FOR INTERPOLATION
124    activity_hold: usize,      //ACTIVITY TIMER
125    display_hold: usize,       //ACTIVITY WINDOW TIMER
126    username: String,          //USERNAME
127    latencies: VecDeque<u128>, //HISTORY OF LATENCIES
128    avg_latency: u128,         //AVERAGE LATENCY TO DISPLAY
129}
130
131pub struct PeerData
132{
133    decoder: Decoder,        //DECODER
134    producer: HeapProd<f32>, //RINGBUFFER WRITER
135}
136
137//GLOBAL VARIABLES
138static LOCAL_STREAMS: LazyLock<Mutex<Option<LocalStream>>> = LazyLock::new(|| Mutex::new(None));
139static CONSUMERS: LazyLock<Mutex<HashMap<usize, (RemoteStream, PeerData)>>> = LazyLock::new(|| Mutex::new(HashMap::new())); //OTHER CLIENTS
140
141static LOCAL_DISPLAY_HOLD: AtomicUsize = AtomicUsize::new(0);
142static AUDIO_GENERATION: AtomicUsize = AtomicUsize::new(0);
143
144//IMPLEMENTATIONS
145impl Drop for StreamGuard
146{
147    //CLEAR STREAMS
148    fn drop(&mut self)
149    {
150        if AUDIO_GENERATION.load(Ordering::Relaxed) == self.generation
151        {
152            if let Ok(mut streams) = LOCAL_STREAMS.lock()
153            {
154                *streams = None;
155            }
156        }
157    }
158}
159
160//PRIVATE
161fn find_devices(host: &Host) -> Option<(Device, Device)> //FIND CONFIGURED/DEFAULT DEVICE
162{
163    //GET DEVICES FROM CONFIG
164    let input_device = config::read_config::<String>("input_device");
165    let output_device = config::read_config::<String>("output_device");
166
167    //GET INPUT DEVICE (OR DEFAULT IF EMPTY)
168    let input_device = if !input_device.is_empty()
169    {
170        host.input_devices().ok()?.find(|d| d.description().is_ok_and(|desc| desc.to_string() == input_device))
171    } else { host.default_input_device() };
172
173    //GET OUTPUT DEVICE (OR DEFAULT IF EMPTY)
174    let output_device = if !output_device.is_empty()
175    {
176        host.output_devices().ok()?.find(|d| d.description().is_ok_and(|desc| desc.to_string() == output_device))
177    } else { host.default_output_device() };
178
179    Some((input_device?, output_device?))
180}
181
182fn configure_device(supported_configs: impl Iterator<Item = SupportedStreamConfigRange>, default_config: SupportedStreamConfig) -> StreamConfig
183{
184    supported_configs
185        .filter(|c| c.min_sample_rate() <= consts::SAMPLE_RATE && c.max_sample_rate() >= consts::SAMPLE_RATE)
186        .next()
187        .map(|c| c.with_sample_rate(consts::SAMPLE_RATE))
188        .unwrap_or(default_config)
189        .into()
190}
191
192fn transmit_audio(encoder: &Encoder, frame: &[f32], buffer: &mut [u8], id: usize, socket: &UdpSocket)
193{
194    //ENCODE (IGNORE ERRORS)
195    if let Ok(len) = encoder.encode_float(&frame, buffer)
196    {
197        //TRANSMIT
198        voice::send(socket, VoicePacket
199        {
200            voice: Some(buffer[..len].to_vec()),
201            id: Some(id),
202
203            ..Default::default()
204        }, &chat_options::get_keys().unwrap()).unwrap();
205    }
206}
207
208//PUBLIC
209pub fn listen_server_voice //SERVER -> CLIENT
210(
211    id: usize,
212    username: String,
213    tx: Sender<ClientEvent>,
214    write_stream: Arc<Mutex<TcpStream>>
215)
216{
217    //RESET SEQs
218    options::set_seq(0);
219    options::set_server_seq(0);
220
221    //DUPLICATE STREAM GUARDS
222    let current_generation = AUDIO_GENERATION.fetch_add(1, Ordering::Relaxed) + 1;
223    let _guard = StreamGuard { generation: current_generation };
224
225    //CONNECT
226    let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").expect("Binding UDP failed"));
227    socket.connect(chat_options::get_server_address()).expect("Connecting to server UDP failed");
228
229    //SET SOCKET TIMEOUT
230    socket.set_read_timeout(Some(Duration::from_millis(200))).expect("Setting socket timeout failed");
231
232    //INIT AUDIO HOST
233    let host = cpal::default_host();
234
235    //SUPPRESS STDERR (AVOID ALSA ERRORS)
236    let stderr_gag = Gag::stderr().unwrap();
237
238    //FIND INPUT/OUTPUT DEVICE
239    let (input_device, output_device) = match find_devices(&host)
240    {
241        Some((input, output)) => (input, output), //FOUND
242        _ => //NOT FOUND
243        {
244            //LEAVE VOICE
245            command::send_command_code(&mut write_stream.lock().unwrap(), &Command::Voice, &None);
246            return;
247        }
248    };
249
250    //DISABLE SUPPRESSION
251    drop(stderr_gag);
252
253    //SEND HELLO PACKET
254    voice::send(&socket, VoicePacket
255    {
256        id: Some(id),
257        ..Default::default()
258    }, &chat_options::get_keys().unwrap()).unwrap();
259
260    //CONFIGURE CPAL INPUT
261    let input_config = configure_device(input_device.supported_input_configs().unwrap(), input_device.default_input_config().unwrap());
262
263    //CONFIGURE CPAL OUTPUT
264    let output_config = configure_device(output_device.supported_output_configs().unwrap(), output_device.default_output_config().unwrap());
265
266    //PREPARE OPUS ENCODER
267    let opus_encoder = Encoder::new
268    (
269        <SampleRate as TryFrom<i32>>::try_from(consts::SAMPLE_RATE as i32).unwrap(),
270        Channels::Mono,
271        Application::Voip
272    ).unwrap();
273
274    //INPUT BUFFERS
275    let mut input_accum: Vec<f32> = Vec::with_capacity(consts::FRAME_SIZE * 2);
276    let mut encoded_buffer = [0u8; 1500]; //ALLOCATE BUFFER TO STANDARD MTU
277
278    //INPUT RESAMPLING
279    let input_channels = input_config.channels as usize;
280    let input_source_rate = input_config.sample_rate as f32;
281    let input_target_rate = consts::SAMPLE_RATE as f32;
282
283    //INPUT INTERPOLATION
284    let input_resample_step = input_source_rate / input_target_rate;
285    let mut input_resample_pos = 0.;
286
287    //VAD
288    let gate_open = Arc::new(Mutex::new(false)); //NOISE GATE
289    let preroll_buffer = Arc::new(Mutex::new(VecDeque::<Vec<f32>>::with_capacity(3))); //PRE-ROLL BUFFER
290    let hold_frames_remaining = Arc::new(Mutex::new(0usize)); //HOLD TIME
291
292    //NOISE REDUCTION
293    let mut denoiser = DenoiseState::new();
294    let mut denoise_buffer = [0.0f32; consts::SAMPLE_RATE as usize / 100];
295
296    //CONFIGURE INPUT STREAM
297    let send_socket = socket.clone();
298    let input_stream = input_device.build_input_stream(&input_config, move |data: &[f32], _: &_|
299    {
300        //CHECK FOR MUTING
301        if chat_options::is_muted(None)
302        {
303            LOCAL_DISPLAY_HOLD.store(0, Ordering::Relaxed); //CLEAR VAD WINDOW
304            input_accum.clear(); //REMOVE BLOB REMAINING IN BUFFER
305
306            //CLOSE GATE
307            if let Ok(mut gate) = gate_open.lock()
308            {
309                *gate = false;
310            }
311
312            //CLEAR PREROLL
313            if let Ok(mut preroll) = preroll_buffer.lock()
314            {
315                preroll.clear();
316            }
317
318            return;
319        }
320
321        //CHECK GENERATION
322        if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
323
324        let frames_in_buffer = data.len() / input_channels;
325
326        let current_hold = LOCAL_DISPLAY_HOLD.load(Ordering::Relaxed);
327        if current_hold > 0
328        {
329             LOCAL_DISPLAY_HOLD.store(current_hold.saturating_sub(frames_in_buffer), Ordering::Relaxed);
330        }
331
332        //MONO DOWNMIX CLOSURE
333        let get_mono_sample = |index: usize| -> f32
334        {
335            if index >= frames_in_buffer { return 0. }
336
337            let mut sum = 0.;
338            for c in 0..input_channels
339            {
340                sum += data[index * input_channels + c];
341            }
342
343            sum / input_channels as f32
344        };
345
346        //RESAMPLE LOOP
347        while input_resample_pos < (frames_in_buffer as f32) - 1.
348        {
349            let idx = input_resample_pos.floor() as usize;
350            let frac = input_resample_pos - idx as f32;
351
352            let s0 = get_mono_sample(idx);
353            let s1 = get_mono_sample(idx + 1);
354
355            let interpolated = s0 + (s1 - s0) * frac;
356            input_accum.push(interpolated);
357
358            input_resample_pos += input_resample_step;
359        }
360
361        //ADJUST POSITION FOR NEXT BUFFER
362        input_resample_pos -= frames_in_buffer as f32;
363
364        //PROCESS
365        while input_accum.len() >= consts::FRAME_SIZE
366        {
367            let mut frame: Vec<f32> = input_accum.drain(0..consts::FRAME_SIZE).collect();
368
369            //NOISE REDUCTION
370            for chunk in frame.chunks_mut(consts::SAMPLE_RATE as usize / 100)
371            {
372                if chunk.len() == consts::SAMPLE_RATE as usize / 100
373                {
374                    //SCALE UP
375                    for sample in chunk.iter_mut()
376                    {
377                        *sample *= 32767.;
378                    }
379
380                    //PROCESS NOISE
381                    denoiser.process_frame(&mut denoise_buffer, chunk);
382
383                    //SCALE DOWN & COPY
384                    for (i, sample) in denoise_buffer.iter().enumerate()
385                    {
386                        chunk[i] = sample / 32767.;
387                    }
388                }
389            }
390
391            //VAD
392            let rms = (frame.iter().map(|&x| x * x).sum::<f32>() / frame.len() as f32 + 1e-10).sqrt(); //RMS CALCULATION (+ SMALL BIAS)
393            let mut gate = gate_open.lock().unwrap();
394            let mut preroll = preroll_buffer.lock().unwrap();
395            let mut hold_frames = hold_frames_remaining.lock().unwrap();
396
397            //HYSTERESIS
398            if !*gate && rms > consts::TRESHOLD_OPEN
399            {
400                *gate = true; //SPEAKING
401
402                //SEND STORED FRAMES
403                for old_frame in preroll.iter()
404                {
405                    transmit_audio(&opus_encoder, old_frame, &mut encoded_buffer, id, &send_socket);
406                }
407
408                preroll.clear();
409                *hold_frames = consts::HOLD_FRAMES;
410            } else if *gate && rms < consts::TRESHOLD_CLOSE
411            {
412                if *hold_frames > 0 //SILENT FRAME, DECREMENT
413                {
414                    *hold_frames -= 1;
415                } else //HOLD TIME EXPIRED, CLOSE GATE
416                {
417                    *gate = false;
418                }
419            } else if *gate && rms >= consts::TRESHOLD_CLOSE //SPEAKING CONTINUES, RESET HOLD TIMER
420            {
421                *hold_frames = consts::HOLD_FRAMES;
422            }
423
424            //STORE TO PRE-ROLL BUFFER (MAX 3 FRAMES)
425            if !*gate
426            {
427                preroll.push_back(frame.clone());
428                if preroll.len() > 3
429                {
430                    preroll.pop_front();
431                }
432            }
433
434            //TRANSMIT ONLY IF GATE IS OPEN
435            if *gate
436            {
437                LOCAL_DISPLAY_HOLD.store((consts::SAMPLE_RATE * consts::DISPLAY_HOLD as u32 / 1000) as usize, Ordering::Relaxed);
438                transmit_audio(&opus_encoder, &frame, &mut encoded_buffer, id, &send_socket);
439            }
440        }
441    }, |_| {}, None).unwrap();
442
443    //OUTPUT RESAMPLING
444    let output_channels = output_config.channels as usize;
445    let output_source_rate = consts::SAMPLE_RATE as f32;
446    let output_target_rate = output_config.sample_rate as f32;
447
448    //OUTPUT INTERPOLATION
449    let output_resample_step = output_source_rate / output_target_rate;
450
451    //CONFIGURE OUTPUT STREAM
452    let output_stream = output_device.build_output_stream(&output_config, move |data: &mut [f32], _: &_|
453    {
454        //CHECK GENERATION
455        if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
456
457        //CLEAR OUTPUT BUFFER
458        data.fill(0.);
459
460        let frames_to_write = data.len() / output_channels;
461        let mut consumers_guard = CONSUMERS.lock().unwrap();
462
463        for i in 0..frames_to_write
464        {
465            let mut mixed_sample = 0.;
466            let mut active_speakers = 0;
467
468            for (stream, _) in consumers_guard.values_mut()
469            {
470                //RESAMPLE LOOP
471                while stream.resample_pos >= 1.
472                {
473                    stream.current_sample = stream.next_sample;
474                    stream.next_sample = stream.consumer.try_pop().unwrap_or(0.); //SILENCE ON UNDERRUN
475                    stream.resample_pos -= 1.;
476                }
477
478                //LINEAR INTERPOLATION
479                let interpolated = stream.current_sample + (stream.next_sample - stream.current_sample) * stream.resample_pos;
480                stream.resample_pos += output_resample_step; //MOVE RESAMPLER POSITION FOR THIS CLIENT
481
482                //ACTIVE SPEAKER DETECTION
483                if interpolated.abs() > consts::MIXING_TRESHOLD
484                {
485                    stream.activity_hold = consts::ACTIVITY_HOLD; //SET TIMER TO ~100ms
486                    stream.display_hold = (consts::SAMPLE_RATE * consts::DISPLAY_HOLD as u32 / 1000) as usize; //SET DISPLAY FOR ~1000ms
487                }
488
489                if stream.activity_hold > 0
490                {
491                    //MIX
492                    mixed_sample += interpolated;
493                    active_speakers += 1;
494                    stream.activity_hold -= 1;
495                }
496
497                //DECREMENT DISPLAY TIMER
498                if stream.display_hold > 0
499                {
500                    stream.display_hold -= 1;
501                }
502            }
503
504            //NORMALIZATION
505            if active_speakers > 1
506            {
507                mixed_sample /= (active_speakers as f32).sqrt();
508            }
509
510            //MIX EFFECTS
511            sfx::play_effects(&mut mixed_sample);
512
513            //SOFT CLIPPING (HYPERBOLIC TANGENT)
514            mixed_sample = mixed_sample.tanh();
515
516            //WRITE SAMPLE TO ALL CHANNELS
517            for channel in 0..output_channels
518            {
519                data[i * output_channels + channel] = mixed_sample;
520            }
521        }
522    }, |_| {}, None).unwrap();
523
524    //RUN STREAMS
525    input_stream.play().unwrap();  //INPUT
526    output_stream.play().unwrap(); //OUTPUT
527
528    //MOVE STREAMS TO GLOBAL STORAGE
529    *LOCAL_STREAMS.lock().unwrap() = Some(LocalStream
530    {
531        _input: input_stream,
532        _output: output_stream,
533    });
534
535    //PLAY JOIN SOUND
536    sfx::clear_effects();
537    sfx::queue_effect(SoundEffect::Join);
538
539    //START VOICE ACTIVITY DISPLAY & PING THREAD
540    let vad_socket = socket.clone();
541    thread::spawn(move ||
542    {
543        let mut iteration_counter = 0u8;
544
545        loop
546        {
547            //QUIT ON /leave
548            if !options::get_use_voice()
549            {
550                tx.send(ClientEvent::VoiceActivity(Vec::new())).unwrap(); //CLEAR WINDOW
551                return;
552            }
553
554            iteration_counter += 1; //INCREMENT
555
556            //SHOW VOICE ACTIVITY
557            display_active_speakers(&username, &tx);
558
559            //SEND PING PACKET
560            if iteration_counter == 10
561            {
562                voice::send(&vad_socket, VoicePacket
563                {
564                    id: Some(id),
565                    code: Some(VoiceCode::PING),
566                    timestamp: Some(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis()),
567
568                    ..Default::default()
569                }, &chat_options::get_keys().unwrap()).unwrap();
570
571                //RESET COUNTER
572                iteration_counter = 0;
573            }
574
575            thread::sleep(Duration::from_millis(100));
576        }
577    });
578
579    //OUTPUT BUFFERS
580    let mut decoded_buffer = [0.0f32; consts::FRAME_SIZE];
581
582    loop
583    {
584        //READ
585        let (network_buffer, _) = match voice::receive(&socket)
586        {
587            Some(r) => r,
588            None => //READING FAILED, TIMEOUT OR CRASH PROBABLY
589            {
590                //CHECK GENERATION
591                if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
592
593                //PLAY LEAVE SOUND EFFECT
594                sfx::queue_effect(SoundEffect::Leave);
595
596                //FINISH SOUND EFFECTS
597                while sfx::is_playing()
598                {
599                    //CHECK GENERATION AGAIN AHAHAHHAHAAH
600                    if AUDIO_GENERATION.load(Ordering::Relaxed) != current_generation { return; }
601
602                    thread::sleep(Duration::from_millis(50));
603                }
604
605                return;
606            }
607        };
608
609        //VERIFY SERVER SEQ
610        if network_buffer.seq <= options::get_server_seq() { continue; } //INGORE INVALID SEQs
611        options::set_server_seq(network_buffer.seq); //SET SERVER SEQ
612
613        //GET ID OF SENDER
614        let sender_id = match network_buffer.id
615        {
616            Some(id) => id,
617            None => continue
618        };
619
620        //CREATE NEW CLIENT CONTEXT ON UNKNOWN CLIENT
621        if !CONSUMERS.lock().unwrap().contains_key(&sender_id)
622        {
623            add_consumer(sender_id, network_buffer.username.unwrap());
624        }
625
626        if let Some((stream, peer)) = CONSUMERS.lock().unwrap().get_mut(&sender_id)
627        {
628            //PING/PONG
629            if let Some(code) = network_buffer.code && let Some(timestamp) = network_buffer.timestamp
630            {
631                match code
632                {
633                    //PING RECEIVED, SEND BACK
634                    VoiceCode::PING =>
635                    {
636                        //SEND PONG PACKET
637                        voice::send(&socket, VoicePacket
638                        {
639                            id: Some(id),
640                            target_id: Some(sender_id),
641                            code: Some(VoiceCode::PONG),
642                            timestamp: Some(timestamp),
643
644                            ..Default::default()
645                        }, &chat_options::get_keys().unwrap()).unwrap();
646                    },
647
648                    //PING FORWARDED BACK, CALCULATE LATENCY
649                    VoiceCode::PONG =>
650                    {
651                        //CALCULATE LATENCY
652                        let latency = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis().saturating_sub(timestamp);
653
654                        //STORE LATENCY TO BUFFER
655                        stream.latencies.push_back(latency);
656                        if stream.latencies.len() > 20 //STORE ONLY LATEST 20 LATENCIES
657                        {
658                            stream.latencies.pop_front();
659                        }
660
661                        //CALCULATE AVERAGE LATENCY
662                        let sum: u128 = stream.latencies.iter().sum();
663                        if !stream.latencies.is_empty()
664                        {
665                            //STORE IN AVG_LATENCY
666                            stream.avg_latency = sum / stream.latencies.len() as u128;
667                        }
668                    }
669                }
670            }
671
672            //CHECK FOR VOICE IN PACKET
673            if network_buffer.voice.is_none() { continue; }
674
675            //CHECK FOR MUTED CLIENT
676            if chat_options::is_muted(Some(sender_id)) { continue; }
677
678            //DECODE
679            if let Ok(decoded_len) = peer.decoder.decode_float(network_buffer.voice.as_deref(), &mut decoded_buffer[..], false)
680            {
681                //PUSH TO RINGBUFFER
682                peer.producer.push_slice(&decoded_buffer[..decoded_len]);
683            }
684        }
685    }
686}
687
688pub fn remove_consumer(id: &usize)
689{
690    if CONSUMERS.lock().unwrap().remove(id).is_some()
691    {
692        //PLAY LEAVE SOUND EFFECT
693        sfx::queue_effect(SoundEffect::Leave);
694    }
695}
696
697pub fn remove_all_consumers()
698{
699    CONSUMERS.lock().unwrap().clear();
700
701    //PLAY JOIN SOUND EFFECT (THIS IS CALLED ON CHANNEL CHANGE)
702    sfx::queue_effect(SoundEffect::Join);
703}
704
705pub fn add_consumer(id: usize, username: String)
706{
707    //OPUS DECODER
708    let decoder = Decoder::new
709    (
710        <SampleRate as TryFrom<i32>>::try_from(consts::SAMPLE_RATE as i32).unwrap(),
711        Channels::Mono,
712    ).unwrap();
713
714    //JITTER BUFFER
715    let rb = HeapRb::<f32>::new(consts::FRAME_SIZE * consts::JITTER_BUFFER_SIZE);
716    let (producer, mut consumer) = rb.split();
717
718    let first_sample = consumer.try_pop().unwrap_or(0.0);
719
720    //INSERT TO SHARED AUDIO THREAD MAP
721    CONSUMERS.lock().unwrap().insert(id, (RemoteStream
722    {
723        consumer: consumer,
724        resample_pos: 0.,
725        current_sample: 0.,
726        next_sample: first_sample,
727        activity_hold: 0,
728        display_hold: 0,
729        username: username,
730        latencies: VecDeque::with_capacity(20),
731        avg_latency: 0,
732    }, PeerData
733    {
734        decoder: decoder,
735        producer: producer,
736    }));
737
738    //PLAY JOIN SOUND EFFECT
739    sfx::queue_effect(SoundEffect::Join);
740}
741
742fn display_active_speakers(local_username: &str, tx: &Sender<ClientEvent>)
743{
744    //ALL USERS
745    let mut users_to_display = Vec::new();
746
747    //ADD LOCAL CLIENT
748    let local_speaking = LOCAL_DISPLAY_HOLD.load(Ordering::Relaxed) > 0;
749    users_to_display.push(VoiceUser
750    {
751        id: 0,
752        username: local_username.to_string(),
753        is_speaking: local_speaking,
754        latency: 0,
755        is_local: true,
756    });
757
758    //COLLECT OTHER USERS
759    if let Ok(consumers) = CONSUMERS.try_lock()
760    {
761        for (id, (stream, _)) in consumers.iter()
762        {
763            users_to_display.push(VoiceUser
764            {
765                id: *id,
766                username: stream.username.clone(),
767                is_speaking: stream.display_hold > 0, //SPEAKING
768                latency: stream.avg_latency,
769                is_local: false,
770            });
771        }
772    }
773
774    //SORT
775    if users_to_display.len() > 1
776    {
777        users_to_display[1..].sort_by_key(|u| u.id);
778    }
779
780    //DISPLAY
781    tx.send(ClientEvent::VoiceActivity(users_to_display)).unwrap();
782}