reconnect WIP
This commit is contained in:
@@ -1,4 +1,13 @@
|
|||||||
|
use crate::room::id::TrackSid;
|
||||||
|
use crate::room::participant::remote_participant::RemoteParticipant;
|
||||||
|
use crate::room::publication::RemoteTrackPublication;
|
||||||
|
use crate::room::track::remote_track::RemoteTrackHandle;
|
||||||
|
use crate::room::{ConnectionState, RoomHandle};
|
||||||
|
use futures::future::Future;
|
||||||
use futures_util::future::BoxFuture;
|
use futures_util::future::BoxFuture;
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
type EventHandler<T> = Box<dyn FnMut(T) -> BoxFuture<'static, ()> + Send + Sync>;
|
type EventHandler<T> = Box<dyn FnMut(T) -> BoxFuture<'static, ()> + Send + Sync>;
|
||||||
@@ -21,120 +30,94 @@ pub enum TrackError {
|
|||||||
TrackNotFound(String),
|
TrackNotFound(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod room {
|
#[derive(Clone, Debug)]
|
||||||
use super::{EventHandler, TrackError};
|
pub struct ParticipantConnectedEvent {
|
||||||
use crate::room::id::TrackSid;
|
pub room_handle: RoomHandle,
|
||||||
use crate::room::participant::remote_participant::RemoteParticipant;
|
pub participant: Arc<RemoteParticipant>,
|
||||||
use crate::room::publication::RemoteTrackPublication;
|
}
|
||||||
use crate::room::track::remote_track::RemoteTrackHandle;
|
|
||||||
use crate::room::RoomHandle;
|
|
||||||
use futures::future::Future;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ParticipantConnectedEvent {
|
pub struct ParticipantDisconnectedEvent {
|
||||||
pub room_handle: RoomHandle,
|
pub room_handle: RoomHandle,
|
||||||
pub participant: Arc<RemoteParticipant>,
|
pub participant: Arc<RemoteParticipant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ParticipantDisconnectedEvent {
|
pub struct TrackSubscribedEvent {
|
||||||
pub room_handle: RoomHandle,
|
pub room_handle: RoomHandle,
|
||||||
pub participant: Arc<RemoteParticipant>,
|
pub track: RemoteTrackHandle,
|
||||||
}
|
pub publication: RemoteTrackPublication,
|
||||||
|
pub participant: Arc<RemoteParticipant>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TrackSubscribedEvent {
|
pub struct TrackPublishedEvent {
|
||||||
pub room_handle: RoomHandle,
|
pub room_handle: RoomHandle,
|
||||||
pub track: RemoteTrackHandle,
|
pub publication: RemoteTrackPublication,
|
||||||
pub publication: RemoteTrackPublication,
|
pub participant: Arc<RemoteParticipant>,
|
||||||
pub participant: Arc<RemoteParticipant>,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TrackPublishedEvent {
|
pub struct TrackSubscriptionFailedEvent {
|
||||||
pub room_handle: RoomHandle,
|
pub room_handle: RoomHandle,
|
||||||
pub publication: RemoteTrackPublication,
|
pub error: TrackError,
|
||||||
pub participant: Arc<RemoteParticipant>,
|
pub sid: TrackSid,
|
||||||
}
|
pub participant: Arc<RemoteParticipant>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TrackSubscriptionFailedEvent {
|
pub struct ConnectionStateChangedEvent {
|
||||||
pub room_handle: RoomHandle,
|
pub room_handle: RoomHandle,
|
||||||
pub error: TrackError,
|
pub state: ConnectionState,
|
||||||
pub sid: TrackSid,
|
}
|
||||||
pub participant: Arc<RemoteParticipant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type OnParticipantConnectedHandler = EventHandler<ParticipantConnectedEvent>;
|
pub(crate) type OnParticipantConnectedHandler = EventHandler<ParticipantConnectedEvent>;
|
||||||
pub(crate) type OnParticipantDisconnectedHandler = EventHandler<ParticipantDisconnectedEvent>;
|
pub(crate) type OnParticipantDisconnectedHandler = EventHandler<ParticipantDisconnectedEvent>;
|
||||||
pub(crate) type OnTrackSubscribedEventHandler = EventHandler<TrackSubscribedEvent>;
|
pub(crate) type OnTrackSubscribedHandler = EventHandler<TrackSubscribedEvent>;
|
||||||
pub(crate) type OnTrackPublishedEventHandler = EventHandler<TrackPublishedEvent>;
|
pub(crate) type OnTrackPublishedHandler = EventHandler<TrackPublishedEvent>;
|
||||||
pub(crate) type OnTrackSubscriptionFailedHandler = EventHandler<TrackSubscriptionFailedEvent>;
|
pub(crate) type OnTrackSubscriptionFailedHandler = EventHandler<TrackSubscriptionFailedEvent>;
|
||||||
|
pub(crate) type OnConnectionStateChangedHandler = EventHandler<ConnectionStateChangedEvent>;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RoomEvents {
|
pub struct RoomEvents {
|
||||||
pub(crate) on_participant_connected: Mutex<Option<OnParticipantConnectedHandler>>,
|
pub(crate) on_participant_connected: Mutex<Option<OnParticipantConnectedHandler>>,
|
||||||
pub(crate) on_participant_disconnected: Mutex<Option<OnParticipantDisconnectedHandler>>,
|
pub(crate) on_participant_disconnected: Mutex<Option<OnParticipantDisconnectedHandler>>,
|
||||||
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedEventHandler>>,
|
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedHandler>>,
|
||||||
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedEventHandler>>,
|
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedHandler>>,
|
||||||
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
||||||
}
|
pub(crate) on_connection_state_changed: Mutex<Option<OnConnectionStateChangedHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl RoomEvents {
|
impl Debug for RoomEvents {
|
||||||
event_setter!(on_participant_connected, ParticipantConnectedEvent);
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
event_setter!(on_participant_disconnected, ParticipantDisconnectedEvent);
|
write!(f, "RoomEvents")
|
||||||
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
|
||||||
event_setter!(on_track_published, TrackPublishedEvent);
|
|
||||||
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod participant {
|
impl RoomEvents {
|
||||||
use super::{EventHandler, TrackError};
|
event_setter!(on_participant_connected, ParticipantConnectedEvent);
|
||||||
use crate::room::id::TrackSid;
|
event_setter!(on_participant_disconnected, ParticipantDisconnectedEvent);
|
||||||
use crate::room::participant::remote_participant::RemoteParticipant;
|
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
||||||
use crate::room::publication::RemoteTrackPublication;
|
event_setter!(on_track_published, TrackPublishedEvent);
|
||||||
use crate::room::track::remote_track::RemoteTrackHandle;
|
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
||||||
use futures::future::Future;
|
event_setter!(on_connection_state_changed, ConnectionStateChangedEvent);
|
||||||
use parking_lot::Mutex;
|
}
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Default)]
|
||||||
pub struct TrackPublishedEvent {
|
pub struct ParticipantEvents {
|
||||||
pub publication: RemoteTrackPublication,
|
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedHandler>>,
|
||||||
pub participant: Arc<RemoteParticipant>,
|
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedHandler>>,
|
||||||
}
|
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl Debug for ParticipantEvents {
|
||||||
pub struct TrackSubscribedEvent {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
pub track: RemoteTrackHandle,
|
write!(f, "ParticipantEvents")
|
||||||
pub publication: RemoteTrackPublication,
|
|
||||||
pub participant: Arc<RemoteParticipant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct TrackSubscriptionFailedEvent {
|
|
||||||
pub sid: TrackSid,
|
|
||||||
pub error: TrackError,
|
|
||||||
pub participant: Arc<RemoteParticipant>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type TrackPublishedHandler = EventHandler<TrackPublishedEvent>;
|
|
||||||
pub(crate) type TrackSubscribedHandler = EventHandler<TrackSubscribedEvent>;
|
|
||||||
pub(crate) type TrackSubscriptionFailedHandler = EventHandler<TrackSubscriptionFailedEvent>;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ParticipantEvents {
|
|
||||||
pub(crate) on_track_published: Mutex<Option<TrackPublishedHandler>>,
|
|
||||||
pub(crate) on_track_subscribed: Mutex<Option<TrackSubscribedHandler>>,
|
|
||||||
pub(crate) on_track_subscription_failed: Mutex<Option<TrackSubscriptionFailedHandler>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParticipantEvents {
|
|
||||||
event_setter!(on_track_published, TrackPublishedEvent);
|
|
||||||
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
|
||||||
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ParticipantEvents {
|
||||||
|
event_setter!(on_track_published, TrackPublishedEvent);
|
||||||
|
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
||||||
|
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ pub mod proto {
|
|||||||
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
|
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
mod events;
|
|
||||||
mod rtc_engine;
|
mod rtc_engine;
|
||||||
mod signal_client;
|
mod signal_client;
|
||||||
|
|
||||||
|
pub mod events;
|
||||||
pub mod room;
|
pub mod room;
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
use parking_lot::lock_api::RwLockUpgradableReadGuard;
|
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::AtomicU8;
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use self::id::ParticipantSid;
|
use self::id::{ParticipantIdentity, ParticipantSid};
|
||||||
use self::participant::local_participant::LocalParticipant;
|
use self::participant::local_participant::LocalParticipant;
|
||||||
use self::participant::remote_participant::RemoteParticipant;
|
use self::participant::remote_participant::RemoteParticipant;
|
||||||
use self::participant::ParticipantInternalTrait;
|
use self::participant::ParticipantInternalTrait;
|
||||||
use self::participant::ParticipantTrait;
|
use self::participant::ParticipantTrait;
|
||||||
use crate::events::room::{
|
use crate::events::{
|
||||||
ParticipantConnectedEvent, ParticipantDisconnectedEvent, RoomEvents, TrackSubscribedEvent,
|
ParticipantConnectedEvent, ParticipantDisconnectedEvent, RoomEvents, TrackPublishedEvent,
|
||||||
|
TrackSubscribedEvent,
|
||||||
};
|
};
|
||||||
use crate::proto;
|
use crate::proto;
|
||||||
use crate::proto::participant_info;
|
use crate::proto::participant_info;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error, instrument, trace_span, Level};
|
||||||
|
|
||||||
use crate::rtc_engine::{EngineError, EngineEvent, EngineEvents, RTCEngine};
|
use crate::rtc_engine::{EngineError, EngineEvent, EngineEvents, RTCEngine};
|
||||||
use crate::signal_client::SignalOptions;
|
use crate::signal_client::SignalOptions;
|
||||||
@@ -27,15 +27,15 @@ pub mod track;
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum RoomError {
|
pub enum RoomError {
|
||||||
#[error("internal RTCEngine failure")]
|
#[error("engine : {0}")]
|
||||||
Engine(#[from] EngineError),
|
Engine(#[from] EngineError),
|
||||||
#[error("internal Room failure")]
|
#[error("room failure: {0}")]
|
||||||
Internal(String),
|
Internal(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoomResult<T> = Result<T, RoomError>;
|
pub type RoomResult<T> = Result<T, RoomError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum ConnectionState {
|
pub enum ConnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Connecting,
|
Connecting,
|
||||||
@@ -43,6 +43,7 @@ pub enum ConnectionState {
|
|||||||
Reconnecting,
|
Reconnecting,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct RoomInner {
|
struct RoomInner {
|
||||||
state: AtomicU8, // ConnectionState
|
state: AtomicU8, // ConnectionState
|
||||||
sid: Mutex<String>,
|
sid: Mutex<String>,
|
||||||
@@ -52,6 +53,7 @@ struct RoomInner {
|
|||||||
local_participant: Arc<LocalParticipant>,
|
local_participant: Arc<LocalParticipant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
inner: Option<Arc<RoomInner>>,
|
inner: Option<Arc<RoomInner>>,
|
||||||
events: Arc<RoomEvents>,
|
events: Arc<RoomEvents>,
|
||||||
@@ -65,14 +67,19 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG)]
|
||||||
pub async fn connect(&mut self, url: &str, token: &str) -> RoomResult<()> {
|
pub async fn connect(&mut self, url: &str, token: &str) -> RoomResult<()> {
|
||||||
let (rtc_engine, engine_events) =
|
let (rtc_engine, engine_events) =
|
||||||
RTCEngine::connect(url, token, SignalOptions::default()).await?;
|
RTCEngine::connect(url, token, SignalOptions::default()).await?;
|
||||||
let rtc_engine = Arc::new(rtc_engine);
|
let rtc_engine = Arc::new(rtc_engine);
|
||||||
let join_response = rtc_engine.join_response();
|
let join_response = rtc_engine.join_response();
|
||||||
|
let pi = join_response.participant.unwrap().clone();
|
||||||
let local_participant = Arc::new(LocalParticipant::new(
|
let local_participant = Arc::new(LocalParticipant::new(
|
||||||
rtc_engine.clone(),
|
rtc_engine.clone(),
|
||||||
join_response.participant.unwrap().clone(),
|
pi.sid.into(),
|
||||||
|
pi.identity.into(),
|
||||||
|
pi.name,
|
||||||
|
pi.metadata,
|
||||||
));
|
));
|
||||||
let room_info = join_response.room.unwrap();
|
let room_info = join_response.room.unwrap();
|
||||||
let inner = Arc::new(RoomInner {
|
let inner = Arc::new(RoomInner {
|
||||||
@@ -84,14 +91,25 @@ impl Room {
|
|||||||
local_participant,
|
local_participant,
|
||||||
});
|
});
|
||||||
|
|
||||||
self.inner = Some(inner.clone());
|
|
||||||
|
|
||||||
// Add already connected participants
|
|
||||||
for pi in join_response.other_participants {
|
for pi in join_response.other_participants {
|
||||||
let p = Self::create_participant(inner.clone(), self.events.clone(), pi.clone());
|
let participant = {
|
||||||
p.update_info(pi).await;
|
let pi = pi.clone();
|
||||||
|
Self::create_participant(
|
||||||
|
inner.clone(),
|
||||||
|
self.events.clone(),
|
||||||
|
pi.sid.into(),
|
||||||
|
pi.identity.into(),
|
||||||
|
pi.name,
|
||||||
|
pi.metadata,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
participant.update_info(pi.clone());
|
||||||
|
participant
|
||||||
|
.update_tracks(RoomHandle::from(inner.clone()), pi.tracks)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.inner = Some(inner.clone());
|
||||||
tokio::spawn(Self::room_task(inner, self.events.clone(), engine_events));
|
tokio::spawn(Self::room_task(inner, self.events.clone(), engine_events));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -121,6 +139,7 @@ impl Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(room_inner, room_events))]
|
||||||
async fn handle_event(
|
async fn handle_event(
|
||||||
room_inner: Arc<RoomInner>,
|
room_inner: Arc<RoomInner>,
|
||||||
room_events: Arc<RoomEvents>,
|
room_events: Arc<RoomEvents>,
|
||||||
@@ -155,10 +174,18 @@ impl Room {
|
|||||||
Self::get_participant(room_inner.clone(), &participant_sid.to_string().into());
|
Self::get_participant(room_inner.clone(), &participant_sid.to_string().into());
|
||||||
|
|
||||||
if let Some(remote_participant) = remote_participant {
|
if let Some(remote_participant) = remote_participant {
|
||||||
remote_participant.add_subscribed_media_track(
|
tokio::spawn({
|
||||||
track_sid.to_string().into(),
|
let track_sid = track_sid.to_owned().into();
|
||||||
rtp_receiver.track(),
|
async move {
|
||||||
);
|
remote_participant
|
||||||
|
.add_subscribed_media_track(
|
||||||
|
RoomHandle::from(room_inner),
|
||||||
|
track_sid,
|
||||||
|
rtp_receiver.track(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// The server should send participant updates before sending a new offer
|
// The server should send participant updates before sending a new offer
|
||||||
// So this should not happen.
|
// So this should not happen.
|
||||||
@@ -173,6 +200,7 @@ impl Room {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(room_inner, room_events))]
|
||||||
async fn handle_participant_update(
|
async fn handle_participant_update(
|
||||||
room_inner: Arc<RoomInner>,
|
room_inner: Arc<RoomInner>,
|
||||||
room_events: Arc<RoomEvents>,
|
room_events: Arc<RoomEvents>,
|
||||||
@@ -182,7 +210,7 @@ impl Room {
|
|||||||
if pi.sid == room_inner.local_participant.sid()
|
if pi.sid == room_inner.local_participant.sid()
|
||||||
|| pi.identity == room_inner.local_participant.identity()
|
|| pi.identity == room_inner.local_participant.identity()
|
||||||
{
|
{
|
||||||
room_inner.local_participant.clone().update_info(pi).await;
|
room_inner.local_participant.clone().update_info(pi);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,12 +227,24 @@ impl Room {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Participant is already connected, update the informations
|
// Participant is already connected, update the informations
|
||||||
remote_participant.update_info(pi).await;
|
remote_participant.update_info(pi.clone());
|
||||||
|
remote_participant
|
||||||
|
.update_tracks(RoomHandle::from(room_inner.clone()), pi.tracks)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create a new participant and call OnConnect event
|
// Create a new participant and call OnConnect event
|
||||||
let remote_participant =
|
let remote_participant = {
|
||||||
Self::create_participant(room_inner.clone(), room_events.clone(), pi);
|
let pi = pi.clone();
|
||||||
|
Self::create_participant(
|
||||||
|
room_inner.clone(),
|
||||||
|
room_events.clone(),
|
||||||
|
pi.sid.into(),
|
||||||
|
pi.identity.into(),
|
||||||
|
pi.name,
|
||||||
|
pi.metadata,
|
||||||
|
)
|
||||||
|
};
|
||||||
let mut handler = room_events.on_participant_connected.lock();
|
let mut handler = room_events.on_participant_connected.lock();
|
||||||
if let Some(cb) = handler.as_mut() {
|
if let Some(cb) = handler.as_mut() {
|
||||||
cb(ParticipantConnectedEvent {
|
cb(ParticipantConnectedEvent {
|
||||||
@@ -212,10 +252,16 @@ impl Room {
|
|||||||
participant: remote_participant.clone(),
|
participant: remote_participant.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
remote_participant.update_info(pi.clone());
|
||||||
|
remote_participant
|
||||||
|
.update_tracks(RoomHandle::from(room_inner.clone()), pi.tracks)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(room_inner, room_events))]
|
||||||
fn handle_participant_disconnect(
|
fn handle_participant_disconnect(
|
||||||
room_inner: Arc<RoomInner>,
|
room_inner: Arc<RoomInner>,
|
||||||
room_events: Arc<RoomEvents>,
|
room_events: Arc<RoomEvents>,
|
||||||
@@ -247,42 +293,64 @@ impl Room {
|
|||||||
fn create_participant(
|
fn create_participant(
|
||||||
room_inner: Arc<RoomInner>,
|
room_inner: Arc<RoomInner>,
|
||||||
room_events: Arc<RoomEvents>,
|
room_events: Arc<RoomEvents>,
|
||||||
pi: proto::ParticipantInfo,
|
sid: ParticipantSid,
|
||||||
|
identity: ParticipantIdentity,
|
||||||
|
name: String,
|
||||||
|
metadata: String,
|
||||||
) -> Arc<RemoteParticipant> {
|
) -> Arc<RemoteParticipant> {
|
||||||
let p = Arc::new(RemoteParticipant::new(pi.clone()));
|
let p = Arc::new(RemoteParticipant::new(
|
||||||
|
sid.clone(),
|
||||||
|
identity,
|
||||||
|
name,
|
||||||
|
metadata,
|
||||||
|
));
|
||||||
|
|
||||||
|
macro_rules! forward_event {
|
||||||
|
($type:ident, when_connected) => {
|
||||||
|
p.internal_events().$type({
|
||||||
|
let room_events = room_events.clone();
|
||||||
|
let room_inner = room_inner.clone();
|
||||||
|
move |event| {
|
||||||
|
let room_events = room_events.clone();
|
||||||
|
let room_inner = room_inner.clone();
|
||||||
|
async move {
|
||||||
|
if room_inner.state.load(Ordering::SeqCst)
|
||||||
|
== ConnectionState::Connected as u8
|
||||||
|
{
|
||||||
|
if let Some(cb) = room_events.$type.lock().as_mut() {
|
||||||
|
cb(event).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
($type:ident) => {
|
||||||
|
p.internal_events().$type({
|
||||||
|
let room_events = room_events.clone();
|
||||||
|
move |event| {
|
||||||
|
let room_events = room_events.clone();
|
||||||
|
async move {
|
||||||
|
if let Some(cb) = room_events.$type.lock().as_mut() {
|
||||||
|
cb(event).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Forward participantevents to room events
|
// Forward participantevents to room events
|
||||||
p.internal_events().on_track_subscribed({
|
forward_event!(on_track_published, when_connected);
|
||||||
let room_events = room_events.clone();
|
forward_event!(on_track_subscribed);
|
||||||
let room_inner = room_inner.clone();
|
forward_event!(on_track_subscription_failed);
|
||||||
|
|
||||||
move |event| {
|
room_inner.participants.write().insert(sid, p.clone());
|
||||||
let room_events = room_events.clone();
|
|
||||||
let room_inner = room_inner.clone();
|
|
||||||
|
|
||||||
async move {
|
|
||||||
if let Some(cb) = room_events.clone().on_track_subscribed.lock().as_mut() {
|
|
||||||
cb(TrackSubscribedEvent {
|
|
||||||
room_handle: RoomHandle::from(room_inner.clone()),
|
|
||||||
track: event.track,
|
|
||||||
participant: event.participant,
|
|
||||||
publication: event.publication,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
room_inner
|
|
||||||
.participants
|
|
||||||
.write()
|
|
||||||
.insert(pi.sid.into(), p.clone());
|
|
||||||
p
|
p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RoomHandle {
|
pub struct RoomHandle {
|
||||||
inner: Arc<RoomInner>,
|
inner: Arc<RoomInner>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
|
use std::sync::Weak;
|
||||||
|
|
||||||
use crate::proto::{data_packet, DataPacket, UserPacket};
|
use crate::proto::{data_packet, DataPacket, UserPacket};
|
||||||
use crate::room::participant::{impl_participant_trait, ParticipantShared, ParticipantInternalTrait};
|
use crate::room::participant::{
|
||||||
use crate::room::RoomError;
|
impl_participant_trait, ParticipantInternalTrait, ParticipantShared,
|
||||||
|
};
|
||||||
|
use crate::room::{RoomError, RoomInner};
|
||||||
use crate::rtc_engine::RTCEngine;
|
use crate::rtc_engine::RTCEngine;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LocalParticipant {
|
pub struct LocalParticipant {
|
||||||
shared: ParticipantShared,
|
shared: ParticipantShared,
|
||||||
rtc_engine: Arc<RTCEngine>,
|
rtc_engine: Arc<RTCEngine>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LocalParticipant {
|
impl LocalParticipant {
|
||||||
pub(crate) fn new(rtc_engine: Arc<RTCEngine>, info: ParticipantInfo) -> Self {
|
pub(crate) fn new(
|
||||||
|
rtc_engine: Arc<RTCEngine>,
|
||||||
|
sid: ParticipantSid,
|
||||||
|
identity: ParticipantIdentity,
|
||||||
|
name: String,
|
||||||
|
metadata: String,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shared: ParticipantShared::new(
|
shared: ParticipantShared::new(sid, identity, name, metadata),
|
||||||
info.sid.into(),
|
|
||||||
info.identity.into(),
|
|
||||||
info.name,
|
|
||||||
info.metadata,
|
|
||||||
),
|
|
||||||
rtc_engine,
|
rtc_engine,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,16 +46,16 @@ impl LocalParticipant {
|
|||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn update_info(self: Arc<Self>, info: ParticipantInfo) {
|
|
||||||
self.shared.update_info(info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticipantInternalTrait for LocalParticipant {
|
impl ParticipantInternalTrait for LocalParticipant {
|
||||||
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
||||||
self.shared.internal_events.clone()
|
self.shared.internal_events.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_info(&self, info: ParticipantInfo) {
|
||||||
|
self.shared.update_info(info);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_participant_trait!(LocalParticipant);
|
impl_participant_trait!(LocalParticipant);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use crate::events::participant::ParticipantEvents;
|
use crate::events::ParticipantEvents;
|
||||||
use crate::proto::ParticipantInfo;
|
use crate::proto::ParticipantInfo;
|
||||||
use crate::room::id::{ParticipantIdentity, ParticipantSid, TrackSid};
|
use crate::room::id::{ParticipantIdentity, ParticipantSid, TrackSid};
|
||||||
use crate::room::participant::local_participant::LocalParticipant;
|
use crate::room::participant::local_participant::LocalParticipant;
|
||||||
use crate::room::participant::remote_participant::RemoteParticipant;
|
use crate::room::participant::remote_participant::RemoteParticipant;
|
||||||
use crate::room::publication::{TrackPublication, TrackPublicationTrait};
|
use crate::room::publication::{TrackPublication, TrackPublicationTrait};
|
||||||
use futures_util::future::BoxFuture;
|
|
||||||
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;
|
||||||
@@ -13,8 +12,7 @@ use std::sync::Arc;
|
|||||||
pub mod local_participant;
|
pub mod local_participant;
|
||||||
pub mod remote_participant;
|
pub mod remote_participant;
|
||||||
|
|
||||||
type OnTrackSubscribed = Box<dyn FnMut(ParticipantHandle) -> BoxFuture<'static, ()> + Send + Sync>;
|
#[derive(Debug)]
|
||||||
|
|
||||||
pub(super) struct ParticipantShared {
|
pub(super) struct ParticipantShared {
|
||||||
pub(super) events: Arc<ParticipantEvents>,
|
pub(super) events: Arc<ParticipantEvents>,
|
||||||
pub(super) internal_events: Arc<ParticipantEvents>,
|
pub(super) internal_events: Arc<ParticipantEvents>,
|
||||||
@@ -57,6 +55,7 @@ impl ParticipantShared {
|
|||||||
|
|
||||||
pub(crate) trait ParticipantInternalTrait {
|
pub(crate) trait ParticipantInternalTrait {
|
||||||
fn internal_events(&self) -> Arc<ParticipantEvents>;
|
fn internal_events(&self) -> Arc<ParticipantEvents>;
|
||||||
|
fn update_info(&self, info: ParticipantInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ParticipantTrait {
|
pub trait ParticipantTrait {
|
||||||
@@ -73,20 +72,11 @@ pub enum ParticipantHandle {
|
|||||||
Remote(Arc<RemoteParticipant>),
|
Remote(Arc<RemoteParticipant>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParticipantHandle {
|
|
||||||
// TODO(theomonnom): Add async support to wrap_variants ...
|
|
||||||
pub(crate) async fn update_info(&self, info: ParticipantInfo) {
|
|
||||||
match self {
|
|
||||||
Self::Local(inner) => inner.clone().update_info(info).await,
|
|
||||||
Self::Remote(inner) => inner.clone().update_info(info).await,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ParticipantInternalTrait for ParticipantHandle {
|
impl ParticipantInternalTrait for ParticipantHandle {
|
||||||
enum_dispatch!(
|
enum_dispatch!(
|
||||||
[Local, Remote]
|
[Local, Remote]
|
||||||
fnc!(internal_events, &Self, [], Arc<ParticipantEvents>);
|
fnc!(internal_events, &Self, [], Arc<ParticipantEvents>);
|
||||||
|
fnc!(update_info, &Self, [info: ParticipantInfo], ());
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +93,7 @@ impl ParticipantTrait for ParticipantHandle {
|
|||||||
|
|
||||||
macro_rules! impl_participant_trait {
|
macro_rules! impl_participant_trait {
|
||||||
($x:ty) => {
|
($x:ty) => {
|
||||||
use crate::events::participant::ParticipantEvents;
|
use crate::events::ParticipantEvents;
|
||||||
use crate::proto::ParticipantInfo;
|
use crate::proto::ParticipantInfo;
|
||||||
use crate::room::id::{ParticipantIdentity, ParticipantSid};
|
use crate::room::id::{ParticipantIdentity, ParticipantSid};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::events::participant::{
|
use crate::events::{
|
||||||
TrackPublishedEvent, TrackSubscribedEvent, TrackSubscriptionFailedEvent,
|
TrackError, TrackPublishedEvent, TrackSubscribedEvent, TrackSubscriptionFailedEvent,
|
||||||
};
|
};
|
||||||
use crate::events::TrackError;
|
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,
|
||||||
@@ -12,141 +12,35 @@ use crate::room::publication::{
|
|||||||
use crate::room::track::remote_audio_track::RemoteAudioTrack;
|
use crate::room::track::remote_audio_track::RemoteAudioTrack;
|
||||||
use crate::room::track::remote_track::RemoteTrackHandle;
|
use crate::room::track::remote_track::RemoteTrackHandle;
|
||||||
use crate::room::track::remote_video_track::RemoteVideoTrack;
|
use crate::room::track::remote_video_track::RemoteVideoTrack;
|
||||||
use crate::room::track::{TrackKind, TrackTrait, TrackHandle};
|
use crate::room::track::{TrackKind, TrackTrait};
|
||||||
|
use crate::room::{RoomHandle, RoomInner};
|
||||||
use livekit_webrtc::media_stream::MediaStreamTrackHandle;
|
use livekit_webrtc::media_stream::MediaStreamTrackHandle;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::time::{sleep, timeout};
|
use tokio::time::{sleep, timeout};
|
||||||
use tracing::{info, error};
|
use tracing::{debug, debug_span, error, instrument, Instrument, Level};
|
||||||
|
|
||||||
use super::ParticipantTrait;
|
use super::ParticipantTrait;
|
||||||
|
|
||||||
const ADD_TRACK_TIMEOUT: Duration = Duration::from_secs(5);
|
const ADD_TRACK_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RemoteParticipant {
|
pub struct RemoteParticipant {
|
||||||
shared: ParticipantShared,
|
shared: ParticipantShared,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoteParticipant {
|
impl RemoteParticipant {
|
||||||
pub(crate) fn new(info: ParticipantInfo) -> Self {
|
pub(crate) fn new(
|
||||||
|
sid: ParticipantSid,
|
||||||
|
identity: ParticipantIdentity,
|
||||||
|
name: String,
|
||||||
|
metadata: String,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
shared: ParticipantShared::new(
|
shared: ParticipantShared::new(sid, identity, name, metadata),
|
||||||
info.sid.into(),
|
|
||||||
info.identity.into(),
|
|
||||||
info.name,
|
|
||||||
info.metadata,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_subscribed_media_track(
|
|
||||||
self: Arc<Self>,
|
|
||||||
sid: TrackSid,
|
|
||||||
media_track: MediaStreamTrackHandle,
|
|
||||||
) {
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let wait_publication = {
|
|
||||||
let participant = self.clone();
|
|
||||||
let sid = sid.clone();
|
|
||||||
async move {
|
|
||||||
loop {
|
|
||||||
let publication = participant.get_track_publication(&sid);
|
|
||||||
if let Some(publication) = publication {
|
|
||||||
return publication;
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(50)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(remote_publication) = timeout(ADD_TRACK_TIMEOUT, wait_publication).await {
|
|
||||||
let track = match remote_publication.kind() {
|
|
||||||
TrackKind::Audio => {
|
|
||||||
if let MediaStreamTrackHandle::Audio(rtc_track) = media_track {
|
|
||||||
let audio_track = RemoteAudioTrack::new(
|
|
||||||
remote_publication.sid().into(),
|
|
||||||
remote_publication.name(),
|
|
||||||
rtc_track,
|
|
||||||
);
|
|
||||||
RemoteTrackHandle::Audio(Arc::new(audio_track))
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TrackKind::Video => {
|
|
||||||
if let MediaStreamTrackHandle::Video(rtc_track) = media_track {
|
|
||||||
let video_track = RemoteVideoTrack::new(
|
|
||||||
remote_publication.sid().into(),
|
|
||||||
remote_publication.name(),
|
|
||||||
rtc_track,
|
|
||||||
);
|
|
||||||
RemoteTrackHandle::Video(Arc::new(video_track))
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
info!("starting track: {:?}", sid);
|
|
||||||
|
|
||||||
remote_publication.update_track(Some(track.clone().into()));
|
|
||||||
self.shared
|
|
||||||
.add_track_publication(TrackPublication::Remote(remote_publication.clone()));
|
|
||||||
track.start();
|
|
||||||
|
|
||||||
let event = TrackSubscribedEvent {
|
|
||||||
track,
|
|
||||||
publication: remote_publication,
|
|
||||||
participant: self.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(cb) = self
|
|
||||||
.shared
|
|
||||||
.internal_events
|
|
||||||
.on_track_subscribed
|
|
||||||
.lock()
|
|
||||||
.as_mut()
|
|
||||||
{
|
|
||||||
cb(event.clone()).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cb) = self.shared.events.on_track_subscribed.lock().as_mut() {
|
|
||||||
cb(event).await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error!("could not find published track with sid: {:?}", sid);
|
|
||||||
|
|
||||||
let event = TrackSubscriptionFailedEvent {
|
|
||||||
sid: sid.clone(),
|
|
||||||
error: TrackError::TrackNotFound(sid.clone().to_string()),
|
|
||||||
participant: self.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(cb) = self
|
|
||||||
.shared
|
|
||||||
.internal_events
|
|
||||||
.on_track_subscription_failed
|
|
||||||
.lock()
|
|
||||||
.as_mut()
|
|
||||||
{
|
|
||||||
cb(event.clone()).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cb) = self
|
|
||||||
.shared
|
|
||||||
.events
|
|
||||||
.on_track_subscription_failed
|
|
||||||
.lock()
|
|
||||||
.as_mut()
|
|
||||||
{
|
|
||||||
cb(event).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_track_publication(&self, sid: &TrackSid) -> Option<RemoteTrackPublication> {
|
fn get_track_publication(&self, sid: &TrackSid) -> Option<RemoteTrackPublication> {
|
||||||
self.shared.tracks.read().get(sid).map(|track| {
|
self.shared.tracks.read().get(sid).map(|track| {
|
||||||
if let TrackPublication::Remote(remote) = track {
|
if let TrackPublication::Remote(remote) = track {
|
||||||
@@ -157,12 +51,125 @@ impl RemoteParticipant {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn update_info(self: Arc<Self>, info: ParticipantInfo) {
|
#[instrument(level = Level::DEBUG, skip(room_handle))]
|
||||||
self.shared.update_info(info.clone());
|
pub(crate) async fn add_subscribed_media_track(
|
||||||
|
self: Arc<Self>,
|
||||||
|
room_handle: RoomHandle,
|
||||||
|
sid: TrackSid,
|
||||||
|
media_track: MediaStreamTrackHandle,
|
||||||
|
) {
|
||||||
|
let wait_publication = {
|
||||||
|
let participant = self.clone();
|
||||||
|
let sid = sid.clone();
|
||||||
|
async move {
|
||||||
|
loop {
|
||||||
|
let publication = participant.get_track_publication(&sid);
|
||||||
|
if let Some(publication) = publication {
|
||||||
|
return publication;
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(remote_publication) = timeout(ADD_TRACK_TIMEOUT, wait_publication).await {
|
||||||
|
let track = match remote_publication.kind() {
|
||||||
|
TrackKind::Audio => {
|
||||||
|
if let MediaStreamTrackHandle::Audio(rtc_track) = media_track {
|
||||||
|
let audio_track = RemoteAudioTrack::new(
|
||||||
|
remote_publication.sid().into(),
|
||||||
|
remote_publication.name(),
|
||||||
|
rtc_track,
|
||||||
|
);
|
||||||
|
RemoteTrackHandle::Audio(Arc::new(audio_track))
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TrackKind::Video => {
|
||||||
|
if let MediaStreamTrackHandle::Video(rtc_track) = media_track {
|
||||||
|
let video_track = RemoteVideoTrack::new(
|
||||||
|
remote_publication.sid().into(),
|
||||||
|
remote_publication.name(),
|
||||||
|
rtc_track,
|
||||||
|
);
|
||||||
|
RemoteTrackHandle::Video(Arc::new(video_track))
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("starting track: {:?}", sid);
|
||||||
|
|
||||||
|
remote_publication.update_track(Some(track.clone().into()));
|
||||||
|
self.shared
|
||||||
|
.add_track_publication(TrackPublication::Remote(remote_publication.clone()));
|
||||||
|
track.start();
|
||||||
|
|
||||||
|
let event = TrackSubscribedEvent {
|
||||||
|
room_handle,
|
||||||
|
track,
|
||||||
|
publication: remote_publication,
|
||||||
|
participant: self.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cb) = self
|
||||||
|
.shared
|
||||||
|
.internal_events
|
||||||
|
.on_track_subscribed
|
||||||
|
.lock()
|
||||||
|
.as_mut()
|
||||||
|
{
|
||||||
|
cb(event.clone()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cb) = self.shared.events.on_track_subscribed.lock().as_mut() {
|
||||||
|
cb(event).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("could not find published track with sid: {:?}", sid);
|
||||||
|
|
||||||
|
let event = TrackSubscriptionFailedEvent {
|
||||||
|
room_handle,
|
||||||
|
sid: sid.clone(),
|
||||||
|
error: TrackError::TrackNotFound(sid.clone().to_string()),
|
||||||
|
participant: self.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cb) = self
|
||||||
|
.shared
|
||||||
|
.internal_events
|
||||||
|
.on_track_subscription_failed
|
||||||
|
.lock()
|
||||||
|
.as_mut()
|
||||||
|
{
|
||||||
|
cb(event.clone()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cb) = self
|
||||||
|
.shared
|
||||||
|
.events
|
||||||
|
.on_track_subscription_failed
|
||||||
|
.lock()
|
||||||
|
.as_mut()
|
||||||
|
{
|
||||||
|
cb(event).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(room_handle))]
|
||||||
|
pub(crate) async fn update_tracks(
|
||||||
|
self: Arc<Self>,
|
||||||
|
room_handle: RoomHandle,
|
||||||
|
tracks: Vec<TrackInfo>,
|
||||||
|
) {
|
||||||
let mut valid_tracks = HashSet::<TrackSid>::new();
|
let mut valid_tracks = HashSet::<TrackSid>::new();
|
||||||
|
|
||||||
for track in info.tracks {
|
for track in tracks {
|
||||||
if let Some(publication) = self.get_track_publication(&track.sid.clone().into()) {
|
if let Some(publication) = self.get_track_publication(&track.sid.clone().into()) {
|
||||||
publication.update_info(track.clone());
|
publication.update_info(track.clone());
|
||||||
} else {
|
} else {
|
||||||
@@ -172,6 +179,7 @@ impl RemoteParticipant {
|
|||||||
|
|
||||||
// This is a new track, fire publish events
|
// This is a new track, fire publish events
|
||||||
let event = TrackPublishedEvent {
|
let event = TrackPublishedEvent {
|
||||||
|
room_handle: room_handle.clone(),
|
||||||
participant: self.clone(),
|
participant: self.clone(),
|
||||||
publication: publication.clone(),
|
publication: publication.clone(),
|
||||||
};
|
};
|
||||||
@@ -200,6 +208,10 @@ impl ParticipantInternalTrait for RemoteParticipant {
|
|||||||
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
||||||
self.shared.internal_events.clone()
|
self.shared.internal_events.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_info(&self, info: ParticipantInfo) {
|
||||||
|
self.shared.update_info(info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_participant_trait!(RemoteParticipant);
|
impl_participant_trait!(RemoteParticipant);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub trait TrackPublicationTrait {
|
|||||||
fn simulcasted(&self) -> bool;
|
fn simulcasted(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(super) struct TrackPublicationShared {
|
pub(super) struct TrackPublicationShared {
|
||||||
pub(super) track: Mutex<Option<TrackHandle>>,
|
pub(super) track: Mutex<Option<TrackHandle>>,
|
||||||
pub(super) name: Mutex<String>,
|
pub(super) name: Mutex<String>,
|
||||||
@@ -75,7 +76,7 @@ impl TrackPublicationShared {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TrackPublication {
|
pub enum TrackPublication {
|
||||||
Local(LocalTrackPublication),
|
Local(LocalTrackPublication),
|
||||||
Remote(RemoteTrackPublication),
|
Remote(RemoteTrackPublication),
|
||||||
@@ -146,7 +147,7 @@ macro_rules! impl_publication_trait {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct LocalTrackPublication {
|
pub struct LocalTrackPublication {
|
||||||
shared: Arc<TrackPublicationShared>,
|
shared: Arc<TrackPublicationShared>,
|
||||||
}
|
}
|
||||||
@@ -161,7 +162,7 @@ impl LocalTrackPublication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RemoteTrackPublication {
|
pub struct RemoteTrackPublication {
|
||||||
shared: Arc<TrackPublicationShared>,
|
shared: Arc<TrackPublicationShared>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
pub struct TrackEvents {}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::room::track::{impl_track_trait, TrackShared};
|
use crate::room::track::{impl_track_trait, TrackShared};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LocalAudioTrack {
|
pub struct LocalAudioTrack {
|
||||||
shared: TrackShared,
|
shared: TrackShared,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::room::track::{impl_track_trait, TrackShared};
|
use crate::room::track::{impl_track_trait, TrackShared};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LocalVideoTrack {
|
pub struct LocalVideoTrack {
|
||||||
shared: TrackShared,
|
shared: TrackShared,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ use std::sync::atomic::AtomicU8;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub mod audio_track;
|
pub mod audio_track;
|
||||||
pub mod events;
|
|
||||||
pub mod local_audio_track;
|
pub mod local_audio_track;
|
||||||
pub mod local_track;
|
pub mod local_track;
|
||||||
pub mod local_video_track;
|
pub mod local_video_track;
|
||||||
@@ -97,6 +96,7 @@ impl From<ProtoTrackSource> for TrackSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TrackDimension(pub u32, pub u32);
|
pub struct TrackDimension(pub u32, pub u32);
|
||||||
|
|
||||||
pub trait TrackTrait {
|
pub trait TrackTrait {
|
||||||
@@ -108,6 +108,7 @@ pub trait TrackTrait {
|
|||||||
fn stop(&self);
|
fn stop(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(super) struct TrackShared {
|
pub(super) struct TrackShared {
|
||||||
pub(super) sid: Mutex<TrackSid>,
|
pub(super) sid: Mutex<TrackSid>,
|
||||||
pub(super) name: Mutex<String>,
|
pub(super) name: Mutex<String>,
|
||||||
@@ -141,7 +142,7 @@ impl TrackShared {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum TrackHandle {
|
pub enum TrackHandle {
|
||||||
LocalVideo(Arc<LocalVideoTrack>),
|
LocalVideo(Arc<LocalVideoTrack>),
|
||||||
LocalAudio(Arc<LocalAudioTrack>),
|
LocalAudio(Arc<LocalAudioTrack>),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::room::track::{impl_track_trait, TrackShared};
|
|||||||
use livekit_webrtc::media_stream::{AudioTrack, MediaStreamTrackHandle};
|
use livekit_webrtc::media_stream::{AudioTrack, MediaStreamTrackHandle};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RemoteAudioTrack {
|
pub struct RemoteAudioTrack {
|
||||||
shared: TrackShared,
|
shared: TrackShared,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use livekit_utils::enum_dispatch;
|
|||||||
|
|
||||||
use super::TrackTrait;
|
use super::TrackTrait;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RemoteTrackHandle {
|
pub enum RemoteTrackHandle {
|
||||||
Audio(Arc<RemoteAudioTrack>),
|
Audio(Arc<RemoteAudioTrack>),
|
||||||
Video(Arc<RemoteVideoTrack>),
|
Video(Arc<RemoteVideoTrack>),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::room::track::{impl_track_trait, TrackShared};
|
use crate::room::track::{impl_track_trait, TrackShared};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RemoteVideoTrack {
|
pub struct RemoteVideoTrack {
|
||||||
shared: TrackShared,
|
shared: TrackShared,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use std::error;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -10,7 +11,7 @@ use prost::Message;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::{debug, error, info, trace};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
use crate::{proto, signal_client};
|
use crate::{proto, signal_client};
|
||||||
use livekit_webrtc::data_channel::{DataChannel, DataChannelInit, DataSendError, DataState};
|
use livekit_webrtc::data_channel::{DataChannel, DataChannelInit, DataSendError, DataState};
|
||||||
@@ -46,6 +47,10 @@ pub(crate) type EngineEmitter = mpsc::Sender<EngineEvent>;
|
|||||||
pub(crate) type EngineEvents = mpsc::Receiver<EngineEvent>;
|
pub(crate) type EngineEvents = mpsc::Receiver<EngineEvent>;
|
||||||
pub(crate) type EngineResult<T> = Result<T, EngineError>;
|
pub(crate) type EngineResult<T> = Result<T, EngineError>;
|
||||||
|
|
||||||
|
// TODO(theomonnom): Smarter retry intervals
|
||||||
|
pub(crate) const RECONNECT_ATTEMPTS: u32 = 10;
|
||||||
|
pub(crate) const RECONNECT_INTERVAL: Duration = Duration::from_millis(300);
|
||||||
|
|
||||||
pub(crate) const MAX_ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
|
pub(crate) const MAX_ICE_CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
|
||||||
pub(crate) const LOSSY_DC_LABEL: &str = "_lossy";
|
pub(crate) const LOSSY_DC_LABEL: &str = "_lossy";
|
||||||
pub(crate) const RELIABLE_DC_LABEL: &str = "_reliable";
|
pub(crate) const RELIABLE_DC_LABEL: &str = "_reliable";
|
||||||
@@ -69,7 +74,7 @@ struct IceCandidateJSON {
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum EngineError {
|
pub enum EngineError {
|
||||||
#[error("signal failure")]
|
#[error("signal failure: {0}")]
|
||||||
Signal(#[from] SignalError),
|
Signal(#[from] SignalError),
|
||||||
#[error("internal webrtc failure")]
|
#[error("internal webrtc failure")]
|
||||||
Rtc(#[from] RTCError),
|
Rtc(#[from] RTCError),
|
||||||
@@ -94,13 +99,25 @@ pub(crate) enum EngineEvent {
|
|||||||
rtp_receiver: RtpReceiver,
|
rtp_receiver: RtpReceiver,
|
||||||
streams: Vec<MediaStream>,
|
streams: Vec<MediaStream>,
|
||||||
},
|
},
|
||||||
|
Connected,
|
||||||
|
Resuming,
|
||||||
|
Resumed,
|
||||||
|
SignalResumed,
|
||||||
|
Restarting,
|
||||||
|
Restarted,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct EngineInner {
|
struct EngineInner {
|
||||||
has_published: AtomicBool,
|
// Join infornation
|
||||||
|
url: String,
|
||||||
|
token: Mutex<String>, // The token is refreshed periodically
|
||||||
|
options: Mutex<SignalOptions>,
|
||||||
join_response: Mutex<JoinResponse>,
|
join_response: Mutex<JoinResponse>,
|
||||||
|
|
||||||
|
has_published: AtomicBool,
|
||||||
pc_state: AtomicU8, // Casted to PCState enum
|
pc_state: AtomicU8, // Casted to PCState enum
|
||||||
|
reconnecting: AtomicBool,
|
||||||
|
|
||||||
publisher_pc: AsyncMutex<PCTransport>,
|
publisher_pc: AsyncMutex<PCTransport>,
|
||||||
subscriber_pc: AsyncMutex<PCTransport>,
|
subscriber_pc: AsyncMutex<PCTransport>,
|
||||||
@@ -109,12 +126,13 @@ struct EngineInner {
|
|||||||
// Used to send data to other participants ( The SFU forward the messages )
|
// Used to send data to other participants ( The SFU forward the messages )
|
||||||
lossy_dc: Mutex<DataChannel>,
|
lossy_dc: Mutex<DataChannel>,
|
||||||
reliable_dc: Mutex<DataChannel>,
|
reliable_dc: Mutex<DataChannel>,
|
||||||
|
|
||||||
// Subscriber data channels
|
// Subscriber data channels
|
||||||
// These fields are never used, we just keep a strong reference to them,
|
// These fields are never used, we just keep a strong reference to them,
|
||||||
// so we can receive data from other participants
|
// so we can receive data from other participants
|
||||||
sub_reliable_dc: Mutex<Option<DataChannel>>,
|
sub_reliable_dc: Mutex<Option<DataChannel>>,
|
||||||
sub_lossy_dc: Mutex<Option<DataChannel>>,
|
sub_lossy_dc: Mutex<Option<DataChannel>>,
|
||||||
|
|
||||||
|
closed: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -126,7 +144,72 @@ pub struct RTCEngine {
|
|||||||
lk_runtime: Arc<LKRuntime>, // Keep a reference while we're using the RTCEngine
|
lk_runtime: Arc<LKRuntime>, // Keep a reference while we're using the RTCEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EngineInner {
|
||||||
|
async fn ensure_publisher_connected(&self, kind: data_packet::Kind) -> EngineResult<()> {
|
||||||
|
if !self.join_response.lock().subscriber_primary {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let publisher = &self.publisher_pc;
|
||||||
|
{
|
||||||
|
let mut publisher = publisher.lock().await;
|
||||||
|
if !publisher.is_connected()
|
||||||
|
&& publisher.peer_connection().ice_connection_state()
|
||||||
|
!= IceConnectionState::IceConnectionChecking
|
||||||
|
{
|
||||||
|
let _ = self.negotiate_publisher().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dc = self.data_channel(kind);
|
||||||
|
if dc.lock().state() == DataState::Open {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until the PeerConnection is connected
|
||||||
|
let wait_connected = async move {
|
||||||
|
while publisher.lock().await.is_connected() && dc.lock().state() == DataState::Open {
|
||||||
|
sleep(Duration::from_millis(50)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = wait_connected => Ok(()),
|
||||||
|
_ = sleep(MAX_ICE_CONNECT_TIMEOUT) => {
|
||||||
|
let err = EngineError::Connection("could not establish publisher connection: timeout".to_string());
|
||||||
|
error!(error = ?err);
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn negotiate_publisher(&self) -> EngineResult<()> {
|
||||||
|
self.has_published.store(true, Ordering::SeqCst);
|
||||||
|
if let Err(err) = self.publisher_pc.lock().await.negotiate().await {
|
||||||
|
error!("failed to negotiate the publisher: {:?}", err);
|
||||||
|
Err(err)?
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_channel(&self, kind: data_packet::Kind) -> &Mutex<DataChannel> {
|
||||||
|
if kind == data_packet::Kind::Reliable {
|
||||||
|
&self.reliable_dc
|
||||||
|
} else {
|
||||||
|
&self.lossy_dc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RTCEngine {
|
impl RTCEngine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
|
||||||
|
Self {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(url, token))]
|
#[tracing::instrument(skip(url, token))]
|
||||||
pub(crate) async fn connect(
|
pub(crate) async fn connect(
|
||||||
url: &str,
|
url: &str,
|
||||||
@@ -172,16 +255,16 @@ impl RTCEngine {
|
|||||||
emitter.clone(),
|
emitter.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
if !join_response.subscriber_primary {
|
||||||
|
engine_inner.negotiate_publisher().await?;
|
||||||
|
}
|
||||||
|
|
||||||
let rtc_engine = Self {
|
let rtc_engine = Self {
|
||||||
signal_client,
|
signal_client,
|
||||||
engine_inner,
|
engine_inner,
|
||||||
lk_runtime,
|
lk_runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
if !join_response.subscriber_primary {
|
|
||||||
rtc_engine.negotiate_publisher().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((rtc_engine, events))
|
Ok((rtc_engine, events))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,8 +274,9 @@ impl RTCEngine {
|
|||||||
data: &DataPacket,
|
data: &DataPacket,
|
||||||
kind: data_packet::Kind,
|
kind: data_packet::Kind,
|
||||||
) -> Result<(), EngineError> {
|
) -> Result<(), EngineError> {
|
||||||
self.ensure_publisher_connected(kind).await?;
|
self.engine_inner.ensure_publisher_connected(kind).await?;
|
||||||
self.data_channel(kind)
|
self.engine_inner
|
||||||
|
.data_channel(kind)
|
||||||
.lock()
|
.lock()
|
||||||
.send(&data.encode_to_vec(), true)
|
.send(&data.encode_to_vec(), true)
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
@@ -244,7 +328,11 @@ impl RTCEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SignalEvent::Close => {
|
SignalEvent::Close => {
|
||||||
// Try reconnect if this isn't expected
|
Self::handle_disconnected(
|
||||||
|
signal_client.clone(),
|
||||||
|
engine_inner.clone(),
|
||||||
|
emitter.clone(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,23 +367,23 @@ impl RTCEngine {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
RTCEvent::ConnectionChange { state, target } => {
|
RTCEvent::ConnectionChange { state, target } => {
|
||||||
// Reconnect if we've been disconnected unexpectedly
|
trace!("connection change, {:?} {:?}", state, target);
|
||||||
trace!("Connection change, {:?} {:?}", state, target);
|
|
||||||
let subscriber_primary = engine_inner.join_response.lock().subscriber_primary;
|
let subscriber_primary = engine_inner.join_response.lock().subscriber_primary;
|
||||||
let is_primary = subscriber_primary && target == SignalTarget::Subscriber;
|
let is_primary = subscriber_primary && target == SignalTarget::Subscriber;
|
||||||
|
|
||||||
if is_primary && state == PeerConnectionState::Disconnected {
|
if is_primary && state == PeerConnectionState::Connected {
|
||||||
let old_state = engine_inner
|
let old_state = engine_inner
|
||||||
.pc_state
|
.pc_state
|
||||||
.swap(PCState::Connected as u8, Ordering::SeqCst);
|
.swap(PCState::Connected as u8, Ordering::SeqCst);
|
||||||
if old_state == PCState::New as u8 {
|
if old_state == PCState::New as u8 {
|
||||||
// TODO(theomonnom) Handle disconnect
|
let _ = emitter.send(EngineEvent::Connected).await; // First time connected
|
||||||
}
|
}
|
||||||
} else if state == PeerConnectionState::Failed {
|
} else if state == PeerConnectionState::Failed {
|
||||||
engine_inner
|
engine_inner
|
||||||
.pc_state
|
.pc_state
|
||||||
.store(PCState::Disconnected as u8, Ordering::SeqCst);
|
.store(PCState::Disconnected as u8, Ordering::SeqCst);
|
||||||
// TODO(theomonnom) Handle disconnect
|
|
||||||
|
Self::handle_disconnected(signal_client, engine_inner, emitter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RTCEvent::DataChannel {
|
RTCEvent::DataChannel {
|
||||||
@@ -449,30 +537,131 @@ impl RTCEngine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_publisher_connected(&self, kind: data_packet::Kind) -> EngineResult<()> {
|
async fn handle_disconnected(
|
||||||
if !self.join_response().subscriber_primary {
|
signal_client: Arc<SignalClient>,
|
||||||
return Ok(());
|
engine_inner: Arc<EngineInner>,
|
||||||
}
|
emitter: EngineEmitter,
|
||||||
|
) {
|
||||||
let publisher = &self.engine_inner.publisher_pc;
|
if engine_inner.closed.load(Ordering::SeqCst)
|
||||||
|
|| engine_inner.reconnecting.load(Ordering::SeqCst)
|
||||||
{
|
{
|
||||||
let mut publisher = publisher.lock().await;
|
return;
|
||||||
if !publisher.is_connected()
|
}
|
||||||
&& publisher.peer_connection().ice_connection_state()
|
|
||||||
!= IceConnectionState::IceConnectionChecking
|
engine_inner.reconnecting.store(true, Ordering::SeqCst);
|
||||||
{
|
warn!("RTCEngine disconnected unexpectedly, reconnecting...");
|
||||||
let _ = self.negotiate_publisher().await;
|
|
||||||
|
let mut full_reconnect = false;
|
||||||
|
for i in 0..RECONNECT_ATTEMPTS {
|
||||||
|
if full_reconnect {
|
||||||
|
if i == 0 {
|
||||||
|
let _ = emitter.send(EngineEvent::Restarting).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("restarting connection... attempt: {}", i);
|
||||||
|
if let Err(err) = Self::try_restart_connection(
|
||||||
|
signal_client.clone(),
|
||||||
|
engine_inner.clone(),
|
||||||
|
emitter.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("restarting connection failed: {}", err);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if i == 0 {
|
||||||
|
let _ = emitter.send(EngineEvent::Resuming).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("resuming connection... attempt: {}", i);
|
||||||
|
if let Err(err) = Self::try_resume_connection(
|
||||||
|
signal_client.clone(),
|
||||||
|
engine_inner.clone(),
|
||||||
|
emitter.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("resuming connection failed: {}", err);
|
||||||
|
if let EngineError::Signal(_) = err {
|
||||||
|
full_reconnect = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokio::time::sleep(RECONNECT_INTERVAL).await;
|
||||||
|
}
|
||||||
|
error!("failed to reconnect after {} attemps", RECONNECT_ATTEMPTS);
|
||||||
|
engine_inner.reconnecting.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// TODO DISCONNECT
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_restart_connection(
|
||||||
|
signal_client: Arc<SignalClient>,
|
||||||
|
engine_inner: Arc<EngineInner>,
|
||||||
|
emitter: EngineEmitter,
|
||||||
|
) -> EngineResult<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_resume_connection(
|
||||||
|
signal_client: Arc<SignalClient>,
|
||||||
|
engine_inner: Arc<EngineInner>,
|
||||||
|
emitter: EngineEmitter,
|
||||||
|
) -> EngineResult<()> {
|
||||||
|
let mut options = engine_inner.options.lock().clone();
|
||||||
|
options.sid = engine_inner
|
||||||
|
.join_response
|
||||||
|
.lock()
|
||||||
|
.participant
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.sid
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
signal_client
|
||||||
|
.reconnect(
|
||||||
|
&engine_inner.url,
|
||||||
|
&engine_inner.token.lock().clone(),
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let _ = emitter.send(EngineEvent::SignalResumed).await;
|
||||||
|
|
||||||
|
engine_inner
|
||||||
|
.subscriber_pc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.prepare_ice_restart();
|
||||||
|
|
||||||
|
if engine_inner.has_published.load(Ordering::SeqCst) {
|
||||||
|
engine_inner
|
||||||
|
.publisher_pc
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.create_and_send_offer(RTCOfferAnswerOptions {
|
||||||
|
ice_restart: true,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let dc = self.data_channel(kind);
|
Self::wait_pc_connection(engine_inner).await?;
|
||||||
if dc.lock().state() == DataState::Open {
|
signal_client.flush_queue().await;
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the PeerConnection is connected
|
let _ = emitter.send(EngineEvent::Resumed);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_pc_connection(engine_inner: Arc<EngineInner>) -> EngineResult<()> {
|
||||||
let wait_connected = async move {
|
let wait_connected = async move {
|
||||||
while publisher.lock().await.is_connected() && dc.lock().state() == DataState::Open {
|
while engine_inner.pc_state.load(Ordering::SeqCst) != PCState::Connected as u8 {
|
||||||
sleep(Duration::from_millis(50)).await;
|
sleep(Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -480,30 +669,14 @@ impl RTCEngine {
|
|||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = wait_connected => Ok(()),
|
_ = wait_connected => Ok(()),
|
||||||
_ = sleep(MAX_ICE_CONNECT_TIMEOUT) => {
|
_ = sleep(MAX_ICE_CONNECT_TIMEOUT) => {
|
||||||
let err = EngineError::Connection("could not establish publisher connection: timeout".to_string());
|
let err = EngineError::Connection("wait_pc_connection timed out".to_string());
|
||||||
error!(error = ?err);
|
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn negotiate_publisher(&self) -> EngineResult<()> {
|
fn close(&self) {
|
||||||
self.engine_inner
|
// TODO
|
||||||
.has_published
|
|
||||||
.store(true, Ordering::SeqCst);
|
|
||||||
if let Err(err) = self
|
|
||||||
.engine_inner
|
|
||||||
.publisher_pc
|
|
||||||
.lock()
|
|
||||||
.await
|
|
||||||
.negotiate()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!("failed to negotiate the publisher: {:?}", err);
|
|
||||||
Err(err)?
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_engine(
|
fn configure_engine(
|
||||||
@@ -617,16 +790,9 @@ impl RTCEngine {
|
|||||||
reliable_dc: Mutex::new(reliable_dc),
|
reliable_dc: Mutex::new(reliable_dc),
|
||||||
sub_lossy_dc: Mutex::new(None),
|
sub_lossy_dc: Mutex::new(None),
|
||||||
sub_reliable_dc: Mutex::new(None),
|
sub_reliable_dc: Mutex::new(None),
|
||||||
|
closed: AtomicBool::new(false),
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_channel(&self, kind: data_packet::Kind) -> &Mutex<DataChannel> {
|
|
||||||
if kind == data_packet::Kind::Reliable {
|
|
||||||
&self.engine_inner.reliable_dc
|
|
||||||
} else {
|
|
||||||
&self.engine_inner.lossy_dc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ pub type OnOfferHandler = Box<
|
|||||||
+ Sync,
|
+ Sync,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub struct PCTransport {
|
pub(crate) struct PCTransport {
|
||||||
peer_connection: PeerConnection,
|
peer_connection: PeerConnection,
|
||||||
pending_candidates: Vec<IceCandidate>,
|
pending_candidates: Vec<IceCandidate>,
|
||||||
on_offer_handler: Option<OnOfferHandler>,
|
on_offer_handler: Option<OnOfferHandler>,
|
||||||
restarting_ice: bool,
|
|
||||||
renegotiate: bool,
|
renegotiate: bool,
|
||||||
|
restarting_ice: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for PCTransport {
|
impl Debug for PCTransport {
|
||||||
@@ -58,7 +58,11 @@ impl PCTransport {
|
|||||||
self.on_offer_handler = Some(handler);
|
self.on_offer_handler = Some(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
pub fn prepare_ice_restart(&mut self) {
|
||||||
|
self.restarting_ice = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(level = Level::DEBUG)]
|
||||||
pub async fn add_ice_candidate(&mut self, ice_candidate: IceCandidate) -> Result<(), RTCError> {
|
pub async fn add_ice_candidate(&mut self, ice_candidate: IceCandidate) -> Result<(), RTCError> {
|
||||||
if self.peer_connection.remote_description().is_none() {
|
if self.peer_connection.remote_description().is_none() {
|
||||||
self.pending_candidates.push(ice_candidate);
|
self.pending_candidates.push(ice_candidate);
|
||||||
@@ -71,7 +75,7 @@ impl PCTransport {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(level = Level::DEBUG)]
|
||||||
pub async fn set_remote_description(
|
pub async fn set_remote_description(
|
||||||
&mut self,
|
&mut self,
|
||||||
remote_description: SessionDescription,
|
remote_description: SessionDescription,
|
||||||
@@ -94,15 +98,15 @@ impl PCTransport {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(level = Level::DEBUG)]
|
||||||
pub async fn negotiate(&mut self) -> Result<(), RTCError> {
|
pub async fn negotiate(&mut self) -> Result<(), RTCError> {
|
||||||
// TODO(theomonnom) Debounce here with NEGOTIATION_FREQUENCY
|
// TODO(theomonnom) Debounce here with NEGOTIATION_FREQUENCY
|
||||||
self.create_and_send_offer(RTCOfferAnswerOptions::default())
|
self.create_and_send_offer(RTCOfferAnswerOptions::default())
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument]
|
#[tracing::instrument(level = Level::DEBUG)]
|
||||||
async fn create_and_send_offer(
|
pub async fn create_and_send_offer(
|
||||||
&mut self,
|
&mut self,
|
||||||
options: RTCOfferAnswerOptions,
|
options: RTCOfferAnswerOptions,
|
||||||
) -> Result<(), RTCError> {
|
) -> Result<(), RTCError> {
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::sync::RwLockWriteGuard;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use livekit_webrtc::peer_connection_factory::{
|
use livekit_webrtc::peer_connection_factory::{
|
||||||
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
||||||
};
|
};
|
||||||
|
use parking_lot::RwLock;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_tungstenite::tungstenite::Error as WsError;
|
use tokio_tungstenite::tungstenite::Error as WsError;
|
||||||
|
|
||||||
use crate::proto::{signal_request, signal_response, JoinResponse};
|
use crate::proto::{signal_request, signal_response, JoinResponse};
|
||||||
use crate::signal_client::signal_stream::SignalStream;
|
use crate::signal_client::signal_stream::SignalStream;
|
||||||
|
use tracing::{instrument, Level};
|
||||||
|
|
||||||
mod signal_stream;
|
mod signal_stream;
|
||||||
|
|
||||||
@@ -21,7 +24,7 @@ pub const JOIN_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum SignalError {
|
pub enum SignalError {
|
||||||
#[error("websocket failure")]
|
#[error("ws failure: {0}")]
|
||||||
WsError(#[from] WsError),
|
WsError(#[from] WsError),
|
||||||
#[error("failed to parse the url")]
|
#[error("failed to parse the url")]
|
||||||
UrlParse(#[from] url::ParseError),
|
UrlParse(#[from] url::ParseError),
|
||||||
@@ -39,12 +42,12 @@ pub(crate) enum SignalEvent {
|
|||||||
Close,
|
Close,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct SignalOptions {
|
pub(crate) struct SignalOptions {
|
||||||
reconnect: bool,
|
pub(crate) reconnect: bool,
|
||||||
auto_subscribe: bool,
|
pub(crate) sid: String,
|
||||||
sid: String,
|
pub auto_subscribe: bool,
|
||||||
adaptive_stream: bool,
|
pub adaptive_stream: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SignalOptions {
|
impl Default for SignalOptions {
|
||||||
@@ -60,32 +63,59 @@ impl Default for SignalOptions {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SignalClient {
|
pub struct SignalClient {
|
||||||
stream: SignalStream,
|
stream: RwLock<Option<SignalStream>>,
|
||||||
emitter: SignalEmitter,
|
emitter: SignalEmitter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignalClient {
|
impl SignalClient {
|
||||||
|
pub fn new() -> (Self, SignalEvents) {
|
||||||
|
let (emitter, events) = mpsc::channel(8);
|
||||||
|
(
|
||||||
|
Self {
|
||||||
|
stream: Default::default(),
|
||||||
|
emitter,
|
||||||
|
},
|
||||||
|
events,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(url, token, options))]
|
||||||
pub(crate) async fn connect(
|
pub(crate) async fn connect(
|
||||||
|
&self,
|
||||||
url: &str,
|
url: &str,
|
||||||
token: &str,
|
token: &str,
|
||||||
options: SignalOptions,
|
options: SignalOptions,
|
||||||
) -> SignalResult<(Self, SignalEvents)> {
|
) -> SignalResult<()> {
|
||||||
let (emitter, events) = mpsc::channel(8);
|
let stream = SignalStream::connect(url, token, options, self.emitter.clone()).await?;
|
||||||
let stream = SignalStream::connect(url, token, options, emitter.clone()).await?;
|
*self.stream.write() = Some(stream);
|
||||||
|
Ok(())
|
||||||
// TODO(theomonnom) Retry initial connection
|
|
||||||
|
|
||||||
Ok((Self { stream, emitter }, events))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send(&self, signal: signal_request::Message) {
|
#[instrument(level = Level::DEBUG)]
|
||||||
if let Err(_) = self.stream.send(signal).await {
|
pub async fn close(&self) {
|
||||||
// TODO(theomonnom) Queue message ( Ignore on full reconnect )
|
if let Some(stream) = self.stream.write().take() {
|
||||||
|
stream.close().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reconnect(&self) {
|
#[instrument(level = Level::DEBUG)]
|
||||||
// TODO(theomonnom) Close & recreate SignalStream, also send the queue if needed
|
pub async fn send(&self, signal: signal_request::Message) {
|
||||||
|
if let Some(stream) = self.stream.read().as_ref() {
|
||||||
|
if stream.send(signal).await.is_ok() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(theomonnom): enqueue message
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clear_queue(&self) {
|
||||||
|
// TODO(theomonnom): impl
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG)]
|
||||||
|
pub async fn flush_queue(&self) {
|
||||||
|
// TODO(theomonnom): impl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +145,9 @@ pub mod utils {
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
use tokio_tungstenite::tungstenite::Error as WsError;
|
use tokio_tungstenite::tungstenite::Error as WsError;
|
||||||
use tracing::{event, Level};
|
use tracing::{event, instrument, Level};
|
||||||
|
|
||||||
|
#[instrument(level = Level::DEBUG, skip(receiver))]
|
||||||
pub(crate) async fn next_join_response(
|
pub(crate) async fn next_join_response(
|
||||||
receiver: &mut mpsc::Receiver<SignalEvent>,
|
receiver: &mut mpsc::Receiver<SignalEvent>,
|
||||||
) -> SignalResult<JoinResponse> {
|
) -> SignalResult<JoinResponse> {
|
||||||
|
|||||||
@@ -43,4 +43,4 @@ rtc::Thread* RTCRuntime::signaling_thread() const {
|
|||||||
std::shared_ptr<RTCRuntime> create_rtc_runtime() {
|
std::shared_ptr<RTCRuntime> create_rtc_runtime() {
|
||||||
return std::make_shared<RTCRuntime>();
|
return std::make_shared<RTCRuntime>();
|
||||||
}
|
}
|
||||||
} // namespace livekit
|
} // namespace livekit
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use cxx::UniquePtr;
|
use cxx::UniquePtr;
|
||||||
use libwebrtc_sys::media_stream as sys_ms;
|
use libwebrtc_sys::media_stream as sys_ms;
|
||||||
use libwebrtc_sys::MEDIA_TYPE_VIDEO;
|
use libwebrtc_sys::MEDIA_TYPE_VIDEO;
|
||||||
|
use livekit_utils::enum_dispatch;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -25,17 +26,6 @@ pub enum MediaStreamTrackHandle {
|
|||||||
Video(Arc<VideoTrack>),
|
Video(Arc<VideoTrack>),
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! shared_getter {
|
|
||||||
($x:ident, $ret:ty) => {
|
|
||||||
fn $x(&self) -> $ret {
|
|
||||||
match self {
|
|
||||||
Self::Video(inner) => inner.$x(),
|
|
||||||
Self::Audio(inner) => inner.$x(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MediaStreamTrackHandle {
|
impl MediaStreamTrackHandle {
|
||||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::MediaStreamTrack>) -> Self {
|
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::MediaStreamTrack>) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -66,17 +56,14 @@ impl Debug for MediaStreamTrackHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MediaStreamTrackTrait for MediaStreamTrackHandle {
|
impl MediaStreamTrackTrait for MediaStreamTrackHandle {
|
||||||
shared_getter!(kind, String);
|
enum_dispatch!(
|
||||||
shared_getter!(id, String);
|
[Audio, Video]
|
||||||
shared_getter!(enabled, bool);
|
fnc!(kind, &Self, [], String);
|
||||||
shared_getter!(state, TrackState);
|
fnc!(id, &Self, [], String);
|
||||||
|
fnc!(enabled, &Self, [], bool);
|
||||||
fn set_enabled(&self, enabled: bool) -> bool {
|
fnc!(state, &Self, [], TrackState);
|
||||||
match self {
|
fnc!(set_enabled, &Self, [enabled: bool], bool);
|
||||||
Self::Video(inner) => inner.set_enabled(enabled),
|
);
|
||||||
Self::Audio(inner) => inner.set_enabled(enabled),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AudioTrack {
|
pub struct AudioTrack {
|
||||||
|
|||||||
Generated
+238
-201
File diff suppressed because it is too large
Load Diff
@@ -16,4 +16,3 @@ parking_lot = "0.12.1"
|
|||||||
egui = { git = "https://github.com/emilk/egui" }
|
egui = { git = "https://github.com/emilk/egui" }
|
||||||
egui-wgpu = { git = "https://github.com/emilk/egui", features = ["winit"] }
|
egui-wgpu = { git = "https://github.com/emilk/egui", features = ["winit"] }
|
||||||
egui-winit = { git = "https://github.com/emilk/egui" }
|
egui-winit = { git = "https://github.com/emilk/egui" }
|
||||||
egui_demo_lib = { git = "https://github.com/emilk/egui" }
|
|
||||||
|
|||||||
+105
-29
@@ -1,7 +1,8 @@
|
|||||||
use crate::events::DemoEvent;
|
use crate::events::UiCmd;
|
||||||
use crate::video_grid::VideoGrid;
|
|
||||||
use crate::video_renderer::VideoRenderer;
|
use crate::video_renderer::VideoRenderer;
|
||||||
|
use crate::{events::AsyncCmd, video_grid::VideoGrid};
|
||||||
use egui_wgpu::WgpuConfiguration;
|
use egui_wgpu::WgpuConfiguration;
|
||||||
|
use livekit::room::track::remote_track::RemoteTrackHandle;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
@@ -9,10 +10,11 @@ use std::sync::{
|
|||||||
};
|
};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use livekit::room::Room;
|
use livekit::room::{ConnectionState, Room, RoomError};
|
||||||
|
|
||||||
const URL: &str = "ws://localhost:7880";
|
// Useful default constants for developing
|
||||||
const TOKEN : &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY0NzMsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NjQ4MDY0NzMsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.BgVdBnq3XFD3_BQHoe1azqjifYysubgFl6Qlzu9IQGI";
|
const DEFAULT_URL: &str = "ws://localhost:7880";
|
||||||
|
const DEFAULT_TOKEN : &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY0NzMsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NjQ4MDY0NzMsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.BgVdBnq3XFD3_BQHoe1azqjifYysubgFl6Qlzu9IQGI";
|
||||||
|
|
||||||
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY3MzAsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJ3ZWIiLCJuYmYiOjE2NjQ4MDY3MzAsInN1YiI6IndlYiIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.VbDoULjX1CVGZu2sPy3SvWYlVZUBXxQVPmdB9BnmlN4
|
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY3MzAsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJ3ZWIiLCJuYmYiOjE2NjQ4MDY3MzAsInN1YiI6IndlYiIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.VbDoULjX1CVGZu2sPy3SvWYlVZUBXxQVPmdB9BnmlN4
|
||||||
|
|
||||||
@@ -30,16 +32,19 @@ struct AppState {
|
|||||||
struct App {
|
struct App {
|
||||||
state: Arc<AppState>,
|
state: Arc<AppState>,
|
||||||
|
|
||||||
renderers: Vec<VideoRenderer>,
|
video_renderers: Vec<VideoRenderer>,
|
||||||
egui_context: egui::Context,
|
egui_context: egui::Context,
|
||||||
egui_state: egui_winit::State,
|
egui_state: egui_winit::State,
|
||||||
egui_painter: egui_wgpu::winit::Painter,
|
egui_painter: egui_wgpu::winit::Painter,
|
||||||
window: winit::window::Window,
|
window: winit::window::Window,
|
||||||
event_tx: mpsc::UnboundedSender<DemoEvent>,
|
cmd_tx: mpsc::UnboundedSender<AsyncCmd>,
|
||||||
|
cmd_rx: mpsc::UnboundedReceiver<UiCmd>,
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
lk_url: String,
|
lk_url: String,
|
||||||
lk_token: String,
|
lk_token: String,
|
||||||
|
connection_failure: Option<String>,
|
||||||
|
room_state: ConnectionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(rt: tokio::runtime::Runtime) {
|
pub fn run(rt: tokio::runtime::Runtime) {
|
||||||
@@ -58,8 +63,8 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
|||||||
egui_painter.set_window(Some(&window));
|
egui_painter.set_window(Some(&window));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<DemoEvent>();
|
let (async_cmd_tx, mut async_cmd_rx) = mpsc::unbounded_channel::<AsyncCmd>();
|
||||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<DemoEvent>();
|
let (ui_cmd_tx, ui_cmd_rx) = mpsc::unbounded_channel::<UiCmd>();
|
||||||
|
|
||||||
let state = Arc::new(AppState {
|
let state = Arc::new(AppState {
|
||||||
room: Mutex::new(Room::new()),
|
room: Mutex::new(Room::new()),
|
||||||
@@ -68,25 +73,44 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
|||||||
|
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
state: state.clone(),
|
state: state.clone(),
|
||||||
renderers: Vec::default(),
|
video_renderers: Vec::default(),
|
||||||
egui_context,
|
egui_context,
|
||||||
egui_state,
|
egui_state,
|
||||||
egui_painter,
|
egui_painter,
|
||||||
window,
|
window,
|
||||||
event_tx,
|
cmd_tx: async_cmd_tx,
|
||||||
lk_url: "ws://localhost:8080/".to_owned(),
|
cmd_rx: ui_cmd_rx,
|
||||||
lk_token: "your token".to_owned(),
|
lk_url: DEFAULT_URL.to_owned(),
|
||||||
|
lk_token: DEFAULT_TOKEN.to_owned(),
|
||||||
|
connection_failure: None,
|
||||||
|
room_state: ConnectionState::Connected,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Async event loop
|
// Async event loop
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while let Some(event) = event_rx.recv().await {
|
{
|
||||||
|
let events = state.room.lock().events();
|
||||||
|
events.on_track_subscribed({
|
||||||
|
let ui_cmd_tx = ui_cmd_tx.clone();
|
||||||
|
move |event| {
|
||||||
|
let ui_cmd_tx = ui_cmd_tx.clone();
|
||||||
|
async move {
|
||||||
|
ui_cmd_tx.send(UiCmd::TrackSubscribed { event }).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while let Some(event) = async_cmd_rx.recv().await {
|
||||||
match event {
|
match event {
|
||||||
DemoEvent::RoomConnect { url, token } => {
|
AsyncCmd::RoomConnect { url, token } => {
|
||||||
state.connecting.store(true, Ordering::SeqCst);
|
state.connecting.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
let mut room = state.room.lock();
|
let mut room = state.room.lock();
|
||||||
room.connect(&url, &token).await.unwrap();
|
ui_cmd_tx
|
||||||
|
.send(UiCmd::ConnectResult {
|
||||||
|
result: room.connect(&url, &token).await,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
state.connecting.store(false, Ordering::SeqCst);
|
state.connecting.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
@@ -105,6 +129,33 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
|||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn update<T>(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow) {
|
fn update<T>(&mut self, event: Event<'_, T>, control_flow: &mut ControlFlow) {
|
||||||
|
if let Ok(cmd) = self.cmd_rx.try_recv() {
|
||||||
|
match cmd {
|
||||||
|
UiCmd::ConnectResult { result } => {
|
||||||
|
if let Err(err) = result {
|
||||||
|
self.connection_failure = Some(err.to_string());
|
||||||
|
} else {
|
||||||
|
self.connection_failure = None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiCmd::TrackSubscribed { event } => {
|
||||||
|
match event.track {
|
||||||
|
RemoteTrackHandle::Video(video_track) => {
|
||||||
|
// Create a new VideoRenderer
|
||||||
|
let video_renderer = VideoRenderer::new(
|
||||||
|
self.egui_painter.render_state().clone().unwrap(),
|
||||||
|
video_track.rtc_track(),
|
||||||
|
);
|
||||||
|
self.video_renderers.push(video_renderer);
|
||||||
|
}
|
||||||
|
RemoteTrackHandle::Audio(_) => {
|
||||||
|
// The demo doesn't support Audio rendering at the moment.
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
Event::WindowEvent { window_id, event } => {
|
Event::WindowEvent { window_id, event } => {
|
||||||
if let Some(flow) = self.on_window_event(window_id, event) {
|
if let Some(flow) = self.on_window_event(window_id, event) {
|
||||||
@@ -157,16 +208,16 @@ impl App {
|
|||||||
if ui.button("Logs").clicked() {}
|
if ui.button("Logs").clicked() {}
|
||||||
if ui.button("Profiler").clicked() {}
|
if ui.button("Profiler").clicked() {}
|
||||||
if ui.button("WebRTC Stats").clicked() {}
|
if ui.button("WebRTC Stats").clicked() {}
|
||||||
|
if ui.button("Events").clicked() {}
|
||||||
});
|
});
|
||||||
ui.menu_button("Simulate", |ui| {});
|
ui.menu_button("Simulate", |ui| {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::SidePanel::right("room_panel")
|
egui::SidePanel::right("room_panel")
|
||||||
.default_width(128.0)
|
.default_width(256.0)
|
||||||
.show(ui.ctx(), |ui| {
|
.show(ui.ctx(), |ui| {
|
||||||
ui.heading("Livekit - Connect to a room");
|
ui.heading("Livekit - Connect to a room");
|
||||||
|
|
||||||
ui.separator();
|
ui.separator();
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@@ -182,9 +233,11 @@ impl App {
|
|||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
let connecting = self.state.connecting.load(Ordering::SeqCst);
|
let connecting = self.state.connecting.load(Ordering::SeqCst);
|
||||||
ui.set_enabled(!connecting);
|
ui.set_enabled(!connecting);
|
||||||
|
|
||||||
if ui.button("Connect").clicked() {
|
if ui.button("Connect").clicked() {
|
||||||
self.event_tx
|
self.connection_failure = None;
|
||||||
.send(DemoEvent::RoomConnect {
|
self.cmd_tx
|
||||||
|
.send(AsyncCmd::RoomConnect {
|
||||||
url: self.lk_url.clone(),
|
url: self.lk_url.clone(),
|
||||||
token: self.lk_token.clone(),
|
token: self.lk_token.clone(),
|
||||||
})
|
})
|
||||||
@@ -196,7 +249,11 @@ impl App {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.allocate_space(ui.available_size());
|
if let Some(err) = &self.connection_failure {
|
||||||
|
ui.colored_label(egui::Color32::RED, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ui.ctx(), |ui| {
|
egui::CentralPanel::default().show(ui.ctx(), |ui| {
|
||||||
@@ -204,14 +261,33 @@ impl App {
|
|||||||
VideoGrid::new("default_grid")
|
VideoGrid::new("default_grid")
|
||||||
.max_columns(6)
|
.max_columns(6)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
for _ in 0..20 {
|
if self.room_state == ConnectionState::Disconnected {
|
||||||
ui.video_frame(|ui| {
|
for _ in 0..20 {
|
||||||
egui::Frame::none()
|
ui.video_frame(|ui| {
|
||||||
.fill(egui::Color32::DARK_GRAY)
|
egui::Frame::none().fill(egui::Color32::DARK_GRAY).show(
|
||||||
.show(ui, |ui| {
|
ui,
|
||||||
ui.allocate_space(ui.available_size());
|
|ui| {
|
||||||
});
|
ui.allocate_space(ui.available_size());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for video_renderer in &self.video_renderers {
|
||||||
|
ui.video_frame(|ui| {
|
||||||
|
if let Some(tex) = video_renderer.texture_id() {
|
||||||
|
ui.painter().image(
|
||||||
|
tex,
|
||||||
|
ui.available_rect_before_wrap(),
|
||||||
|
egui::Rect::from_min_max(
|
||||||
|
egui::pos2(0.0, 0.0),
|
||||||
|
egui::pos2(1.0, 1.0),
|
||||||
|
),
|
||||||
|
egui::Color32::WHITE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use livekit::events::TrackSubscribedEvent;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AsyncCmd {
|
pub enum AsyncCmd {
|
||||||
RoomConnect { url: String, token: String },
|
RoomConnect { url: String, token: String },
|
||||||
@@ -5,5 +7,10 @@ pub enum AsyncCmd {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum UiCmd {
|
pub enum UiCmd {
|
||||||
ConnectResult,
|
ConnectResult {
|
||||||
|
result: livekit::room::RoomResult<()>,
|
||||||
|
},
|
||||||
|
TrackSubscribed {
|
||||||
|
event: TrackSubscribedEvent,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
mod app;
|
||||||
mod events;
|
mod events;
|
||||||
mod video_grid;
|
mod video_grid;
|
||||||
mod video_renderer;
|
mod video_renderer;
|
||||||
mod app;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
let fmt_layer = tracing_subscriber::fmt::Layer::default();
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(fmt_layer)
|
||||||
|
.init();
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_multi_thread()
|
let rt = tokio::runtime::Builder::new_multi_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use livekit::webrtc::video_frame_buffer::PlanarYuv8Buffer;
|
|||||||
use livekit::webrtc::video_frame_buffer::PlanarYuvBuffer;
|
use livekit::webrtc::video_frame_buffer::PlanarYuvBuffer;
|
||||||
use livekit::webrtc::video_frame_buffer::VideoFrameBufferTrait;
|
use livekit::webrtc::video_frame_buffer::VideoFrameBufferTrait;
|
||||||
use livekit::webrtc::yuv_helper;
|
use livekit::webrtc::yuv_helper;
|
||||||
|
use tracing::debug_span;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use std::{
|
use std::{
|
||||||
@@ -101,6 +102,9 @@ impl VideoRenderer {
|
|||||||
let internal = internal.clone();
|
let internal = internal.clone();
|
||||||
|
|
||||||
Box::new(move |_frame, buffer| {
|
Box::new(move |_frame, buffer| {
|
||||||
|
let span = debug_span!("texture_upload");
|
||||||
|
let _enter = span.enter();
|
||||||
|
|
||||||
let mut internal = internal.lock().unwrap();
|
let mut internal = internal.lock().unwrap();
|
||||||
let buffer = buffer.to_i420();
|
let buffer = buffer.to_i420();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user