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 parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
type EventHandler<T> = Box<dyn FnMut(T) -> BoxFuture<'static, ()> + Send + Sync>;
|
||||
@@ -21,120 +30,94 @@ pub enum TrackError {
|
||||
TrackNotFound(String),
|
||||
}
|
||||
|
||||
pub mod room {
|
||||
use super::{EventHandler, TrackError};
|
||||
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::RoomHandle;
|
||||
use futures::future::Future;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParticipantConnectedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ParticipantConnectedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParticipantDisconnectedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ParticipantDisconnectedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrackSubscribedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub track: RemoteTrackHandle,
|
||||
pub publication: RemoteTrackPublication,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackSubscribedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub track: RemoteTrackHandle,
|
||||
pub publication: RemoteTrackPublication,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrackPublishedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub publication: RemoteTrackPublication,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackPublishedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub publication: RemoteTrackPublication,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TrackSubscriptionFailedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub error: TrackError,
|
||||
pub sid: TrackSid,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackSubscriptionFailedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub error: TrackError,
|
||||
pub sid: TrackSid,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConnectionStateChangedEvent {
|
||||
pub room_handle: RoomHandle,
|
||||
pub state: ConnectionState,
|
||||
}
|
||||
|
||||
pub(crate) type OnParticipantConnectedHandler = EventHandler<ParticipantConnectedEvent>;
|
||||
pub(crate) type OnParticipantDisconnectedHandler = EventHandler<ParticipantDisconnectedEvent>;
|
||||
pub(crate) type OnTrackSubscribedEventHandler = EventHandler<TrackSubscribedEvent>;
|
||||
pub(crate) type OnTrackPublishedEventHandler = EventHandler<TrackPublishedEvent>;
|
||||
pub(crate) type OnTrackSubscriptionFailedHandler = EventHandler<TrackSubscriptionFailedEvent>;
|
||||
pub(crate) type OnParticipantConnectedHandler = EventHandler<ParticipantConnectedEvent>;
|
||||
pub(crate) type OnParticipantDisconnectedHandler = EventHandler<ParticipantDisconnectedEvent>;
|
||||
pub(crate) type OnTrackSubscribedHandler = EventHandler<TrackSubscribedEvent>;
|
||||
pub(crate) type OnTrackPublishedHandler = EventHandler<TrackPublishedEvent>;
|
||||
pub(crate) type OnTrackSubscriptionFailedHandler = EventHandler<TrackSubscriptionFailedEvent>;
|
||||
pub(crate) type OnConnectionStateChangedHandler = EventHandler<ConnectionStateChangedEvent>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RoomEvents {
|
||||
pub(crate) on_participant_connected: Mutex<Option<OnParticipantConnectedHandler>>,
|
||||
pub(crate) on_participant_disconnected: Mutex<Option<OnParticipantDisconnectedHandler>>,
|
||||
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedEventHandler>>,
|
||||
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedEventHandler>>,
|
||||
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct RoomEvents {
|
||||
pub(crate) on_participant_connected: Mutex<Option<OnParticipantConnectedHandler>>,
|
||||
pub(crate) on_participant_disconnected: Mutex<Option<OnParticipantDisconnectedHandler>>,
|
||||
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedHandler>>,
|
||||
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedHandler>>,
|
||||
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
||||
pub(crate) on_connection_state_changed: Mutex<Option<OnConnectionStateChangedHandler>>,
|
||||
}
|
||||
|
||||
impl RoomEvents {
|
||||
event_setter!(on_participant_connected, ParticipantConnectedEvent);
|
||||
event_setter!(on_participant_disconnected, ParticipantDisconnectedEvent);
|
||||
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
||||
event_setter!(on_track_published, TrackPublishedEvent);
|
||||
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
||||
impl Debug for RoomEvents {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "RoomEvents")
|
||||
}
|
||||
}
|
||||
|
||||
pub mod participant {
|
||||
use super::{EventHandler, TrackError};
|
||||
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 futures::future::Future;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
impl RoomEvents {
|
||||
event_setter!(on_participant_connected, ParticipantConnectedEvent);
|
||||
event_setter!(on_participant_disconnected, ParticipantDisconnectedEvent);
|
||||
event_setter!(on_track_subscribed, TrackSubscribedEvent);
|
||||
event_setter!(on_track_published, TrackPublishedEvent);
|
||||
event_setter!(on_track_subscription_failed, TrackSubscriptionFailedEvent);
|
||||
event_setter!(on_connection_state_changed, ConnectionStateChangedEvent);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackPublishedEvent {
|
||||
pub publication: RemoteTrackPublication,
|
||||
pub participant: Arc<RemoteParticipant>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct ParticipantEvents {
|
||||
pub(crate) on_track_published: Mutex<Option<OnTrackPublishedHandler>>,
|
||||
pub(crate) on_track_subscribed: Mutex<Option<OnTrackSubscribedHandler>>,
|
||||
pub(crate) on_track_subscription_failed: Mutex<Option<OnTrackSubscriptionFailedHandler>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TrackSubscribedEvent {
|
||||
pub track: RemoteTrackHandle,
|
||||
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 Debug for ParticipantEvents {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ParticipantEvents")
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
mod events;
|
||||
mod rtc_engine;
|
||||
mod signal_client;
|
||||
|
||||
pub mod events;
|
||||
pub mod room;
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use parking_lot::lock_api::RwLockUpgradableReadGuard;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicU8;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use self::id::ParticipantSid;
|
||||
use self::id::{ParticipantIdentity, ParticipantSid};
|
||||
use self::participant::local_participant::LocalParticipant;
|
||||
use self::participant::remote_participant::RemoteParticipant;
|
||||
use self::participant::ParticipantInternalTrait;
|
||||
use self::participant::ParticipantTrait;
|
||||
use crate::events::room::{
|
||||
ParticipantConnectedEvent, ParticipantDisconnectedEvent, RoomEvents, TrackSubscribedEvent,
|
||||
use crate::events::{
|
||||
ParticipantConnectedEvent, ParticipantDisconnectedEvent, RoomEvents, TrackPublishedEvent,
|
||||
TrackSubscribedEvent,
|
||||
};
|
||||
use crate::proto;
|
||||
use crate::proto::participant_info;
|
||||
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::signal_client::SignalOptions;
|
||||
@@ -27,15 +27,15 @@ pub mod track;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum RoomError {
|
||||
#[error("internal RTCEngine failure")]
|
||||
#[error("engine : {0}")]
|
||||
Engine(#[from] EngineError),
|
||||
#[error("internal Room failure")]
|
||||
#[error("room failure: {0}")]
|
||||
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 {
|
||||
Disconnected,
|
||||
Connecting,
|
||||
@@ -43,6 +43,7 @@ pub enum ConnectionState {
|
||||
Reconnecting,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RoomInner {
|
||||
state: AtomicU8, // ConnectionState
|
||||
sid: Mutex<String>,
|
||||
@@ -52,6 +53,7 @@ struct RoomInner {
|
||||
local_participant: Arc<LocalParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Room {
|
||||
inner: Option<Arc<RoomInner>>,
|
||||
events: Arc<RoomEvents>,
|
||||
@@ -65,14 +67,19 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
pub async fn connect(&mut self, url: &str, token: &str) -> RoomResult<()> {
|
||||
let (rtc_engine, engine_events) =
|
||||
RTCEngine::connect(url, token, SignalOptions::default()).await?;
|
||||
let rtc_engine = Arc::new(rtc_engine);
|
||||
let join_response = rtc_engine.join_response();
|
||||
let pi = join_response.participant.unwrap().clone();
|
||||
let local_participant = Arc::new(LocalParticipant::new(
|
||||
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 inner = Arc::new(RoomInner {
|
||||
@@ -84,14 +91,25 @@ impl Room {
|
||||
local_participant,
|
||||
});
|
||||
|
||||
self.inner = Some(inner.clone());
|
||||
|
||||
// Add already connected participants
|
||||
for pi in join_response.other_participants {
|
||||
let p = Self::create_participant(inner.clone(), self.events.clone(), pi.clone());
|
||||
p.update_info(pi).await;
|
||||
let participant = {
|
||||
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));
|
||||
|
||||
Ok(())
|
||||
@@ -121,6 +139,7 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = Level::DEBUG, skip(room_inner, room_events))]
|
||||
async fn handle_event(
|
||||
room_inner: Arc<RoomInner>,
|
||||
room_events: Arc<RoomEvents>,
|
||||
@@ -155,10 +174,18 @@ impl Room {
|
||||
Self::get_participant(room_inner.clone(), &participant_sid.to_string().into());
|
||||
|
||||
if let Some(remote_participant) = remote_participant {
|
||||
remote_participant.add_subscribed_media_track(
|
||||
track_sid.to_string().into(),
|
||||
rtp_receiver.track(),
|
||||
);
|
||||
tokio::spawn({
|
||||
let track_sid = track_sid.to_owned().into();
|
||||
async move {
|
||||
remote_participant
|
||||
.add_subscribed_media_track(
|
||||
RoomHandle::from(room_inner),
|
||||
track_sid,
|
||||
rtp_receiver.track(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// The server should send participant updates before sending a new offer
|
||||
// So this should not happen.
|
||||
@@ -173,6 +200,7 @@ impl Room {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = Level::DEBUG, skip(room_inner, room_events))]
|
||||
async fn handle_participant_update(
|
||||
room_inner: Arc<RoomInner>,
|
||||
room_events: Arc<RoomEvents>,
|
||||
@@ -182,7 +210,7 @@ impl Room {
|
||||
if pi.sid == room_inner.local_participant.sid()
|
||||
|| 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;
|
||||
}
|
||||
|
||||
@@ -199,12 +227,24 @@ impl Room {
|
||||
)
|
||||
} else {
|
||||
// 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 {
|
||||
// Create a new participant and call OnConnect event
|
||||
let remote_participant =
|
||||
Self::create_participant(room_inner.clone(), room_events.clone(), pi);
|
||||
let remote_participant = {
|
||||
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();
|
||||
if let Some(cb) = handler.as_mut() {
|
||||
cb(ParticipantConnectedEvent {
|
||||
@@ -212,10 +252,16 @@ impl Room {
|
||||
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(
|
||||
room_inner: Arc<RoomInner>,
|
||||
room_events: Arc<RoomEvents>,
|
||||
@@ -247,42 +293,64 @@ impl Room {
|
||||
fn create_participant(
|
||||
room_inner: Arc<RoomInner>,
|
||||
room_events: Arc<RoomEvents>,
|
||||
pi: proto::ParticipantInfo,
|
||||
sid: ParticipantSid,
|
||||
identity: ParticipantIdentity,
|
||||
name: String,
|
||||
metadata: String,
|
||||
) -> 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
|
||||
p.internal_events().on_track_subscribed({
|
||||
let room_events = room_events.clone();
|
||||
let room_inner = room_inner.clone();
|
||||
forward_event!(on_track_published, when_connected);
|
||||
forward_event!(on_track_subscribed);
|
||||
forward_event!(on_track_subscription_failed);
|
||||
|
||||
move |event| {
|
||||
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());
|
||||
room_inner.participants.write().insert(sid, p.clone());
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RoomHandle {
|
||||
inner: Arc<RoomInner>,
|
||||
}
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
use std::sync::Weak;
|
||||
|
||||
use crate::proto::{data_packet, DataPacket, UserPacket};
|
||||
use crate::room::participant::{impl_participant_trait, ParticipantShared, ParticipantInternalTrait};
|
||||
use crate::room::RoomError;
|
||||
use crate::room::participant::{
|
||||
impl_participant_trait, ParticipantInternalTrait, ParticipantShared,
|
||||
};
|
||||
use crate::room::{RoomError, RoomInner};
|
||||
use crate::rtc_engine::RTCEngine;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalParticipant {
|
||||
shared: ParticipantShared,
|
||||
rtc_engine: Arc<RTCEngine>,
|
||||
}
|
||||
|
||||
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 {
|
||||
shared: ParticipantShared::new(
|
||||
info.sid.into(),
|
||||
info.identity.into(),
|
||||
info.name,
|
||||
info.metadata,
|
||||
),
|
||||
shared: ParticipantShared::new(sid, identity, name, metadata),
|
||||
rtc_engine,
|
||||
}
|
||||
}
|
||||
@@ -40,16 +46,16 @@ impl LocalParticipant {
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn update_info(self: Arc<Self>, info: ParticipantInfo) {
|
||||
self.shared.update_info(info);
|
||||
}
|
||||
}
|
||||
|
||||
impl ParticipantInternalTrait for LocalParticipant {
|
||||
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
||||
self.shared.internal_events.clone()
|
||||
}
|
||||
|
||||
fn update_info(&self, info: ParticipantInfo) {
|
||||
self.shared.update_info(info);
|
||||
}
|
||||
}
|
||||
|
||||
impl_participant_trait!(LocalParticipant);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use crate::events::participant::ParticipantEvents;
|
||||
use crate::events::ParticipantEvents;
|
||||
use crate::proto::ParticipantInfo;
|
||||
use crate::room::id::{ParticipantIdentity, ParticipantSid, TrackSid};
|
||||
use crate::room::participant::local_participant::LocalParticipant;
|
||||
use crate::room::participant::remote_participant::RemoteParticipant;
|
||||
use crate::room::publication::{TrackPublication, TrackPublicationTrait};
|
||||
use futures_util::future::BoxFuture;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use std::collections::HashMap;
|
||||
@@ -13,8 +12,7 @@ use std::sync::Arc;
|
||||
pub mod local_participant;
|
||||
pub mod remote_participant;
|
||||
|
||||
type OnTrackSubscribed = Box<dyn FnMut(ParticipantHandle) -> BoxFuture<'static, ()> + Send + Sync>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct ParticipantShared {
|
||||
pub(super) events: Arc<ParticipantEvents>,
|
||||
pub(super) internal_events: Arc<ParticipantEvents>,
|
||||
@@ -57,6 +55,7 @@ impl ParticipantShared {
|
||||
|
||||
pub(crate) trait ParticipantInternalTrait {
|
||||
fn internal_events(&self) -> Arc<ParticipantEvents>;
|
||||
fn update_info(&self, info: ParticipantInfo);
|
||||
}
|
||||
|
||||
pub trait ParticipantTrait {
|
||||
@@ -73,20 +72,11 @@ pub enum ParticipantHandle {
|
||||
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 {
|
||||
enum_dispatch!(
|
||||
[Local, Remote]
|
||||
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 {
|
||||
($x:ty) => {
|
||||
use crate::events::participant::ParticipantEvents;
|
||||
use crate::events::ParticipantEvents;
|
||||
use crate::proto::ParticipantInfo;
|
||||
use crate::room::id::{ParticipantIdentity, ParticipantSid};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::events::participant::{
|
||||
TrackPublishedEvent, TrackSubscribedEvent, TrackSubscriptionFailedEvent,
|
||||
use crate::events::{
|
||||
TrackError, TrackPublishedEvent, TrackSubscribedEvent, TrackSubscriptionFailedEvent,
|
||||
};
|
||||
use crate::events::TrackError;
|
||||
use crate::proto::TrackInfo;
|
||||
use crate::room::id::TrackSid;
|
||||
use crate::room::participant::{
|
||||
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_track::RemoteTrackHandle;
|
||||
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 std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use tokio::time::{sleep, timeout};
|
||||
use tracing::{info, error};
|
||||
use tracing::{debug, debug_span, error, instrument, Instrument, Level};
|
||||
|
||||
use super::ParticipantTrait;
|
||||
|
||||
const ADD_TRACK_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteParticipant {
|
||||
shared: ParticipantShared,
|
||||
}
|
||||
|
||||
impl RemoteParticipant {
|
||||
pub(crate) fn new(info: ParticipantInfo) -> Self {
|
||||
pub(crate) fn new(
|
||||
sid: ParticipantSid,
|
||||
identity: ParticipantIdentity,
|
||||
name: String,
|
||||
metadata: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
shared: ParticipantShared::new(
|
||||
info.sid.into(),
|
||||
info.identity.into(),
|
||||
info.name,
|
||||
info.metadata,
|
||||
),
|
||||
shared: ParticipantShared::new(sid, identity, name, 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> {
|
||||
self.shared.tracks.read().get(sid).map(|track| {
|
||||
if let TrackPublication::Remote(remote) = track {
|
||||
@@ -157,12 +51,125 @@ impl RemoteParticipant {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn update_info(self: Arc<Self>, info: ParticipantInfo) {
|
||||
self.shared.update_info(info.clone());
|
||||
#[instrument(level = Level::DEBUG, skip(room_handle))]
|
||||
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();
|
||||
|
||||
for track in info.tracks {
|
||||
for track in tracks {
|
||||
if let Some(publication) = self.get_track_publication(&track.sid.clone().into()) {
|
||||
publication.update_info(track.clone());
|
||||
} else {
|
||||
@@ -172,6 +179,7 @@ impl RemoteParticipant {
|
||||
|
||||
// This is a new track, fire publish events
|
||||
let event = TrackPublishedEvent {
|
||||
room_handle: room_handle.clone(),
|
||||
participant: self.clone(),
|
||||
publication: publication.clone(),
|
||||
};
|
||||
@@ -200,6 +208,10 @@ impl ParticipantInternalTrait for RemoteParticipant {
|
||||
fn internal_events(&self) -> Arc<ParticipantEvents> {
|
||||
self.shared.internal_events.clone()
|
||||
}
|
||||
|
||||
fn update_info(&self, info: ParticipantInfo) {
|
||||
self.shared.update_info(info)
|
||||
}
|
||||
}
|
||||
|
||||
impl_participant_trait!(RemoteParticipant);
|
||||
|
||||
@@ -25,6 +25,7 @@ pub trait TrackPublicationTrait {
|
||||
fn simulcasted(&self) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TrackPublicationShared {
|
||||
pub(super) track: Mutex<Option<TrackHandle>>,
|
||||
pub(super) name: Mutex<String>,
|
||||
@@ -75,7 +76,7 @@ impl TrackPublicationShared {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TrackPublication {
|
||||
Local(LocalTrackPublication),
|
||||
Remote(RemoteTrackPublication),
|
||||
@@ -146,7 +147,7 @@ macro_rules! impl_publication_trait {
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LocalTrackPublication {
|
||||
shared: Arc<TrackPublicationShared>,
|
||||
}
|
||||
@@ -161,7 +162,7 @@ impl LocalTrackPublication {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RemoteTrackPublication {
|
||||
shared: Arc<TrackPublicationShared>,
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub struct TrackEvents {}
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::room::track::{impl_track_trait, TrackShared};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalAudioTrack {
|
||||
shared: TrackShared,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::room::track::{impl_track_trait, TrackShared};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalVideoTrack {
|
||||
shared: TrackShared,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use std::sync::atomic::AtomicU8;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod audio_track;
|
||||
pub mod events;
|
||||
pub mod local_audio_track;
|
||||
pub mod local_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 trait TrackTrait {
|
||||
@@ -108,6 +108,7 @@ pub trait TrackTrait {
|
||||
fn stop(&self);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) struct TrackShared {
|
||||
pub(super) sid: Mutex<TrackSid>,
|
||||
pub(super) name: Mutex<String>,
|
||||
@@ -141,7 +142,7 @@ impl TrackShared {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TrackHandle {
|
||||
LocalVideo(Arc<LocalVideoTrack>),
|
||||
LocalAudio(Arc<LocalAudioTrack>),
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::room::track::{impl_track_trait, TrackShared};
|
||||
use livekit_webrtc::media_stream::{AudioTrack, MediaStreamTrackHandle};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteAudioTrack {
|
||||
shared: TrackShared,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use livekit_utils::enum_dispatch;
|
||||
|
||||
use super::TrackTrait;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RemoteTrackHandle {
|
||||
Audio(Arc<RemoteAudioTrack>),
|
||||
Video(Arc<RemoteVideoTrack>),
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::room::track::{impl_track_trait, TrackShared};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RemoteVideoTrack {
|
||||
shared: TrackShared,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::error;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
@@ -10,7 +11,7 @@ use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, info, trace};
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
use crate::{proto, signal_client};
|
||||
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 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 LOSSY_DC_LABEL: &str = "_lossy";
|
||||
pub(crate) const RELIABLE_DC_LABEL: &str = "_reliable";
|
||||
@@ -69,7 +74,7 @@ struct IceCandidateJSON {
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum EngineError {
|
||||
#[error("signal failure")]
|
||||
#[error("signal failure: {0}")]
|
||||
Signal(#[from] SignalError),
|
||||
#[error("internal webrtc failure")]
|
||||
Rtc(#[from] RTCError),
|
||||
@@ -94,13 +99,25 @@ pub(crate) enum EngineEvent {
|
||||
rtp_receiver: RtpReceiver,
|
||||
streams: Vec<MediaStream>,
|
||||
},
|
||||
Connected,
|
||||
Resuming,
|
||||
Resumed,
|
||||
SignalResumed,
|
||||
Restarting,
|
||||
Restarted,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EngineInner {
|
||||
has_published: AtomicBool,
|
||||
// Join infornation
|
||||
url: String,
|
||||
token: Mutex<String>, // The token is refreshed periodically
|
||||
options: Mutex<SignalOptions>,
|
||||
join_response: Mutex<JoinResponse>,
|
||||
|
||||
has_published: AtomicBool,
|
||||
pc_state: AtomicU8, // Casted to PCState enum
|
||||
reconnecting: AtomicBool,
|
||||
|
||||
publisher_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 )
|
||||
lossy_dc: Mutex<DataChannel>,
|
||||
reliable_dc: Mutex<DataChannel>,
|
||||
|
||||
// Subscriber data channels
|
||||
// These fields are never used, we just keep a strong reference to them,
|
||||
// so we can receive data from other participants
|
||||
sub_reliable_dc: Mutex<Option<DataChannel>>,
|
||||
sub_lossy_dc: Mutex<Option<DataChannel>>,
|
||||
|
||||
closed: AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -126,7 +144,72 @@ pub struct 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 {
|
||||
pub fn new() -> Self {
|
||||
|
||||
Self {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(url, token))]
|
||||
pub(crate) async fn connect(
|
||||
url: &str,
|
||||
@@ -172,16 +255,16 @@ impl RTCEngine {
|
||||
emitter.clone(),
|
||||
));
|
||||
|
||||
if !join_response.subscriber_primary {
|
||||
engine_inner.negotiate_publisher().await?;
|
||||
}
|
||||
|
||||
let rtc_engine = Self {
|
||||
signal_client,
|
||||
engine_inner,
|
||||
lk_runtime,
|
||||
};
|
||||
|
||||
if !join_response.subscriber_primary {
|
||||
rtc_engine.negotiate_publisher().await?;
|
||||
}
|
||||
|
||||
Ok((rtc_engine, events))
|
||||
}
|
||||
|
||||
@@ -191,8 +274,9 @@ impl RTCEngine {
|
||||
data: &DataPacket,
|
||||
kind: data_packet::Kind,
|
||||
) -> Result<(), EngineError> {
|
||||
self.ensure_publisher_connected(kind).await?;
|
||||
self.data_channel(kind)
|
||||
self.engine_inner.ensure_publisher_connected(kind).await?;
|
||||
self.engine_inner
|
||||
.data_channel(kind)
|
||||
.lock()
|
||||
.send(&data.encode_to_vec(), true)
|
||||
.map_err(Into::into)
|
||||
@@ -244,7 +328,11 @@ impl RTCEngine {
|
||||
}
|
||||
}
|
||||
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 } => {
|
||||
// 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 is_primary = subscriber_primary && target == SignalTarget::Subscriber;
|
||||
|
||||
if is_primary && state == PeerConnectionState::Disconnected {
|
||||
if is_primary && state == PeerConnectionState::Connected {
|
||||
let old_state = engine_inner
|
||||
.pc_state
|
||||
.swap(PCState::Connected as u8, Ordering::SeqCst);
|
||||
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 {
|
||||
engine_inner
|
||||
.pc_state
|
||||
.store(PCState::Disconnected as u8, Ordering::SeqCst);
|
||||
// TODO(theomonnom) Handle disconnect
|
||||
|
||||
Self::handle_disconnected(signal_client, engine_inner, emitter);
|
||||
}
|
||||
}
|
||||
RTCEvent::DataChannel {
|
||||
@@ -449,30 +537,131 @@ impl RTCEngine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn ensure_publisher_connected(&self, kind: data_packet::Kind) -> EngineResult<()> {
|
||||
if !self.join_response().subscriber_primary {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let publisher = &self.engine_inner.publisher_pc;
|
||||
async fn handle_disconnected(
|
||||
signal_client: Arc<SignalClient>,
|
||||
engine_inner: Arc<EngineInner>,
|
||||
emitter: EngineEmitter,
|
||||
) {
|
||||
if engine_inner.closed.load(Ordering::SeqCst)
|
||||
|| engine_inner.reconnecting.load(Ordering::SeqCst)
|
||||
{
|
||||
let mut publisher = publisher.lock().await;
|
||||
if !publisher.is_connected()
|
||||
&& publisher.peer_connection().ice_connection_state()
|
||||
!= IceConnectionState::IceConnectionChecking
|
||||
{
|
||||
let _ = self.negotiate_publisher().await;
|
||||
return;
|
||||
}
|
||||
|
||||
engine_inner.reconnecting.store(true, Ordering::SeqCst);
|
||||
warn!("RTCEngine disconnected unexpectedly, reconnecting...");
|
||||
|
||||
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);
|
||||
if dc.lock().state() == DataState::Open {
|
||||
return Ok(());
|
||||
}
|
||||
Self::wait_pc_connection(engine_inner).await?;
|
||||
signal_client.flush_queue().await;
|
||||
|
||||
// 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 {
|
||||
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;
|
||||
}
|
||||
};
|
||||
@@ -480,30 +669,14 @@ impl RTCEngine {
|
||||
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);
|
||||
let err = EngineError::Connection("wait_pc_connection timed out".to_string());
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn negotiate_publisher(&self) -> EngineResult<()> {
|
||||
self.engine_inner
|
||||
.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 close(&self) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
fn configure_engine(
|
||||
@@ -617,16 +790,9 @@ impl RTCEngine {
|
||||
reliable_dc: Mutex::new(reliable_dc),
|
||||
sub_lossy_dc: Mutex::new(None),
|
||||
sub_reliable_dc: Mutex::new(None),
|
||||
closed: AtomicBool::new(false),
|
||||
},
|
||||
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,
|
||||
>;
|
||||
|
||||
pub struct PCTransport {
|
||||
pub(crate) struct PCTransport {
|
||||
peer_connection: PeerConnection,
|
||||
pending_candidates: Vec<IceCandidate>,
|
||||
on_offer_handler: Option<OnOfferHandler>,
|
||||
restarting_ice: bool,
|
||||
renegotiate: bool,
|
||||
restarting_ice: bool,
|
||||
}
|
||||
|
||||
impl Debug for PCTransport {
|
||||
@@ -58,7 +58,11 @@ impl PCTransport {
|
||||
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> {
|
||||
if self.peer_connection.remote_description().is_none() {
|
||||
self.pending_candidates.push(ice_candidate);
|
||||
@@ -71,7 +75,7 @@ impl PCTransport {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[tracing::instrument(level = Level::DEBUG)]
|
||||
pub async fn set_remote_description(
|
||||
&mut self,
|
||||
remote_description: SessionDescription,
|
||||
@@ -94,15 +98,15 @@ impl PCTransport {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
#[tracing::instrument(level = Level::DEBUG)]
|
||||
pub async fn negotiate(&mut self) -> Result<(), RTCError> {
|
||||
// TODO(theomonnom) Debounce here with NEGOTIATION_FREQUENCY
|
||||
self.create_and_send_offer(RTCOfferAnswerOptions::default())
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn create_and_send_offer(
|
||||
#[tracing::instrument(level = Level::DEBUG)]
|
||||
pub async fn create_and_send_offer(
|
||||
&mut self,
|
||||
options: RTCOfferAnswerOptions,
|
||||
) -> Result<(), RTCError> {
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::RwLockWriteGuard;
|
||||
use std::time::Duration;
|
||||
|
||||
use livekit_webrtc::peer_connection_factory::{
|
||||
ContinualGatheringPolicy, ICEServer, IceTransportsType, RTCConfiguration,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_tungstenite::tungstenite::Error as WsError;
|
||||
|
||||
use crate::proto::{signal_request, signal_response, JoinResponse};
|
||||
use crate::signal_client::signal_stream::SignalStream;
|
||||
use tracing::{instrument, Level};
|
||||
|
||||
mod signal_stream;
|
||||
|
||||
@@ -21,7 +24,7 @@ pub const JOIN_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SignalError {
|
||||
#[error("websocket failure")]
|
||||
#[error("ws failure: {0}")]
|
||||
WsError(#[from] WsError),
|
||||
#[error("failed to parse the url")]
|
||||
UrlParse(#[from] url::ParseError),
|
||||
@@ -39,12 +42,12 @@ pub(crate) enum SignalEvent {
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct SignalOptions {
|
||||
reconnect: bool,
|
||||
auto_subscribe: bool,
|
||||
sid: String,
|
||||
adaptive_stream: bool,
|
||||
pub(crate) reconnect: bool,
|
||||
pub(crate) sid: String,
|
||||
pub auto_subscribe: bool,
|
||||
pub adaptive_stream: bool,
|
||||
}
|
||||
|
||||
impl Default for SignalOptions {
|
||||
@@ -60,32 +63,59 @@ impl Default for SignalOptions {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SignalClient {
|
||||
stream: SignalStream,
|
||||
stream: RwLock<Option<SignalStream>>,
|
||||
emitter: SignalEmitter,
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
url: &str,
|
||||
token: &str,
|
||||
options: SignalOptions,
|
||||
) -> SignalResult<(Self, SignalEvents)> {
|
||||
let (emitter, events) = mpsc::channel(8);
|
||||
let stream = SignalStream::connect(url, token, options, emitter.clone()).await?;
|
||||
|
||||
// TODO(theomonnom) Retry initial connection
|
||||
|
||||
Ok((Self { stream, emitter }, events))
|
||||
) -> SignalResult<()> {
|
||||
let stream = SignalStream::connect(url, token, options, self.emitter.clone()).await?;
|
||||
*self.stream.write() = Some(stream);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn send(&self, signal: signal_request::Message) {
|
||||
if let Err(_) = self.stream.send(signal).await {
|
||||
// TODO(theomonnom) Queue message ( Ignore on full reconnect )
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
pub async fn close(&self) {
|
||||
if let Some(stream) = self.stream.write().take() {
|
||||
stream.close().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reconnect(&self) {
|
||||
// TODO(theomonnom) Close & recreate SignalStream, also send the queue if needed
|
||||
#[instrument(level = Level::DEBUG)]
|
||||
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::time::timeout;
|
||||
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(
|
||||
receiver: &mut mpsc::Receiver<SignalEvent>,
|
||||
) -> SignalResult<JoinResponse> {
|
||||
|
||||
@@ -43,4 +43,4 @@ rtc::Thread* RTCRuntime::signaling_thread() const {
|
||||
std::shared_ptr<RTCRuntime> create_rtc_runtime() {
|
||||
return std::make_shared<RTCRuntime>();
|
||||
}
|
||||
} // namespace livekit
|
||||
} // namespace livekit
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use cxx::UniquePtr;
|
||||
use libwebrtc_sys::media_stream as sys_ms;
|
||||
use libwebrtc_sys::MEDIA_TYPE_VIDEO;
|
||||
use livekit_utils::enum_dispatch;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::pin::Pin;
|
||||
use std::sync::{Arc, Mutex};
|
||||
@@ -25,17 +26,6 @@ pub enum MediaStreamTrackHandle {
|
||||
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 {
|
||||
pub(crate) fn new(cxx_handle: UniquePtr<sys_ms::ffi::MediaStreamTrack>) -> Self {
|
||||
unsafe {
|
||||
@@ -66,17 +56,14 @@ impl Debug for MediaStreamTrackHandle {
|
||||
}
|
||||
|
||||
impl MediaStreamTrackTrait for MediaStreamTrackHandle {
|
||||
shared_getter!(kind, String);
|
||||
shared_getter!(id, String);
|
||||
shared_getter!(enabled, bool);
|
||||
shared_getter!(state, TrackState);
|
||||
|
||||
fn set_enabled(&self, enabled: bool) -> bool {
|
||||
match self {
|
||||
Self::Video(inner) => inner.set_enabled(enabled),
|
||||
Self::Audio(inner) => inner.set_enabled(enabled),
|
||||
}
|
||||
}
|
||||
enum_dispatch!(
|
||||
[Audio, Video]
|
||||
fnc!(kind, &Self, [], String);
|
||||
fnc!(id, &Self, [], String);
|
||||
fnc!(enabled, &Self, [], bool);
|
||||
fnc!(state, &Self, [], TrackState);
|
||||
fnc!(set_enabled, &Self, [enabled: bool], bool);
|
||||
);
|
||||
}
|
||||
|
||||
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-wgpu = { git = "https://github.com/emilk/egui", features = ["winit"] }
|
||||
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::video_grid::VideoGrid;
|
||||
use crate::events::UiCmd;
|
||||
use crate::video_renderer::VideoRenderer;
|
||||
use crate::{events::AsyncCmd, video_grid::VideoGrid};
|
||||
use egui_wgpu::WgpuConfiguration;
|
||||
use livekit::room::track::remote_track::RemoteTrackHandle;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -9,10 +10,11 @@ use std::sync::{
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use livekit::room::Room;
|
||||
use livekit::room::{ConnectionState, Room, RoomError};
|
||||
|
||||
const URL: &str = "ws://localhost:7880";
|
||||
const TOKEN : &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY0NzMsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NjQ4MDY0NzMsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.BgVdBnq3XFD3_BQHoe1azqjifYysubgFl6Qlzu9IQGI";
|
||||
// Useful default constants for developing
|
||||
const DEFAULT_URL: &str = "ws://localhost:7880";
|
||||
const DEFAULT_TOKEN : &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY0NzMsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJuYXRpdmUiLCJuYmYiOjE2NjQ4MDY0NzMsInN1YiI6Im5hdGl2ZSIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.BgVdBnq3XFD3_BQHoe1azqjifYysubgFl6Qlzu9IQGI";
|
||||
|
||||
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIzODQ4MDY3MzAsImlzcyI6IkFQSXpLYkFTaUNWYWtnSiIsIm5hbWUiOiJ3ZWIiLCJuYmYiOjE2NjQ4MDY3MzAsInN1YiI6IndlYiIsInZpZGVvIjp7InJvb21DcmVhdGUiOnRydWUsInJvb21Kb2luIjp0cnVlfX0.VbDoULjX1CVGZu2sPy3SvWYlVZUBXxQVPmdB9BnmlN4
|
||||
|
||||
@@ -30,16 +32,19 @@ struct AppState {
|
||||
struct App {
|
||||
state: Arc<AppState>,
|
||||
|
||||
renderers: Vec<VideoRenderer>,
|
||||
video_renderers: Vec<VideoRenderer>,
|
||||
egui_context: egui::Context,
|
||||
egui_state: egui_winit::State,
|
||||
egui_painter: egui_wgpu::winit::Painter,
|
||||
window: winit::window::Window,
|
||||
event_tx: mpsc::UnboundedSender<DemoEvent>,
|
||||
cmd_tx: mpsc::UnboundedSender<AsyncCmd>,
|
||||
cmd_rx: mpsc::UnboundedReceiver<UiCmd>,
|
||||
|
||||
// UI State
|
||||
lk_url: String,
|
||||
lk_token: String,
|
||||
connection_failure: Option<String>,
|
||||
room_state: ConnectionState,
|
||||
}
|
||||
|
||||
pub fn run(rt: tokio::runtime::Runtime) {
|
||||
@@ -58,8 +63,8 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
||||
egui_painter.set_window(Some(&window));
|
||||
}
|
||||
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<DemoEvent>();
|
||||
let (event_tx, mut event_rx) = mpsc::unbounded_channel::<DemoEvent>();
|
||||
let (async_cmd_tx, mut async_cmd_rx) = mpsc::unbounded_channel::<AsyncCmd>();
|
||||
let (ui_cmd_tx, ui_cmd_rx) = mpsc::unbounded_channel::<UiCmd>();
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
room: Mutex::new(Room::new()),
|
||||
@@ -68,25 +73,44 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
||||
|
||||
let mut app = App {
|
||||
state: state.clone(),
|
||||
renderers: Vec::default(),
|
||||
video_renderers: Vec::default(),
|
||||
egui_context,
|
||||
egui_state,
|
||||
egui_painter,
|
||||
window,
|
||||
event_tx,
|
||||
lk_url: "ws://localhost:8080/".to_owned(),
|
||||
lk_token: "your token".to_owned(),
|
||||
cmd_tx: async_cmd_tx,
|
||||
cmd_rx: ui_cmd_rx,
|
||||
lk_url: DEFAULT_URL.to_owned(),
|
||||
lk_token: DEFAULT_TOKEN.to_owned(),
|
||||
connection_failure: None,
|
||||
room_state: ConnectionState::Connected,
|
||||
};
|
||||
|
||||
// Async event loop
|
||||
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 {
|
||||
DemoEvent::RoomConnect { url, token } => {
|
||||
AsyncCmd::RoomConnect { url, token } => {
|
||||
state.connecting.store(true, Ordering::SeqCst);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -105,6 +129,33 @@ pub fn run(rt: tokio::runtime::Runtime) {
|
||||
|
||||
impl App {
|
||||
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 {
|
||||
Event::WindowEvent { 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("Profiler").clicked() {}
|
||||
if ui.button("WebRTC Stats").clicked() {}
|
||||
if ui.button("Events").clicked() {}
|
||||
});
|
||||
ui.menu_button("Simulate", |ui| {});
|
||||
});
|
||||
});
|
||||
|
||||
egui::SidePanel::right("room_panel")
|
||||
.default_width(128.0)
|
||||
.default_width(256.0)
|
||||
.show(ui.ctx(), |ui| {
|
||||
ui.heading("Livekit - Connect to a room");
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
@@ -182,9 +233,11 @@ impl App {
|
||||
ui.horizontal(|ui| {
|
||||
let connecting = self.state.connecting.load(Ordering::SeqCst);
|
||||
ui.set_enabled(!connecting);
|
||||
|
||||
if ui.button("Connect").clicked() {
|
||||
self.event_tx
|
||||
.send(DemoEvent::RoomConnect {
|
||||
self.connection_failure = None;
|
||||
self.cmd_tx
|
||||
.send(AsyncCmd::RoomConnect {
|
||||
url: self.lk_url.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| {
|
||||
@@ -204,14 +261,33 @@ impl App {
|
||||
VideoGrid::new("default_grid")
|
||||
.max_columns(6)
|
||||
.show(ui, |ui| {
|
||||
for _ in 0..20 {
|
||||
ui.video_frame(|ui| {
|
||||
egui::Frame::none()
|
||||
.fill(egui::Color32::DARK_GRAY)
|
||||
.show(ui, |ui| {
|
||||
ui.allocate_space(ui.available_size());
|
||||
});
|
||||
});
|
||||
if self.room_state == ConnectionState::Disconnected {
|
||||
for _ in 0..20 {
|
||||
ui.video_frame(|ui| {
|
||||
egui::Frame::none().fill(egui::Color32::DARK_GRAY).show(
|
||||
ui,
|
||||
|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)]
|
||||
pub enum AsyncCmd {
|
||||
RoomConnect { url: String, token: String },
|
||||
@@ -5,5 +7,10 @@ pub enum AsyncCmd {
|
||||
|
||||
#[derive(Debug)]
|
||||
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 video_grid;
|
||||
mod video_renderer;
|
||||
mod app;
|
||||
|
||||
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()
|
||||
.enable_all()
|
||||
.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::VideoFrameBufferTrait;
|
||||
use livekit::webrtc::yuv_helper;
|
||||
use tracing::debug_span;
|
||||
use std::convert::TryInto;
|
||||
use std::num::NonZeroU32;
|
||||
use std::{
|
||||
@@ -101,6 +102,9 @@ impl VideoRenderer {
|
||||
let internal = internal.clone();
|
||||
|
||||
Box::new(move |_frame, buffer| {
|
||||
let span = debug_span!("texture_upload");
|
||||
let _enter = span.enter();
|
||||
|
||||
let mut internal = internal.lock().unwrap();
|
||||
let buffer = buffer.to_i420();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user