audio_level & active_speakers
This commit is contained in:
@@ -52,6 +52,14 @@ impl ParticipantInternalTrait for LocalParticipant {
|
|||||||
fn update_info(self: &Arc<Self>, info: ParticipantInfo) {
|
fn update_info(self: &Arc<Self>, info: ParticipantInfo) {
|
||||||
self.shared.update_info(info);
|
self.shared.update_info(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_speaking(&self, speaking: bool) {
|
||||||
|
self.shared.set_speaking(speaking);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_audio_level(&self, level: f32) {
|
||||||
|
self.shared.set_audio_level(level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_participant_trait!(LocalParticipant);
|
impl_participant_trait!(LocalParticipant);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::room::room_session::SessionEmitter;
|
|||||||
use livekit_utils::enum_dispatch;
|
use livekit_utils::enum_dispatch;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod local_participant;
|
pub mod local_participant;
|
||||||
@@ -19,6 +20,8 @@ pub(super) struct ParticipantShared {
|
|||||||
pub(super) name: Mutex<String>,
|
pub(super) name: Mutex<String>,
|
||||||
pub(super) metadata: Mutex<String>,
|
pub(super) metadata: Mutex<String>,
|
||||||
pub(super) tracks: RwLock<HashMap<TrackSid, TrackPublication>>,
|
pub(super) tracks: RwLock<HashMap<TrackSid, TrackPublication>>,
|
||||||
|
pub(super) speaking: AtomicBool,
|
||||||
|
pub(super) audio_level: AtomicU32,
|
||||||
pub(super) internal_tx: SessionEmitter,
|
pub(super) internal_tx: SessionEmitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +39,8 @@ impl ParticipantShared {
|
|||||||
name: Mutex::new(name),
|
name: Mutex::new(name),
|
||||||
metadata: Mutex::new(metadata),
|
metadata: Mutex::new(metadata),
|
||||||
tracks: Default::default(),
|
tracks: Default::default(),
|
||||||
|
speaking: Default::default(),
|
||||||
|
audio_level: Default::default(),
|
||||||
internal_tx,
|
internal_tx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,12 +52,23 @@ impl ParticipantShared {
|
|||||||
*self.metadata.lock() = info.metadata; // TODO(theomonnom): callback MetadataChanged
|
*self.metadata.lock() = info.metadata; // TODO(theomonnom): callback MetadataChanged
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_speaking(&self, speaking: bool) {
|
||||||
|
self.speaking.store(speaking, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_audio_level(&self, audio_level: f32) {
|
||||||
|
self.audio_level
|
||||||
|
.store(audio_level.to_bits(), Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn add_track_publication(&self, publication: TrackPublication) {
|
pub(crate) fn add_track_publication(&self, publication: TrackPublication) {
|
||||||
self.tracks.write().insert(publication.sid(), publication);
|
self.tracks.write().insert(publication.sid(), publication);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait ParticipantInternalTrait {
|
pub(crate) trait ParticipantInternalTrait {
|
||||||
|
fn set_speaking(&self, speaking: bool);
|
||||||
|
fn set_audio_level(&self, level: f32);
|
||||||
fn update_info(self: &Arc<Self>, info: ParticipantInfo);
|
fn update_info(self: &Arc<Self>, info: ParticipantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +77,8 @@ pub trait ParticipantTrait {
|
|||||||
fn identity(&self) -> ParticipantIdentity;
|
fn identity(&self) -> ParticipantIdentity;
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn metadata(&self) -> String;
|
fn metadata(&self) -> String;
|
||||||
|
fn is_speaking(&self) -> bool;
|
||||||
|
fn audio_level(&self) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -72,7 +90,9 @@ pub enum ParticipantHandle {
|
|||||||
impl ParticipantHandle {
|
impl ParticipantHandle {
|
||||||
enum_dispatch!(
|
enum_dispatch!(
|
||||||
[Local, Remote]
|
[Local, Remote]
|
||||||
fnc!(update_info, &Self, [info: ParticipantInfo], ());
|
fnc!(pub(crate), update_info, &Self, [info: ParticipantInfo], ());
|
||||||
|
fnc!(pub(crate), set_speaking, &Self, [speaking: bool], ());
|
||||||
|
fnc!(pub(crate), set_audio_level, &Self, [audio_level: f32], ());
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +103,8 @@ impl ParticipantTrait for ParticipantHandle {
|
|||||||
fnc!(identity, &Self, [], ParticipantIdentity);
|
fnc!(identity, &Self, [], ParticipantIdentity);
|
||||||
fnc!(name, &Self, [], String);
|
fnc!(name, &Self, [], String);
|
||||||
fnc!(metadata, &Self, [], String);
|
fnc!(metadata, &Self, [], String);
|
||||||
|
fnc!(is_speaking, &Self, [], bool);
|
||||||
|
fnc!(audio_level, &Self, [], f32);
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +112,7 @@ macro_rules! impl_participant_trait {
|
|||||||
($x:ty) => {
|
($x:ty) => {
|
||||||
use crate::proto::ParticipantInfo;
|
use crate::proto::ParticipantInfo;
|
||||||
use crate::room::id::{ParticipantIdentity, ParticipantSid};
|
use crate::room::id::{ParticipantIdentity, ParticipantSid};
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
impl crate::room::participant::ParticipantTrait for $x {
|
impl crate::room::participant::ParticipantTrait for $x {
|
||||||
@@ -108,10 +131,16 @@ macro_rules! impl_participant_trait {
|
|||||||
fn metadata(&self) -> String {
|
fn metadata(&self) -> String {
|
||||||
self.shared.metadata.lock().clone()
|
self.shared.metadata.lock().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_speaking(&self) -> bool {
|
||||||
|
self.shared.speaking.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn audio_level(&self) -> f32 {
|
||||||
|
f32::from_bits(self.shared.audio_level.load(Ordering::SeqCst))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) use impl_participant_trait;
|
pub(super) use impl_participant_trait;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use crate::proto::TrackInfo;
|
|
||||||
use crate::room::id::TrackSid;
|
use crate::room::id::TrackSid;
|
||||||
use crate::room::participant::{
|
use crate::room::participant::{
|
||||||
impl_participant_trait, ParticipantInternalTrait, ParticipantShared,
|
impl_participant_trait, ParticipantInternalTrait, ParticipantShared,
|
||||||
@@ -156,6 +155,14 @@ impl ParticipantInternalTrait for RemoteParticipant {
|
|||||||
valid_tracks.insert(track.sid.into());
|
valid_tracks.insert(track.sid.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_speaking(&self, speaking: bool) {
|
||||||
|
self.shared.set_speaking(speaking);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_audio_level(&self, level: f32) {
|
||||||
|
self.shared.set_audio_level(level);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_participant_trait!(RemoteParticipant);
|
impl_participant_trait!(RemoteParticipant);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::id::{ParticipantIdentity, ParticipantSid};
|
use super::id::{ParticipantIdentity, ParticipantSid};
|
||||||
use super::participant::local_participant::LocalParticipant;
|
use super::participant::local_participant::LocalParticipant;
|
||||||
use super::participant::remote_participant::RemoteParticipant;
|
use super::participant::remote_participant::RemoteParticipant;
|
||||||
|
use super::participant::ParticipantHandle;
|
||||||
use super::participant::{ParticipantInternalTrait, ParticipantTrait};
|
use super::participant::{ParticipantInternalTrait, ParticipantTrait};
|
||||||
use super::{RoomEmitter, RoomError, RoomEvent, RoomResult, SimulateScenario};
|
use super::{RoomEmitter, RoomError, RoomEvent, RoomResult, SimulateScenario};
|
||||||
use crate::proto::{self, participant_info};
|
use crate::proto::{self, participant_info, SpeakerInfo};
|
||||||
use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RTCEngine};
|
use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RTCEngine};
|
||||||
use crate::signal_client::SignalOptions;
|
use crate::signal_client::SignalOptions;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
@@ -50,6 +51,7 @@ struct SessionInner {
|
|||||||
sid: Mutex<String>,
|
sid: Mutex<String>,
|
||||||
name: Mutex<String>,
|
name: Mutex<String>,
|
||||||
participants: RwLock<HashMap<ParticipantSid, Arc<RemoteParticipant>>>,
|
participants: RwLock<HashMap<ParticipantSid, Arc<RemoteParticipant>>>,
|
||||||
|
active_speakers: RwLock<Vec<ParticipantHandle>>,
|
||||||
rtc_engine: Arc<RTCEngine>,
|
rtc_engine: Arc<RTCEngine>,
|
||||||
local_participant: Arc<LocalParticipant>,
|
local_participant: Arc<LocalParticipant>,
|
||||||
internal_tx: SessionEmitter,
|
internal_tx: SessionEmitter,
|
||||||
@@ -95,6 +97,7 @@ impl SessionHandle {
|
|||||||
sid: Mutex::new(room_info.sid),
|
sid: Mutex::new(room_info.sid),
|
||||||
name: Mutex::new(room_info.name),
|
name: Mutex::new(room_info.name),
|
||||||
participants: Default::default(),
|
participants: Default::default(),
|
||||||
|
active_speakers: Default::default(),
|
||||||
rtc_engine,
|
rtc_engine,
|
||||||
local_participant,
|
local_participant,
|
||||||
internal_tx,
|
internal_tx,
|
||||||
@@ -214,7 +217,13 @@ impl SessionInner {
|
|||||||
match event {
|
match event {
|
||||||
SessionEvent::Room(event) => {
|
SessionEvent::Room(event) => {
|
||||||
if self.state.load(Ordering::Acquire) != ConnectionState::Connected as u8
|
if self.state.load(Ordering::Acquire) != ConnectionState::Connected as u8
|
||||||
&& matches!(event, RoomEvent::TrackPublished { .. })
|
&& matches!(
|
||||||
|
event,
|
||||||
|
RoomEvent::TrackPublished { .. }
|
||||||
|
| RoomEvent::ParticipantConnected { .. }
|
||||||
|
| RoomEvent::ParticipantDisconnected { .. }
|
||||||
|
| RoomEvent::ActiveSpeakersChanged { .. }
|
||||||
|
)
|
||||||
{
|
{
|
||||||
return Ok(()); // Ignore the event
|
return Ok(()); // Ignore the event
|
||||||
}
|
}
|
||||||
@@ -298,6 +307,7 @@ impl SessionInner {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EngineEvent::SpeakersChanged { speakers } => self.handle_speakers_changed(speakers),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -308,10 +318,6 @@ impl SessionInner {
|
|||||||
self.rtc_engine.close().await;
|
self.rtc_engine.close().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_participant(&self, sid: &ParticipantSid) -> Option<Arc<RemoteParticipant>> {
|
|
||||||
self.participants.read().get(sid).cloned()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change the connection state and emit an event
|
/// Change the connection state and emit an event
|
||||||
/// Does nothing if the state is already the same
|
/// Does nothing if the state is already the same
|
||||||
#[instrument(level = Level::DEBUG)]
|
#[instrument(level = Level::DEBUG)]
|
||||||
@@ -384,6 +390,40 @@ impl SessionInner {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG)]
|
||||||
|
fn handle_speakers_changed(&self, speakers_info: Vec<SpeakerInfo>) {
|
||||||
|
let mut speakers = Vec::new();
|
||||||
|
|
||||||
|
for speaker in speakers_info {
|
||||||
|
let participant = {
|
||||||
|
if speaker.sid == self.local_participant.sid() {
|
||||||
|
ParticipantHandle::Local(self.local_participant.clone())
|
||||||
|
} else {
|
||||||
|
if let Some(participant) = self.get_participant(&speaker.sid.into()) {
|
||||||
|
ParticipantHandle::Remote(participant)
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
participant.set_speaking(speaker.active);
|
||||||
|
participant.set_audio_level(speaker.level);
|
||||||
|
|
||||||
|
if speaker.active {
|
||||||
|
speakers.push(participant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
speakers.sort_by(|a, b| a.audio_level().partial_cmp(&b.audio_level()).unwrap());
|
||||||
|
*self.active_speakers.write() = speakers.clone();
|
||||||
|
let _ = self
|
||||||
|
.internal_tx
|
||||||
|
.send(SessionEvent::Room(RoomEvent::ActiveSpeakersChanged {
|
||||||
|
speakers,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = Level::DEBUG)]
|
#[instrument(level = Level::DEBUG)]
|
||||||
fn handle_restarting(&self) {
|
fn handle_restarting(&self) {
|
||||||
// Remove existing participants/subscriptions on full reconnect
|
// Remove existing participants/subscriptions on full reconnect
|
||||||
@@ -451,6 +491,10 @@ impl SessionInner {
|
|||||||
self.participants.write().insert(sid, p.clone());
|
self.participants.write().insert(sid, p.clone());
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_participant(&self, sid: &ParticipantSid) -> Option<Arc<RemoteParticipant>> {
|
||||||
|
self.participants.read().get(sid).cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> {
|
fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use lazy_static::lazy_static;
|
|||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
use tracing::{error, info, warn};
|
use tracing::{error, info, warn};
|
||||||
|
|
||||||
use crate::proto::{data_packet, DataPacket, JoinResponse, ParticipantUpdate};
|
use crate::proto::{data_packet, DataPacket, JoinResponse, ParticipantUpdate, SpeakerInfo};
|
||||||
use crate::rtc_engine::lk_runtime::LKRuntime;
|
use crate::rtc_engine::lk_runtime::LKRuntime;
|
||||||
use crate::signal_client::{SignalError, SignalOptions};
|
use crate::signal_client::{SignalError, SignalOptions};
|
||||||
|
|
||||||
@@ -78,6 +78,9 @@ pub enum EngineEvent {
|
|||||||
payload: Vec<u8>,
|
payload: Vec<u8>,
|
||||||
kind: data_packet::Kind,
|
kind: data_packet::Kind,
|
||||||
},
|
},
|
||||||
|
SpeakersChanged {
|
||||||
|
speakers: Vec<SpeakerInfo>,
|
||||||
|
},
|
||||||
Resuming,
|
Resuming,
|
||||||
Resumed,
|
Resumed,
|
||||||
Restarting,
|
Restarting,
|
||||||
@@ -274,6 +277,12 @@ impl EngineInner {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
SessionEvent::SpeakersChanged { speakers } => {
|
||||||
|
let _ = self
|
||||||
|
.engine_emitter
|
||||||
|
.send(EngineEvent::SpeakersChanged { speakers })
|
||||||
|
.await;
|
||||||
|
}
|
||||||
SessionEvent::Connected => {}
|
SessionEvent::Connected => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ pub enum SessionEvent {
|
|||||||
stream: MediaStream,
|
stream: MediaStream,
|
||||||
receiver: RtpReceiver,
|
receiver: RtpReceiver,
|
||||||
},
|
},
|
||||||
|
SpeakersChanged {
|
||||||
|
speakers: Vec<proto::SpeakerInfo>,
|
||||||
|
},
|
||||||
// TODO(theomonnom): Move entirely the reconnection logic on mod.rs
|
// TODO(theomonnom): Move entirely the reconnection logic on mod.rs
|
||||||
Close {
|
Close {
|
||||||
source: String,
|
source: String,
|
||||||
@@ -416,6 +419,13 @@ impl SessionInner {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
signal_response::Message::SpeakersChanged(speaker) => {
|
||||||
|
let _ = self
|
||||||
|
.emitter
|
||||||
|
.send(SessionEvent::SpeakersChanged {
|
||||||
|
speakers: speaker.speakers,
|
||||||
|
});
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,9 +520,7 @@ impl SessionInner {
|
|||||||
kind: data_packet::Kind::from_i32(data.kind).unwrap(),
|
kind: data_packet::Kind::from_i32(data.kind).unwrap(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Value::Speaker(_) => {
|
Value::Speaker(_) => {}
|
||||||
// TODO(theomonnonm)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
|
||||||
|
// TODO(theomonnom): Match the complete function signature like:
|
||||||
|
// - pub(crate) fn update_info(&self, info: ParticipantInfo) -> ();
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! enum_dispatch {
|
macro_rules! enum_dispatch {
|
||||||
// This arm is used to avoid nested loops with the arguments
|
// This arm is used to avoid nested loops with the arguments
|
||||||
@@ -10,16 +13,15 @@ macro_rules! enum_dispatch {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($fnc:ident, $self:ty, [$($arg:ident: $t:ty),*], $ret:ty, [$($variant:ident),+]) => {
|
($vis:vis$(,)? $fnc:ident, $self:ty, [$($arg:ident: $t:ty),*], $ret:ty, [$($variant:ident),+]) => {
|
||||||
fn $fnc(self: $self, $($arg: $t),*) -> $ret {
|
$vis fn $fnc(self: $self, $($arg: $t),*) -> $ret {
|
||||||
enum_dispatch!(@match self $fnc ($($arg,)*) [$($variant),+])
|
enum_dispatch!(@match self $fnc ($($arg,)*) [$($variant),+])
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
($variants:tt $(fnc!($fnc:ident, $self:ty, $args:tt, $ret:ty);)+) => {
|
($variants:tt $(fnc!($vis:vis$(,)? $fnc:ident, $self:ty, $args:tt, $ret:ty);)+) => {
|
||||||
$(
|
$(
|
||||||
enum_dispatch!($fnc, $self, $args, $ret, $variants);
|
enum_dispatch!($vis, $fnc, $self, $args, $ret, $variants);
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user