feat: move signal_client to livekit-api (#98)
This commit is contained in:
+6
-7
@@ -12,22 +12,21 @@ default = []
|
||||
|
||||
# Note that the following features only change the behavior of tokio-tungstenite.
|
||||
# It doesn't change the behavior of libwebrtc/webrtc-sys
|
||||
native-tls = ["tokio-tungstenite/native-tls"]
|
||||
native-tls-vendored = ["tokio-tungstenite/native-tls-vendored"]
|
||||
rustls-tls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots"]
|
||||
rustls-tls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"]
|
||||
__rustls-tls = ["tokio-tungstenite/__rustls-tls"]
|
||||
native-tls = ["livekit-api/native-tls"]
|
||||
native-tls-vendored = ["livekit-api/native-tls-vendored"]
|
||||
rustls-tls-native-roots = ["livekit-api/rustls-tls-native-roots"]
|
||||
rustls-tls-webpki-roots = ["livekit-api/rustls-tls-webpki-roots"]
|
||||
__rustls-tls = ["livekit-api/__rustls-tls"]
|
||||
|
||||
[dependencies]
|
||||
livekit-api = { path = "../livekit-api", version = "0.1.0", default-features = false, features = ["signal-client"] }
|
||||
livekit-webrtc = { path = "../livekit-webrtc", version = "0.1.1" }
|
||||
livekit-protocol = { path = "../livekit-protocol", version = "0.1.0" }
|
||||
prost = "0.11"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tokio-tungstenite = { version = "0.19" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
parking_lot = { version = "0.12.1" }
|
||||
url = "2.3"
|
||||
futures-util = { version = "0.3", default-features = false, features = ["sink"] }
|
||||
thiserror = "1.0"
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -3,7 +3,6 @@ extern crate core;
|
||||
pub mod proto;
|
||||
mod room;
|
||||
mod rtc_engine;
|
||||
mod signal_client;
|
||||
|
||||
pub mod webrtc {
|
||||
pub use livekit_webrtc::*;
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::participant::ConnectionQuality;
|
||||
use crate::prelude::*;
|
||||
use crate::rtc_engine::EngineError;
|
||||
use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RtcEngine};
|
||||
use crate::signal_client::SignalOptions;
|
||||
use livekit_api::signal_client::SignalOptions;
|
||||
use livekit_protocol as proto;
|
||||
use livekit_protocol::observer::Dispatcher;
|
||||
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
|
||||
|
||||
@@ -2,8 +2,8 @@ use crate::options::TrackPublishOptions;
|
||||
use crate::prelude::LocalTrack;
|
||||
use crate::rtc_engine::lk_runtime::LkRuntime;
|
||||
use crate::rtc_engine::rtc_session::{RtcSession, SessionEvent, SessionEvents};
|
||||
use crate::signal_client::{SignalError, SignalOptions};
|
||||
use crate::DataPacketKind;
|
||||
use livekit_api::signal_client::{SignalError, SignalOptions};
|
||||
use livekit_protocol as proto;
|
||||
use livekit_webrtc::prelude::*;
|
||||
use livekit_webrtc::session_description::SdpParseError;
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::prelude::TrackKind;
|
||||
use crate::rtc_engine::lk_runtime::LkRuntime;
|
||||
use crate::rtc_engine::peer_transport::PeerTransport;
|
||||
use crate::rtc_engine::rtc_events::{RtcEvent, RtcEvents};
|
||||
use crate::signal_client::{SignalClient, SignalEvent, SignalEvents, SignalOptions};
|
||||
use crate::track::LocalTrack;
|
||||
use crate::DataPacketKind;
|
||||
use livekit_api::signal_client::{SignalClient, SignalEvent, SignalEvents, SignalOptions};
|
||||
use livekit_protocol as proto;
|
||||
use livekit_webrtc::prelude::*;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
use crate::signal_client::signal_stream::SignalStream;
|
||||
use livekit_protocol as proto;
|
||||
use parking_lot::Mutex;
|
||||
use std::fmt::Debug;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::RwLock as AsyncRwLock;
|
||||
use tokio_tungstenite::tungstenite::Error as WsError;
|
||||
|
||||
mod signal_stream;
|
||||
|
||||
pub(crate) type SignalEmitter = mpsc::Sender<SignalEvent>;
|
||||
pub(crate) type SignalEvents = mpsc::Receiver<SignalEvent>;
|
||||
pub(crate) type SignalResult<T> = Result<T, SignalError>;
|
||||
|
||||
pub const JOIN_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
pub const PROTOCOL_VERSION: u32 = 8;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SignalError {
|
||||
#[error("already connected")]
|
||||
AlreadyConnected,
|
||||
#[error("ws failure: {0}")]
|
||||
WsError(#[from] WsError),
|
||||
#[error("failed to parse the url")]
|
||||
UrlParse(#[from] url::ParseError),
|
||||
#[error("failed to decode messages from server")]
|
||||
ProtoParse(#[from] prost::DecodeError),
|
||||
#[error("{0}")]
|
||||
Timeout(String),
|
||||
}
|
||||
|
||||
/// Events used by the RTCEngine who will handle the reconnection logic
|
||||
#[derive(Debug)]
|
||||
pub enum SignalEvent {
|
||||
Open,
|
||||
Signal(proto::signal_response::Message),
|
||||
Close,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SignalOptions {
|
||||
pub auto_subscribe: bool,
|
||||
pub adaptive_stream: bool,
|
||||
}
|
||||
|
||||
impl Default for SignalOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
auto_subscribe: true,
|
||||
adaptive_stream: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SignalClient {
|
||||
stream: AsyncRwLock<Option<SignalStream>>,
|
||||
url: String,
|
||||
token: Mutex<String>, // TODO(theomonnom): Handle token refresh
|
||||
join_response: proto::JoinResponse,
|
||||
options: SignalOptions,
|
||||
emitter: SignalEmitter,
|
||||
}
|
||||
|
||||
impl SignalClient {
|
||||
pub async fn connect(
|
||||
url: &str,
|
||||
token: &str,
|
||||
options: SignalOptions,
|
||||
) -> SignalResult<(Self, proto::JoinResponse, SignalEvents)> {
|
||||
let (emitter, mut events) = mpsc::channel(8);
|
||||
let lk_url = get_livekit_url(url, token, &options)?;
|
||||
let new_stream = SignalStream::connect(lk_url, emitter.clone()).await?;
|
||||
let join_response = get_join_response(&mut events).await?;
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
stream: AsyncRwLock::new(Some(new_stream)),
|
||||
url: url.to_string(),
|
||||
token: Mutex::new(token.to_string()),
|
||||
join_response: join_response.clone(),
|
||||
options,
|
||||
emitter,
|
||||
},
|
||||
join_response,
|
||||
events,
|
||||
))
|
||||
}
|
||||
|
||||
// Restart is called when trying to resume the room (RtcSession resume)
|
||||
// TODO(theomonom): Should this be renamed to resume?
|
||||
pub async fn restart(&self) -> SignalResult<()> {
|
||||
self.close().await;
|
||||
|
||||
let sid = &self.join_response.participant.as_ref().unwrap().sid;
|
||||
let token = self.token.lock().clone();
|
||||
|
||||
let mut lk_url = get_livekit_url(&self.url, &token, &self.options)?;
|
||||
lk_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("reconnect", "1")
|
||||
.append_pair("sid", sid);
|
||||
|
||||
let new_stream = SignalStream::connect(lk_url, self.emitter.clone()).await?;
|
||||
*self.stream.write().await = Some(new_stream);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
if let Some(stream) = self.stream.write().await.take() {
|
||||
stream.close().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send(&self, signal: proto::signal_request::Message) {
|
||||
// TODO: Check if currently reconnecting and queue message
|
||||
|
||||
if let Some(stream) = self.stream.read().await.as_ref() {
|
||||
if stream.send(signal).await.is_ok() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TODO(theomonnom): return result?
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn clear_queue(&self) {
|
||||
// TODO(theomonnom): Clear the queue
|
||||
}
|
||||
|
||||
pub async fn flush_queue(&self) {
|
||||
// TODO(theomonnom): Send the queue
|
||||
}
|
||||
|
||||
pub fn join_response(&self) -> proto::JoinResponse {
|
||||
self.join_response.clone()
|
||||
}
|
||||
|
||||
pub fn options(&self) -> SignalOptions {
|
||||
self.options.clone()
|
||||
}
|
||||
|
||||
pub fn url(&self) -> String {
|
||||
self.url.clone()
|
||||
}
|
||||
|
||||
pub fn token(&self) -> String {
|
||||
self.token.lock().clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_livekit_url(url: &str, token: &str, options: &SignalOptions) -> SignalResult<url::Url> {
|
||||
let mut lk_url = url::Url::parse(url)?;
|
||||
lk_url.set_path("/rtc");
|
||||
lk_url
|
||||
.query_pairs_mut()
|
||||
.append_pair("access_token", token)
|
||||
.append_pair("protocol", PROTOCOL_VERSION.to_string().as_str())
|
||||
.append_pair(
|
||||
"auto_subscribe",
|
||||
if options.auto_subscribe { "1" } else { "0" },
|
||||
)
|
||||
.append_pair(
|
||||
"adaptive_stream",
|
||||
if options.adaptive_stream { "1" } else { "0" },
|
||||
);
|
||||
|
||||
Ok(lk_url)
|
||||
}
|
||||
|
||||
async fn get_join_response(receiver: &mut SignalEvents) -> SignalResult<proto::JoinResponse> {
|
||||
let join = async {
|
||||
while let Some(event) = receiver.recv().await {
|
||||
match event {
|
||||
SignalEvent::Signal(proto::signal_response::Message::Join(join)) => {
|
||||
return Ok(join)
|
||||
}
|
||||
SignalEvent::Close => break,
|
||||
SignalEvent::Open => continue,
|
||||
_ => {
|
||||
log::warn!(
|
||||
"received unexpected message while waiting for JoinResponse: {:?}",
|
||||
event
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(WsError::ConnectionClosed)?
|
||||
};
|
||||
|
||||
tokio::time::timeout(JOIN_RESPONSE_TIMEOUT, join)
|
||||
.await
|
||||
.map_err(|_| SignalError::Timeout("failed to receive JoinResponse".to_string()))?
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
use crate::signal_client::{SignalEmitter, SignalEvent, SignalResult};
|
||||
use futures_util::stream::{SplitSink, SplitStream};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use livekit_protocol as proto;
|
||||
use prost::Message as ProstMessage;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_tungstenite::tungstenite::protocol::frame::coding::CloseCode;
|
||||
use tokio_tungstenite::tungstenite::protocol::CloseFrame;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
|
||||
|
||||
use super::SignalEvents;
|
||||
|
||||
type WebSocket = WebSocketStream<MaybeTlsStream<TcpStream>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InternalMessage {
|
||||
Signal {
|
||||
signal: proto::signal_request::Message,
|
||||
response_chn: oneshot::Sender<SignalResult<()>>,
|
||||
},
|
||||
Pong {
|
||||
ping_data: Vec<u8>,
|
||||
},
|
||||
Close {
|
||||
close_frame: Option<CloseFrame<'static>>,
|
||||
},
|
||||
}
|
||||
|
||||
/// SignalStream hold the WebSocket connection
|
||||
///
|
||||
/// It is replaced by [SignalClient] at each reconnection.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct SignalStream {
|
||||
internal_tx: mpsc::Sender<InternalMessage>,
|
||||
read_handle: JoinHandle<()>,
|
||||
write_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl SignalStream {
|
||||
/// Connect to livekit websocket.
|
||||
/// Return SignalError if the connections failed
|
||||
///
|
||||
/// SignalStream will never try to reconnect if the connection has been
|
||||
/// closed.
|
||||
pub async fn connect(url: url::Url, emitter: SignalEmitter) -> SignalResult<Self> {
|
||||
log::info!("connecting to SignalClient: {}", url);
|
||||
|
||||
let (ws_stream, _) = connect_async(url).await?;
|
||||
let _ = emitter.send(SignalEvent::Open).await;
|
||||
|
||||
let (ws_writer, ws_reader) = ws_stream.split();
|
||||
let (internal_tx, internal_rx) = mpsc::channel::<InternalMessage>(8);
|
||||
|
||||
let write_handle = tokio::spawn(Self::write_task(internal_rx, ws_writer, emitter.clone()));
|
||||
let read_handle = tokio::spawn(Self::read_task(internal_tx.clone(), ws_reader, emitter));
|
||||
|
||||
Ok(Self {
|
||||
internal_tx,
|
||||
read_handle,
|
||||
write_handle,
|
||||
})
|
||||
}
|
||||
|
||||
/// Close the websocket
|
||||
/// It sends a CloseFrame to the server before closing
|
||||
pub async fn close(self) {
|
||||
let _ = self
|
||||
.internal_tx
|
||||
.send(InternalMessage::Close {
|
||||
close_frame: Some(CloseFrame {
|
||||
code: CloseCode::Normal,
|
||||
reason: "disconnected by client".into(),
|
||||
}),
|
||||
})
|
||||
.await;
|
||||
|
||||
let _ = self.write_handle.await;
|
||||
let _ = self.read_handle.await;
|
||||
}
|
||||
|
||||
/// Send a SignalRequest to the websocket
|
||||
/// It also waits for the message to be sent
|
||||
pub async fn send(&self, signal: proto::signal_request::Message) -> SignalResult<()> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
let msg = InternalMessage::Signal {
|
||||
signal,
|
||||
response_chn: send,
|
||||
};
|
||||
let _ = self.internal_tx.send(msg).await;
|
||||
recv.await.expect("channel closed")
|
||||
}
|
||||
|
||||
/// This task is used to send messages to the websocket
|
||||
/// It is also responsible for closing the connection
|
||||
async fn write_task(
|
||||
mut internal_rx: mpsc::Receiver<InternalMessage>,
|
||||
mut ws_writer: SplitSink<WebSocket, Message>,
|
||||
emitter: SignalEmitter,
|
||||
) {
|
||||
while let Some(msg) = internal_rx.recv().await {
|
||||
match msg {
|
||||
InternalMessage::Signal {
|
||||
signal,
|
||||
response_chn,
|
||||
} => {
|
||||
log::debug!("sending SignalRequest: {:?}", signal);
|
||||
|
||||
let data = Message::Binary(
|
||||
proto::SignalRequest {
|
||||
message: Some(signal),
|
||||
}
|
||||
.encode_to_vec(),
|
||||
);
|
||||
|
||||
if let Err(err) = ws_writer.send(data).await {
|
||||
log::error!("failed to send signal: {:?}", err);
|
||||
let _ = response_chn.send(Err(err.into()));
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = response_chn.send(Ok(()));
|
||||
}
|
||||
InternalMessage::Pong { ping_data } => {
|
||||
if let Err(err) = ws_writer.send(Message::Pong(ping_data)).await {
|
||||
log::error!("failed to send pong message: {:?}", err);
|
||||
}
|
||||
}
|
||||
InternalMessage::Close { close_frame } => {
|
||||
if let Some(close_frame) = close_frame {
|
||||
let _ = ws_writer.send(Message::Close(Some(close_frame))).await;
|
||||
let _ = ws_writer.flush().await;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = ws_writer.close().await;
|
||||
let _ = emitter.send(SignalEvent::Close).await;
|
||||
}
|
||||
|
||||
/// This task is used to read incoming messages from the websocket
|
||||
/// and dispatch them through the EventEmitter.
|
||||
///
|
||||
/// It can also send messages to [handle_write] task ( Used e.g. answer to pings )
|
||||
async fn read_task(
|
||||
internal_tx: mpsc::Sender<InternalMessage>,
|
||||
mut ws_reader: SplitStream<WebSocket>,
|
||||
emitter: SignalEmitter,
|
||||
) {
|
||||
while let Some(msg) = ws_reader.next().await {
|
||||
match msg {
|
||||
Ok(Message::Binary(data)) => {
|
||||
let res = proto::SignalResponse::decode(data.as_slice())
|
||||
.expect("failed to decode SignalResponse");
|
||||
|
||||
let msg = res.message.unwrap();
|
||||
log::debug!("received SignalResponse: {:?}", msg);
|
||||
let _ = emitter.send(SignalEvent::Signal(msg)).await;
|
||||
}
|
||||
Ok(Message::Ping(data)) => {
|
||||
let _ = internal_tx
|
||||
.send(InternalMessage::Pong { ping_data: data })
|
||||
.await;
|
||||
continue;
|
||||
}
|
||||
Ok(Message::Close(close)) => {
|
||||
log::debug!("server closed the connection: {:?}", close);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
log::error!("unhandled websocket message {:?}", msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = internal_tx
|
||||
.send(InternalMessage::Close { close_frame: None })
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user