use crate::participant::ConnectionQuality; use crate::prelude::*; use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RtcEngine}; use crate::signal_client::SignalOptions; use crate::{RoomError, RoomEvent, RoomResult, SimulateScenario}; use livekit_protocol as proto; use livekit_protocol::observer::Dispatcher; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use std::collections::HashMap; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; use tokio::task::JoinHandle; use tracing::{error, info, instrument, trace, Level}; #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum ConnectionState { Disconnected, Connected, Reconnecting, Unknown, } /// Internal representation of a RoomSession #[derive(Debug)] struct SessionInner { state: AtomicU8, // ConnectionState sid: Mutex, name: Mutex, metadata: Mutex, participants: RwLock>, participants_tasks: RwLock, oneshot::Sender<()>)>>, active_speakers: RwLock>, rtc_engine: Arc, local_participant: LocalParticipant, dispatcher: Dispatcher, } #[derive(Debug)] pub(crate) struct SessionHandle { session: RoomSession, session_task: JoinHandle<()>, close_emitter: oneshot::Sender<()>, } /// RoomSession represents a connection to a room. /// It can be cloned and shared across threads. #[derive(Debug, Clone)] pub struct RoomSession { inner: Arc, } impl SessionHandle { pub async fn connect(url: &str, token: &str) -> RoomResult { let (rtc_engine, engine_events) = RtcEngine::new(); let rtc_engine = Arc::new(rtc_engine); rtc_engine .connect(url, token, SignalOptions::default()) .await?; let join_response = rtc_engine.join_response().unwrap(); let pi = join_response.participant.unwrap().clone(); let local_participant = LocalParticipant::new( rtc_engine.clone(), pi.sid.into(), pi.identity.into(), pi.name, pi.metadata, ); let room_info = join_response.room.unwrap(); let inner = Arc::new(SessionInner { state: AtomicU8::new(ConnectionState::Disconnected as u8), sid: Mutex::new(room_info.sid.into()), name: Mutex::new(room_info.name), metadata: Mutex::new(room_info.metadata), participants: Default::default(), participants_tasks: Default::default(), active_speakers: Default::default(), rtc_engine, local_participant, dispatcher: Default::default(), }); for pi in join_response.other_participants { let participant = { let pi = pi.clone(); inner.create_participant(pi.sid.into(), pi.identity.into(), pi.name, pi.metadata) }; participant.update_info(pi.clone()); } let (close_emitter, close_receiver) = oneshot::channel(); let session_task = tokio::spawn(inner.clone().room_task(engine_events, close_receiver)); inner.update_connection_state(ConnectionState::Connected); let session = Self { session: RoomSession { inner }, session_task, close_emitter, }; Ok(session) } pub async fn close(self) { self.session.inner.close().await; let _ = self.close_emitter.send(()); let _ = self.session_task.await; } pub fn subscribe(&self) -> mpsc::UnboundedReceiver { self.session.inner.dispatcher.register() } pub fn session(&self) -> RoomSession { self.session.clone() } } impl RoomSession { pub fn sid(&self) -> RoomSid { self.inner.sid.lock().clone() } pub fn name(&self) -> String { self.inner.name.lock().clone() } pub fn metadata(&self) -> String { self.inner.metadata.lock().clone() } pub fn local_participant(&self) -> LocalParticipant { self.inner.local_participant.clone() } pub fn connection_state(&self) -> ConnectionState { self.inner.state.load(Ordering::Acquire).try_into().unwrap() } pub fn participants(&self) -> RwLockReadGuard> { self.inner.participants.read() } pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> { self.inner.rtc_engine.simulate_scenario(scenario).await } } impl SessionInner { #[instrument(level = Level::DEBUG)] async fn room_task( self: Arc, mut engine_events: EngineEvents, mut close_receiver: oneshot::Receiver<()>, ) { loop { tokio::select! { res = engine_events.recv() => { if let Some(event) = res { if let Err(err) = self.on_engine_event(event).await { error!("failed to handle engine event: {:?}", err); } } }, _ = &mut close_receiver => { trace!("closing room_task"); break; } } } } /// Listen to the Participant events and forward them to the Room Dispatcher #[instrument(level = Level::DEBUG)] async fn participant_task( self: Arc, participant: Participant, mut participant_events: mpsc::UnboundedReceiver, mut close_rx: oneshot::Receiver<()>, ) { loop { tokio::select! { res = participant_events.recv() => { if let Some(event) = res { if let Err(err) = self.on_participant_event(&participant, event).await { error!("failed to handle participant event for {:?}: {:?}", participant.sid(), err); } } }, _ = &mut close_rx => { trace!("closing participant_task for {:?}", participant.sid()); break; }, } } } #[instrument(level = Level::DEBUG)] async fn on_participant_event( self: &Arc, participant: &Participant, event: ParticipantEvent, ) -> RoomResult<()> { if let Participant::Remote(remote_participant) = participant { match event { ParticipantEvent::TrackPublished { publication } => { self.dispatcher.dispatch(&RoomEvent::TrackPublished { participant: remote_participant.clone(), publication, }); } ParticipantEvent::TrackUnpublished { publication } => { self.dispatcher.dispatch(&RoomEvent::TrackUnpublished { participant: remote_participant.clone(), publication, }); } ParticipantEvent::TrackSubscribed { track, publication } => { self.dispatcher.dispatch(&RoomEvent::TrackSubscribed { participant: remote_participant.clone(), track, publication, }); } ParticipantEvent::TrackUnsubscribed { track, publication } => { self.dispatcher.dispatch(&RoomEvent::TrackUnsubscribed { participant: remote_participant.clone(), track, publication, }); } _ => {} }; } Ok(()) } #[instrument(level = Level::DEBUG)] async fn on_engine_event(self: &Arc, event: EngineEvent) -> RoomResult<()> { match event { EngineEvent::ParticipantUpdate { updates } => self.handle_participant_update(updates), EngineEvent::MediaTrack { track, stream, receiver: _, } => { let stream_id = stream.id(); let lk_stream_id = unpack_stream_id(&stream_id); if lk_stream_id.is_none() { Err(RoomError::Internal(format!( "MediaTrack event with invalid track_id: {:?}", &stream_id )))?; } let (participant_sid, track_sid) = lk_stream_id.unwrap(); let track_sid = track_sid.to_owned().into(); let remote_participant = self.get_participant(&participant_sid.to_string().into()); if let Some(remote_participant) = remote_participant { tokio::spawn(async move { remote_participant .add_subscribed_media_track(track_sid, track) .await; }); } else { // The server should send participant updates before sending a new offer // So this should never happen. Err(RoomError::Internal(format!( "AddTrack event with invalid participant_sid: {:?}", participant_sid )))?; } } EngineEvent::Resuming => { if self.update_connection_state(ConnectionState::Reconnecting) { self.dispatcher.dispatch(&RoomEvent::Reconnecting); } } EngineEvent::Resumed => { self.update_connection_state(ConnectionState::Connected); self.dispatcher.dispatch(&RoomEvent::Reconnected); // TODO(theomonnom): Update subscriptions settings // TODO(theomonnom): Send sync state } EngineEvent::Restarting => self.handle_restarting(), EngineEvent::Restarted => self.handle_restarted(), EngineEvent::Disconnected => self.handle_disconnected(), EngineEvent::Data { payload, kind, participant_sid, } => { let payload = Arc::new(payload); if let Some(participant) = self.get_participant(&participant_sid.into()) { self.dispatcher.dispatch(&RoomEvent::DataReceived { payload: payload.clone(), kind, participant: participant.clone(), }); participant.on_data_received(payload, kind); } } EngineEvent::SpeakersChanged { speakers } => self.handle_speakers_changed(speakers), EngineEvent::ConnectionQuality { updates } => { self.handle_connection_quality_update(updates) } } Ok(()) } #[instrument(level = Level::DEBUG)] async fn close(&self) { self.rtc_engine.close().await; } /// Change the connection state and emit an event /// Does nothing if the state is already the same #[instrument(level = Level::DEBUG)] fn update_connection_state(&self, state: ConnectionState) -> bool { let old_state = self.state.load(Ordering::Acquire); if old_state == state as u8 { return false; } self.state.store(state as u8, Ordering::Release); self.dispatcher .dispatch(&RoomEvent::ConnectionStateChanged(state)); return true; } /// Update the participants inside a Room. /// It'll create, update or remove a participant /// It also update the participant tracks. #[instrument(level = Level::DEBUG)] fn handle_participant_update(self: &Arc, updates: Vec) { for pi in updates { if pi.sid == self.local_participant.sid() || pi.identity == self.local_participant.identity() { self.local_participant.clone().update_info(pi); continue; } let remote_participant = self.get_participant(&pi.sid.clone().into()); if let Some(remote_participant) = remote_participant { if pi.state == proto::participant_info::State::Disconnected as i32 { // Participant disconnected info!("Participant disconnected: {}", pi.sid); self.clone() .handle_participant_disconnect(remote_participant) } else { // Participant is already connected, update the it remote_participant.update_info(pi.clone()); } } else { // Create a new participant info!("Participant connected: {}", pi.sid); let remote_participant = { let pi = pi.clone(); self.create_participant(pi.sid.into(), pi.identity.into(), pi.name, pi.metadata) }; let _ = self .dispatcher .dispatch(&RoomEvent::ParticipantConnected(remote_participant.clone())); remote_participant.update_info(pi.clone()); } } } /// Active speakers changed /// Update the participants & sort the active_speakers by audio_level #[instrument(level = Level::DEBUG)] fn handle_speakers_changed(&self, speakers_info: Vec) { let mut speakers = Vec::new(); for speaker in speakers_info { let participant = { if speaker.sid == self.local_participant.sid() { Participant::Local(self.local_participant.clone()) } else { if let Some(participant) = self.get_participant(&speaker.sid.into()) { Participant::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 .dispatcher .dispatch(&RoomEvent::ActiveSpeakersChanged { speakers }); } /// Handle a connection quality update /// Emit ConnectionQualityChanged event for the concerned participants #[instrument(level = Level::DEBUG)] fn handle_connection_quality_update(&self, updates: Vec) { for update in updates { let participant = { if update.participant_sid == self.local_participant.sid() { Participant::Local(self.local_participant.clone()) } else { if let Some(participant) = self.get_participant(&update.participant_sid.into()) { Participant::Remote(participant) } else { continue; } } }; let quality: ConnectionQuality = proto::ConnectionQuality::from_i32(update.quality) .unwrap() .into(); participant.set_connection_quality(quality); self.dispatcher .dispatch(&RoomEvent::ConnectionQualityChanged { participant, quality, }); } } #[instrument(level = Level::DEBUG)] fn handle_restarting(self: &Arc) { // Remove existing participants/subscriptions on full reconnect for (_, participant) in self.participants.read().iter() { self.clone() .handle_participant_disconnect(participant.clone()); } if self.update_connection_state(ConnectionState::Reconnecting) { self.dispatcher.dispatch(&RoomEvent::Reconnecting); } } #[instrument(level = Level::DEBUG)] fn handle_restarted(self: &Arc) { // Full reconnect succeeded! let join_response = self.rtc_engine.join_response().unwrap(); self.update_connection_state(ConnectionState::Connected); self.dispatcher.dispatch(&RoomEvent::Reconnected); if let Some(pi) = join_response.participant { self.local_participant.update_info(pi); // The sid may have changed } self.handle_participant_update(join_response.other_participants); // TODO(theomonnom): unpublish & republish tracks } #[instrument(level = Level::DEBUG)] fn handle_disconnected(&self) { if self.state.load(Ordering::Acquire) == ConnectionState::Disconnected as u8 { return; } self.update_connection_state(ConnectionState::Disconnected); self.dispatcher.dispatch(&RoomEvent::Disconnected); } /// Create a new participant /// Also add it to the participants list #[instrument(level = Level::DEBUG)] fn create_participant( self: &Arc, sid: ParticipantSid, identity: ParticipantIdentity, name: String, metadata: String, ) -> RemoteParticipant { let participant = RemoteParticipant::new(sid.clone(), identity, name, metadata); // Create the participant task let (close_tx, close_rx) = oneshot::channel(); let participant_task = tokio::spawn(self.clone().participant_task( Participant::Remote(participant.clone()), participant.register_observer(), close_rx, )); self.participants_tasks .write() .insert(sid.clone(), (participant_task, close_tx)); self.participants.write().insert(sid, participant.clone()); participant } /// A participant has disconnected /// Cleanup the participant and emit an event #[instrument(level = Level::DEBUG)] fn handle_participant_disconnect(self: Arc, remote_participant: RemoteParticipant) { tokio::spawn(async move { for (sid, _) in &*remote_participant.tracks() { remote_participant.unpublish_track(&sid); } // Close the participant task if let Some((task, close_tx)) = self .participants_tasks .write() .remove(&remote_participant.sid()) { let _ = close_tx.send(()); let _ = task.await; } self.participants.write().remove(&remote_participant.sid()); self.dispatcher .dispatch(&RoomEvent::ParticipantDisconnected(remote_participant)); }); } fn get_participant(&self, sid: &ParticipantSid) -> Option { self.participants.read().get(sid).cloned() } } fn unpack_stream_id(stream_id: &str) -> Option<(&str, &str)> { let split: Vec<&str> = stream_id.split('|').collect(); if split.len() == 2 { let participant_sid = split.get(0).unwrap(); let track_sid = split.get(1).unwrap(); Some((participant_sid, track_sid)) } else { None } } impl From for ConnectionState { fn from(value: u8) -> Self { match value { 0 => ConnectionState::Disconnected, 1 => ConnectionState::Connected, 2 => ConnectionState::Reconnecting, _ => ConnectionState::Unknown, } } }