XMPP improvements
- Handle unsolicited XMPP messages gracefully - Split out generic XMPP connection handler which can be used for connecting to brewery MUCs - Don't deadlock during Jingle handling
This commit is contained in:
parent
5759d5c800
commit
037c2d944f
|
@ -1829,8 +1829,6 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xmpp-parsers-gst-meet"
|
name = "xmpp-parsers-gst-meet"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8cde8e9611ca7cac569119e4ec1b6fe50da4df91a168fdab786693029ab1482a"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"blake2",
|
"blake2",
|
||||||
|
|
|
@ -27,6 +27,7 @@ rcgen = { version = "0.8", default-features = false }
|
||||||
ring = { version = "0.16", default-features = false }
|
ring = { version = "0.16", default-features = false }
|
||||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
serde_json = { version = "1", default-features = false, features = ["std"] }
|
serde_json = { version = "1", default-features = false, features = ["std"] }
|
||||||
|
serde_with = { version = "1", default-features = false, features = ["macros"] }
|
||||||
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "time"] }
|
tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "sync", "time"] }
|
||||||
tokio-stream = { version = "0.1", default-features = false, features = ["time"] }
|
tokio-stream = { version = "0.1", default-features = false, features = ["time"] }
|
||||||
tokio-tungstenite = { version = "0.14", default-features = false, features = ["connect", "rustls-tls"] }
|
tokio-tungstenite = { version = "0.14", default-features = false, features = ["connect", "rustls-tls"] }
|
||||||
|
@ -39,7 +40,7 @@ tracing-subscriber = { version = "0.2", optional = true, default-features = fals
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
] }
|
] }
|
||||||
uuid = { version = "0.8", default-features = false, features = ["v4"] }
|
uuid = { version = "0.8", default-features = false, features = ["v4"] }
|
||||||
xmpp-parsers = { package = "xmpp-parsers-gst-meet", version = "0.18", default-features = false }
|
xmpp-parsers = { path = "../../xmpp-rs/xmpp-parsers", package = "xmpp-parsers-gst-meet", version = "0.18", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
|
@ -6,11 +6,13 @@ use futures::{
|
||||||
stream::{StreamExt, TryStreamExt},
|
stream::{StreamExt, TryStreamExt},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::{serde_as, DisplayFromStr};
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tokio_tungstenite::tungstenite::{http::Request, Message};
|
use tokio_tungstenite::tungstenite::{http::Request, Message};
|
||||||
use tracing::{debug, error, info, warn};
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
|
#[serde_as]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(tag = "colibriClass")]
|
#[serde(tag = "colibriClass")]
|
||||||
pub enum ColibriMessage {
|
pub enum ColibriMessage {
|
||||||
|
@ -20,7 +22,11 @@ pub enum ColibriMessage {
|
||||||
previous_speakers: Vec<String>,
|
previous_speakers: Vec<String>,
|
||||||
},
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
EndpointConnectivityStatusChangeEvent { endpoint: String, active: bool },
|
EndpointConnectivityStatusChangeEvent {
|
||||||
|
endpoint: String,
|
||||||
|
#[serde_as(as = "DisplayFromStr")]
|
||||||
|
active: bool,
|
||||||
|
},
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
EndpointMessage {
|
EndpointMessage {
|
||||||
from: String,
|
from: String,
|
||||||
|
|
|
@ -4,17 +4,23 @@ use anyhow::{anyhow, bail, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use gstreamer::prelude::{ElementExt, ElementExtManual, GstBinExt};
|
use gstreamer::prelude::{ElementExt, ElementExtManual, GstBinExt};
|
||||||
|
use maplit::hashmap;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::Serialize;
|
||||||
use tokio::sync::{mpsc, oneshot, Mutex};
|
use tokio::sync::{mpsc, oneshot, Mutex};
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
|
pub use xmpp_parsers::disco::Feature;
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
disco::{DiscoInfoQuery, DiscoInfoResult, Feature},
|
disco::{DiscoInfoQuery, DiscoInfoResult},
|
||||||
ecaps2::{self, ECaps2},
|
ecaps2::{self, ECaps2},
|
||||||
hashes::Algo,
|
hashes::Algo,
|
||||||
iq::{Iq, IqType},
|
iq::{Iq, IqType},
|
||||||
jingle::{Action, Jingle},
|
jingle::{Action, Jingle},
|
||||||
|
message::{Message, MessageType},
|
||||||
muc::{Muc, MucUser},
|
muc::{Muc, MucUser},
|
||||||
|
nick::Nick,
|
||||||
ns,
|
ns,
|
||||||
presence::{self, Presence},
|
presence::{self, Presence},
|
||||||
BareJid, Element, FullJid, Jid,
|
BareJid, Element, FullJid, Jid,
|
||||||
|
@ -25,7 +31,8 @@ use crate::{
|
||||||
jingle::JingleSession,
|
jingle::JingleSession,
|
||||||
source::MediaType,
|
source::MediaType,
|
||||||
stanza_filter::StanzaFilter,
|
stanza_filter::StanzaFilter,
|
||||||
xmpp,
|
util::generate_id,
|
||||||
|
xmpp::{self, connection::Connection},
|
||||||
};
|
};
|
||||||
|
|
||||||
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
|
static DISCO_INFO: Lazy<DiscoInfoResult> = Lazy::new(|| {
|
||||||
|
@ -70,6 +77,7 @@ pub struct JitsiConferenceConfig {
|
||||||
pub nick: String,
|
pub nick: String,
|
||||||
pub region: String,
|
pub region: String,
|
||||||
pub video_codec: String,
|
pub video_codec: String,
|
||||||
|
pub extra_muc_features: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -79,6 +87,7 @@ pub struct JitsiConference {
|
||||||
pub(crate) xmpp_tx: mpsc::Sender<Element>,
|
pub(crate) xmpp_tx: mpsc::Sender<Element>,
|
||||||
pub(crate) config: JitsiConferenceConfig,
|
pub(crate) config: JitsiConferenceConfig,
|
||||||
pub(crate) external_services: Vec<xmpp::extdisco::Service>,
|
pub(crate) external_services: Vec<xmpp::extdisco::Service>,
|
||||||
|
pub(crate) jingle_session: Arc<Mutex<Option<JingleSession>>>,
|
||||||
pub(crate) inner: Arc<Mutex<JitsiConferenceInner>>,
|
pub(crate) inner: Arc<Mutex<JitsiConferenceInner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +103,7 @@ impl fmt::Debug for JitsiConference {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Participant {
|
pub struct Participant {
|
||||||
pub jid: FullJid,
|
pub jid: Option<FullJid>,
|
||||||
pub muc_jid: FullJid,
|
pub muc_jid: FullJid,
|
||||||
pub nick: Option<String>,
|
pub nick: Option<String>,
|
||||||
}
|
}
|
||||||
|
@ -102,7 +111,6 @@ pub struct Participant {
|
||||||
type BoxedResultFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
type BoxedResultFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
||||||
|
|
||||||
pub(crate) struct JitsiConferenceInner {
|
pub(crate) struct JitsiConferenceInner {
|
||||||
pub(crate) jingle_session: Option<JingleSession>,
|
|
||||||
participants: HashMap<String, Participant>,
|
participants: HashMap<String, Participant>,
|
||||||
on_participant:
|
on_participant:
|
||||||
Option<Arc<dyn (Fn(JitsiConference, Participant) -> BoxedResultFuture) + Send + Sync>>,
|
Option<Arc<dyn (Fn(JitsiConference, Participant) -> BoxedResultFuture) + Send + Sync>>,
|
||||||
|
@ -112,7 +120,6 @@ pub(crate) struct JitsiConferenceInner {
|
||||||
Option<Arc<dyn (Fn(JitsiConference, ColibriMessage) -> BoxedResultFuture) + Send + Sync>>,
|
Option<Arc<dyn (Fn(JitsiConference, ColibriMessage) -> BoxedResultFuture) + Send + Sync>>,
|
||||||
state: JitsiConferenceState,
|
state: JitsiConferenceState,
|
||||||
connected_tx: Option<oneshot::Sender<()>>,
|
connected_tx: Option<oneshot::Sender<()>>,
|
||||||
connected_rx: Option<oneshot::Receiver<()>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for JitsiConferenceInner {
|
impl fmt::Debug for JitsiConferenceInner {
|
||||||
|
@ -124,51 +131,60 @@ impl fmt::Debug for JitsiConferenceInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JitsiConference {
|
impl JitsiConference {
|
||||||
#[tracing::instrument(level = "debug", skip(xmpp_tx), err)]
|
#[tracing::instrument(level = "debug", err)]
|
||||||
pub(crate) async fn new(
|
pub async fn join(
|
||||||
|
xmpp_connection: Connection,
|
||||||
glib_main_context: glib::MainContext,
|
glib_main_context: glib::MainContext,
|
||||||
jid: FullJid,
|
|
||||||
xmpp_tx: mpsc::Sender<Element>,
|
|
||||||
config: JitsiConferenceConfig,
|
config: JitsiConferenceConfig,
|
||||||
external_services: Vec<xmpp::extdisco::Service>,
|
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
|
let conference_stanza = xmpp::jitsi::Conference {
|
||||||
|
machine_uid: Uuid::new_v4().to_string(),
|
||||||
|
room: config.muc.to_string(),
|
||||||
|
properties: hashmap! {
|
||||||
|
// Disable voice processing
|
||||||
|
// TODO put this in config
|
||||||
|
"stereo".to_string() => "true".to_string(),
|
||||||
|
"startBitrate".to_string() => "800".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
Ok(Self {
|
|
||||||
|
let focus = config.focus.clone();
|
||||||
|
|
||||||
|
let conference = Self {
|
||||||
glib_main_context,
|
glib_main_context,
|
||||||
jid,
|
jid: xmpp_connection
|
||||||
xmpp_tx,
|
.jid()
|
||||||
|
.await
|
||||||
|
.context("not connected (no JID)")?,
|
||||||
|
xmpp_tx: xmpp_connection.tx.clone(),
|
||||||
config,
|
config,
|
||||||
external_services,
|
external_services: xmpp_connection.external_services().await,
|
||||||
|
jingle_session: Arc::new(Mutex::new(None)),
|
||||||
inner: Arc::new(Mutex::new(JitsiConferenceInner {
|
inner: Arc::new(Mutex::new(JitsiConferenceInner {
|
||||||
state: JitsiConferenceState::Discovering,
|
state: JitsiConferenceState::Discovering,
|
||||||
participants: HashMap::new(),
|
participants: HashMap::new(),
|
||||||
on_participant: None,
|
on_participant: None,
|
||||||
on_participant_left: None,
|
on_participant_left: None,
|
||||||
on_colibri_message: None,
|
on_colibri_message: None,
|
||||||
jingle_session: None,
|
|
||||||
connected_tx: Some(tx),
|
connected_tx: Some(tx),
|
||||||
connected_rx: Some(rx),
|
|
||||||
})),
|
})),
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn connected(&self) -> Result<()> {
|
|
||||||
let rx = {
|
|
||||||
let mut locked_inner = self.inner.lock().await;
|
|
||||||
locked_inner
|
|
||||||
.connected_rx
|
|
||||||
.take()
|
|
||||||
.context("connected() called twice")?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xmpp_connection.add_stanza_filter(conference.clone()).await;
|
||||||
|
|
||||||
|
let iq = Iq::from_set(generate_id(), conference_stanza).with_to(Jid::Full(focus));
|
||||||
|
xmpp_connection.tx.send(iq.into()).await?;
|
||||||
|
|
||||||
rx.await?;
|
rx.await?;
|
||||||
Ok(())
|
|
||||||
|
Ok(conference)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", err)]
|
#[tracing::instrument(level = "debug", err)]
|
||||||
pub async fn leave(self) -> Result<()> {
|
pub async fn leave(self) -> Result<()> {
|
||||||
let mut inner = self.inner.lock().await;
|
if let Some(jingle_session) = self.jingle_session.lock().await.take() {
|
||||||
|
|
||||||
if let Some(jingle_session) = inner.jingle_session.take() {
|
|
||||||
debug!("pausing all sinks");
|
debug!("pausing all sinks");
|
||||||
jingle_session.pause_all_sinks();
|
jingle_session.pause_all_sinks();
|
||||||
|
|
||||||
|
@ -224,10 +240,9 @@ impl JitsiConference {
|
||||||
pub async fn pipeline(&self) -> Result<gstreamer::Pipeline> {
|
pub async fn pipeline(&self) -> Result<gstreamer::Pipeline> {
|
||||||
Ok(
|
Ok(
|
||||||
self
|
self
|
||||||
.inner
|
.jingle_session
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.context("not connected (no jingle session)")?
|
.context("not connected (no jingle session)")?
|
||||||
.pipeline(),
|
.pipeline(),
|
||||||
|
@ -255,10 +270,9 @@ impl JitsiConference {
|
||||||
pub async fn audio_sink_element(&self) -> Result<gstreamer::Element> {
|
pub async fn audio_sink_element(&self) -> Result<gstreamer::Element> {
|
||||||
Ok(
|
Ok(
|
||||||
self
|
self
|
||||||
.inner
|
.jingle_session
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.context("not connected (no jingle session)")?
|
.context("not connected (no jingle session)")?
|
||||||
.audio_sink_element(),
|
.audio_sink_element(),
|
||||||
|
@ -268,10 +282,9 @@ impl JitsiConference {
|
||||||
pub async fn video_sink_element(&self) -> Result<gstreamer::Element> {
|
pub async fn video_sink_element(&self) -> Result<gstreamer::Element> {
|
||||||
Ok(
|
Ok(
|
||||||
self
|
self
|
||||||
.inner
|
.jingle_session
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.context("not connected (no jingle session)")?
|
.context("not connected (no jingle session)")?
|
||||||
.video_sink_element(),
|
.video_sink_element(),
|
||||||
|
@ -280,10 +293,9 @@ impl JitsiConference {
|
||||||
|
|
||||||
pub async fn send_colibri_message(&self, message: ColibriMessage) -> Result<()> {
|
pub async fn send_colibri_message(&self, message: ColibriMessage) -> Result<()> {
|
||||||
self
|
self
|
||||||
.inner
|
.jingle_session
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.context("not connected (no jingle session)")?
|
.context("not connected (no jingle session)")?
|
||||||
.colibri_channel
|
.colibri_channel
|
||||||
|
@ -293,6 +305,54 @@ impl JitsiConference {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_json_message<T: Serialize>(&self, payload: &T) -> Result<()> {
|
||||||
|
let message = Message {
|
||||||
|
from: Some(Jid::Full(self.jid.clone())),
|
||||||
|
to: Some(Jid::Bare(self.config.muc.clone())),
|
||||||
|
id: Some(Uuid::new_v4().to_string()),
|
||||||
|
type_: MessageType::Groupchat,
|
||||||
|
bodies: Default::default(),
|
||||||
|
subjects: Default::default(),
|
||||||
|
thread: None,
|
||||||
|
payloads: vec![Element::try_from(xmpp::jitsi::JsonMessage {
|
||||||
|
payload: serde_json::to_value(payload)?,
|
||||||
|
})?],
|
||||||
|
};
|
||||||
|
self.xmpp_tx.send(message.into()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn ensure_participant(&self, id: &str) -> Result<()> {
|
||||||
|
if !self.inner.lock().await.participants.contains_key(id) {
|
||||||
|
let participant = Participant {
|
||||||
|
jid: None,
|
||||||
|
muc_jid: self.config.muc.clone().with_resource(id),
|
||||||
|
nick: None,
|
||||||
|
};
|
||||||
|
self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.participants
|
||||||
|
.insert(id.to_owned(), participant.clone());
|
||||||
|
if let Some(f) = self.inner.lock().await.on_participant.as_ref().cloned() {
|
||||||
|
if let Err(e) = f(self.clone(), participant.clone()).await {
|
||||||
|
warn!("on_participant failed: {:?}", e);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if let Ok(pipeline) = self.pipeline().await {
|
||||||
|
gstreamer::debug_bin_to_dot_file(
|
||||||
|
&pipeline,
|
||||||
|
gstreamer::DebugGraphDetails::ALL,
|
||||||
|
&format!("participant-added-{}", participant.muc_jid.resource),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(f))]
|
#[tracing::instrument(level = "trace", skip(f))]
|
||||||
pub async fn on_participant(
|
pub async fn on_participant(
|
||||||
&self,
|
&self,
|
||||||
|
@ -313,6 +373,15 @@ impl JitsiConference {
|
||||||
if let Err(e) = f(self.clone(), participant.clone()).await {
|
if let Err(e) = f(self.clone(), participant.clone()).await {
|
||||||
warn!("on_participant failed: {:?}", e);
|
warn!("on_participant failed: {:?}", e);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if let Ok(pipeline) = self.pipeline().await {
|
||||||
|
gstreamer::debug_bin_to_dot_file(
|
||||||
|
&pipeline,
|
||||||
|
gstreamer::DebugGraphDetails::ALL,
|
||||||
|
&format!("participant-added-{}", participant.muc_jid.resource),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,10 +418,9 @@ impl StanzaFilter for JitsiConference {
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", err)]
|
#[tracing::instrument(level = "trace", err)]
|
||||||
async fn take(&self, element: Element) -> Result<()> {
|
async fn take(&self, element: Element) -> Result<()> {
|
||||||
let mut locked_inner = self.inner.lock().await;
|
|
||||||
|
|
||||||
use JitsiConferenceState::*;
|
use JitsiConferenceState::*;
|
||||||
match locked_inner.state {
|
let state = self.inner.lock().await.state;
|
||||||
|
match state {
|
||||||
Discovering => {
|
Discovering => {
|
||||||
let iq = Iq::try_from(element)?;
|
let iq = Iq::try_from(element)?;
|
||||||
if let IqType::Result(Some(element)) = iq.payload {
|
if let IqType::Result(Some(element)) = iq.payload {
|
||||||
|
@ -377,34 +445,40 @@ impl StanzaFilter for JitsiConference {
|
||||||
|
|
||||||
let jitsi_disco_hash =
|
let jitsi_disco_hash =
|
||||||
ecaps2::hash_ecaps2(&ecaps2::compute_disco(&jitsi_disco_info)?, Algo::Sha_256)?;
|
ecaps2::hash_ecaps2(&ecaps2::compute_disco(&jitsi_disco_info)?, Algo::Sha_256)?;
|
||||||
self
|
let mut presence = vec![
|
||||||
.send_presence(vec![
|
Muc::new().into(),
|
||||||
Muc::new().into(),
|
ECaps2::new(vec![jitsi_disco_hash]).into(),
|
||||||
ECaps2::new(vec![jitsi_disco_hash]).into(),
|
Element::builder("stats-id", "").append("gst-meet").build(),
|
||||||
Element::builder("stats-id", "").append("gst-meet").build(),
|
Element::builder("jitsi_participant_codecType", "")
|
||||||
Element::builder("jitsi_participant_codecType", "")
|
.append(self.config.video_codec.as_str())
|
||||||
.append(self.config.video_codec.as_str())
|
.build(),
|
||||||
.build(),
|
Element::builder("jitsi_participant_region", "")
|
||||||
Element::builder("jitsi_participant_region", "")
|
.append(self.config.region.as_str())
|
||||||
.append(self.config.region.as_str())
|
.build(),
|
||||||
.build(),
|
Element::builder("audiomuted", "").append("false").build(),
|
||||||
Element::builder("audiomuted", "").append("false").build(),
|
Element::builder("videomuted", "").append("false").build(),
|
||||||
Element::builder("videomuted", "").append("false").build(),
|
Element::builder("nick", "http://jabber.org/protocol/nick")
|
||||||
Element::builder("nick", "http://jabber.org/protocol/nick")
|
.append(self.config.nick.as_str())
|
||||||
.append(self.config.nick.as_str())
|
.build(),
|
||||||
.build(),
|
Element::builder("region", "http://jitsi.org/jitsi-meet")
|
||||||
Element::builder("region", "http://jitsi.org/jitsi-meet")
|
.attr("id", &self.config.region)
|
||||||
.attr("id", &self.config.region)
|
.build(),
|
||||||
.build(),
|
];
|
||||||
])
|
presence.extend(
|
||||||
.await?;
|
self
|
||||||
locked_inner.state = JoiningMuc;
|
.config
|
||||||
|
.extra_muc_features
|
||||||
|
.iter()
|
||||||
|
.map(|feature| Element::builder("feature", "").attr("var", feature).build()),
|
||||||
|
);
|
||||||
|
self.send_presence(presence).await?;
|
||||||
|
self.inner.lock().await.state = JoiningMuc;
|
||||||
},
|
},
|
||||||
JoiningMuc => {
|
JoiningMuc => {
|
||||||
let presence = Presence::try_from(element)?;
|
let presence = Presence::try_from(element)?;
|
||||||
if BareJid::from(presence.from.as_ref().unwrap().clone()) == self.config.muc {
|
if BareJid::from(presence.from.as_ref().unwrap().clone()) == self.config.muc {
|
||||||
debug!("Joined MUC: {}", self.config.muc);
|
debug!("Joined MUC: {}", self.config.muc);
|
||||||
locked_inner.state = Idle;
|
self.inner.lock().await.state = Idle;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Idle => {
|
Idle => {
|
||||||
|
@ -438,7 +512,7 @@ impl StanzaFilter for JitsiConference {
|
||||||
.with_from(Jid::Full(self.jid.clone()));
|
.with_from(Jid::Full(self.jid.clone()));
|
||||||
self.xmpp_tx.send(result_iq.into()).await?;
|
self.xmpp_tx.send(result_iq.into()).await?;
|
||||||
|
|
||||||
locked_inner.jingle_session =
|
*self.jingle_session.lock().await =
|
||||||
Some(JingleSession::initiate(self, jingle).await?);
|
Some(JingleSession::initiate(self, jingle).await?);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -453,8 +527,10 @@ impl StanzaFilter for JitsiConference {
|
||||||
.with_from(Jid::Full(self.jid.clone()));
|
.with_from(Jid::Full(self.jid.clone()));
|
||||||
self.xmpp_tx.send(result_iq.into()).await?;
|
self.xmpp_tx.send(result_iq.into()).await?;
|
||||||
|
|
||||||
locked_inner
|
self
|
||||||
.jingle_session
|
.jingle_session
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.context("not connected (no jingle session")?
|
.context("not connected (no jingle session")?
|
||||||
.source_add(jingle)
|
.source_add(jingle)
|
||||||
|
@ -470,11 +546,11 @@ impl StanzaFilter for JitsiConference {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
IqType::Result(_) => {
|
IqType::Result(_) => {
|
||||||
if let Some(jingle_session) = locked_inner.jingle_session.as_ref() {
|
if let Some(jingle_session) = self.jingle_session.lock().await.as_mut() {
|
||||||
if Some(iq.id) == jingle_session.accept_iq_id {
|
if Some(iq.id) == jingle_session.accept_iq_id {
|
||||||
let colibri_url = jingle_session.colibri_url.clone();
|
let colibri_url = jingle_session.colibri_url.clone();
|
||||||
|
|
||||||
locked_inner.jingle_session.as_mut().unwrap().accept_iq_id = None;
|
jingle_session.accept_iq_id = None;
|
||||||
|
|
||||||
debug!("Focus acknowledged session-accept");
|
debug!("Focus acknowledged session-accept");
|
||||||
|
|
||||||
|
@ -483,11 +559,7 @@ impl StanzaFilter for JitsiConference {
|
||||||
let colibri_channel = ColibriChannel::new(&colibri_url).await?;
|
let colibri_channel = ColibriChannel::new(&colibri_url).await?;
|
||||||
let (tx, rx) = mpsc::channel(8);
|
let (tx, rx) = mpsc::channel(8);
|
||||||
colibri_channel.subscribe(tx).await;
|
colibri_channel.subscribe(tx).await;
|
||||||
locked_inner
|
jingle_session.colibri_channel = Some(colibri_channel);
|
||||||
.jingle_session
|
|
||||||
.as_mut()
|
|
||||||
.unwrap()
|
|
||||||
.colibri_channel = Some(colibri_channel);
|
|
||||||
|
|
||||||
let self_ = self.clone();
|
let self_ = self.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
|
@ -503,7 +575,7 @@ impl StanzaFilter for JitsiConference {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(connected_tx) = locked_inner.connected_tx.take() {
|
if let Some(connected_tx) = self.inner.lock().await.connected_tx.take() {
|
||||||
connected_tx.send(()).unwrap();
|
connected_tx.send(()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -522,47 +594,77 @@ impl StanzaFilter for JitsiConference {
|
||||||
let bare_from: BareJid = from.clone().into();
|
let bare_from: BareJid = from.clone().into();
|
||||||
if bare_from == self.config.muc && from.resource != "focus" {
|
if bare_from == self.config.muc && from.resource != "focus" {
|
||||||
trace!("received MUC presence from {}", from.resource);
|
trace!("received MUC presence from {}", from.resource);
|
||||||
for payload in presence.payloads {
|
let nick_payload = presence
|
||||||
if !payload.is("x", ns::MUC_USER) {
|
.payloads
|
||||||
continue;
|
.iter()
|
||||||
}
|
.find(|e| e.is("nick", ns::NICK))
|
||||||
let muc_user = MucUser::try_from(payload)?;
|
.map(|e| Nick::try_from(e.clone()))
|
||||||
debug!("MUC user presence: {:?}", muc_user);
|
.transpose()?;
|
||||||
|
if let Some(muc_user_payload) = presence
|
||||||
|
.payloads
|
||||||
|
.into_iter()
|
||||||
|
.find(|e| e.is("x", ns::MUC_USER))
|
||||||
|
{
|
||||||
|
let muc_user = MucUser::try_from(muc_user_payload)?;
|
||||||
for item in muc_user.items {
|
for item in muc_user.items {
|
||||||
if let Some(jid) = &item.jid {
|
if let Some(jid) = &item.jid {
|
||||||
if jid == &self.jid {
|
if jid == &self.jid {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let participant = Participant {
|
let participant = Participant {
|
||||||
jid: jid.clone(),
|
jid: Some(jid.clone()),
|
||||||
muc_jid: from.clone(),
|
muc_jid: from.clone(),
|
||||||
nick: item.nick,
|
nick: item
|
||||||
|
.nick
|
||||||
|
.or_else(|| nick_payload.as_ref().map(|nick| nick.0.clone())),
|
||||||
};
|
};
|
||||||
if presence.type_ == presence::Type::Unavailable
|
if presence.type_ == presence::Type::Unavailable
|
||||||
&& locked_inner
|
&& self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.participants
|
.participants
|
||||||
.remove(&from.resource.clone())
|
.remove(&from.resource.clone())
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
debug!("participant left: {:?}", jid);
|
debug!("participant left: {:?}", jid);
|
||||||
if let Some(f) = &locked_inner.on_participant_left {
|
if let Some(f) = &self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.on_participant_left
|
||||||
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
{
|
||||||
debug!("calling on_participant_left with old participant");
|
debug!("calling on_participant_left with old participant");
|
||||||
if let Err(e) = f(self.clone(), participant).await {
|
if let Err(e) = f(self.clone(), participant).await {
|
||||||
warn!("on_participant_left failed: {:?}", e);
|
warn!("on_participant_left failed: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if locked_inner
|
else if self
|
||||||
|
.inner
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
.participants
|
.participants
|
||||||
.insert(from.resource.clone(), participant.clone())
|
.insert(from.resource.clone(), participant.clone())
|
||||||
.is_none()
|
.is_none()
|
||||||
{
|
{
|
||||||
debug!("new participant: {:?}", jid);
|
debug!("new participant: {:?}", jid);
|
||||||
if let Some(f) = &locked_inner.on_participant {
|
if let Some(f) = &self.inner.lock().await.on_participant.as_ref().cloned() {
|
||||||
debug!("calling on_participant with new participant");
|
debug!("calling on_participant with new participant");
|
||||||
if let Err(e) = f(self.clone(), participant).await {
|
if let Err(e) = f(self.clone(), participant.clone()).await {
|
||||||
warn!("on_participant failed: {:?}", e);
|
warn!("on_participant failed: {:?}", e);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if let Some(jingle_session) = self.jingle_session.lock().await.as_ref() {
|
||||||
|
gstreamer::debug_bin_to_dot_file(
|
||||||
|
&jingle_session.pipeline(),
|
||||||
|
gstreamer::DebugGraphDetails::ALL,
|
||||||
|
&format!("participant-added-{}", participant.muc_jid.resource),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,6 +236,7 @@ impl JingleSession {
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
debug!("skipping media: {}", description.media);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,20 +247,24 @@ impl JingleSession {
|
||||||
.context("missing ssrc-info")?
|
.context("missing ssrc-info")?
|
||||||
.owner
|
.owner
|
||||||
.clone();
|
.clone();
|
||||||
if owner == "jvb" {
|
|
||||||
debug!("skipping ssrc (owner = jvb)");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc);
|
||||||
remote_ssrc_map.insert(
|
remote_ssrc_map.insert(
|
||||||
ssrc.id.parse()?,
|
ssrc.id.parse()?,
|
||||||
Source {
|
Source {
|
||||||
ssrc: ssrc.id.parse()?,
|
ssrc: ssrc.id.parse()?,
|
||||||
participant_id: owner
|
participant_id: if owner == "jvb" {
|
||||||
.split('/')
|
None
|
||||||
.nth(1)
|
}
|
||||||
.context("invalid ssrc-info owner")?
|
else {
|
||||||
.to_owned(),
|
Some(
|
||||||
|
owner
|
||||||
|
.split('/')
|
||||||
|
.nth(1)
|
||||||
|
.context("invalid ssrc-info owner")?
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
},
|
||||||
media_type: if description.media == "audio" {
|
media_type: if description.media == "audio" {
|
||||||
MediaType::Audio
|
MediaType::Audio
|
||||||
}
|
}
|
||||||
|
@ -576,10 +581,10 @@ impl JingleSession {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let handle = Handle::current();
|
let handle = Handle::current();
|
||||||
let inner_ = conference.inner.clone();
|
let jingle_session = conference.jingle_session.clone();
|
||||||
rtpbin.connect("new-jitterbuffer", false, move |values| {
|
rtpbin.connect("new-jitterbuffer", false, move |values| {
|
||||||
let handle = handle.clone();
|
let handle = handle.clone();
|
||||||
let inner_ = inner_.clone();
|
let jingle_session = jingle_session.clone();
|
||||||
let f = move || {
|
let f = move || {
|
||||||
let rtpjitterbuffer: gstreamer::Element = values[1].get()?;
|
let rtpjitterbuffer: gstreamer::Element = values[1].get()?;
|
||||||
let session: u32 = values[2].get()?;
|
let session: u32 = values[2].get()?;
|
||||||
|
@ -590,13 +595,12 @@ impl JingleSession {
|
||||||
);
|
);
|
||||||
|
|
||||||
let source = handle.block_on(async move {
|
let source = handle.block_on(async move {
|
||||||
let locked_inner = inner_.lock().await;
|
|
||||||
let jingle_session = locked_inner
|
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
|
||||||
.context("not connected (no jingle session)")?;
|
|
||||||
Ok::<_, anyhow::Error>(
|
Ok::<_, anyhow::Error>(
|
||||||
jingle_session
|
jingle_session
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.context("not connected (no jingle session)")?
|
||||||
.remote_ssrc_map
|
.remote_ssrc_map
|
||||||
.get(&ssrc)
|
.get(&ssrc)
|
||||||
.context(format!("unknown ssrc: {}", ssrc))?
|
.context(format!("unknown ssrc: {}", ssrc))?
|
||||||
|
@ -604,7 +608,7 @@ impl JingleSession {
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
debug!("jitterbuffer is for remote source: {:?}", source);
|
debug!("jitterbuffer is for remote source: {:?}", source);
|
||||||
if source.media_type == MediaType::Video {
|
if source.media_type == MediaType::Video && source.participant_id.is_some() {
|
||||||
debug!("enabling RTX for ssrc {}", ssrc);
|
debug!("enabling RTX for ssrc {}", ssrc);
|
||||||
rtpjitterbuffer.set_property("do-retransmission", true)?;
|
rtpjitterbuffer.set_property("do-retransmission", true)?;
|
||||||
}
|
}
|
||||||
|
@ -703,7 +707,7 @@ impl JingleSession {
|
||||||
|
|
||||||
{
|
{
|
||||||
let handle = Handle::current();
|
let handle = Handle::current();
|
||||||
let inner = conference.inner.clone();
|
let conference = conference.clone();
|
||||||
let pipeline = pipeline.clone();
|
let pipeline = pipeline.clone();
|
||||||
let rtpbin_ = rtpbin.clone();
|
let rtpbin_ = rtpbin.clone();
|
||||||
rtpbin.connect("pad-added", false, move |values| {
|
rtpbin.connect("pad-added", false, move |values| {
|
||||||
|
@ -717,13 +721,13 @@ impl JingleSession {
|
||||||
let ssrc: u32 = parts.next().context("malformed pad name")?.parse()?;
|
let ssrc: u32 = parts.next().context("malformed pad name")?.parse()?;
|
||||||
let pt: u8 = parts.next().context("malformed pad name")?.parse()?;
|
let pt: u8 = parts.next().context("malformed pad name")?.parse()?;
|
||||||
let source = handle.block_on(async {
|
let source = handle.block_on(async {
|
||||||
let locked_inner = inner.lock().await;
|
|
||||||
let jingle_session = locked_inner
|
|
||||||
.jingle_session
|
|
||||||
.as_ref()
|
|
||||||
.context("not connected (no jingle session)")?;
|
|
||||||
Ok::<_, anyhow::Error>(
|
Ok::<_, anyhow::Error>(
|
||||||
jingle_session
|
conference
|
||||||
|
.jingle_session
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.context("not connected (no jingle session)")?
|
||||||
.remote_ssrc_map
|
.remote_ssrc_map
|
||||||
.get(&ssrc)
|
.get(&ssrc)
|
||||||
.context(format!("unknown ssrc: {}", ssrc))?
|
.context(format!("unknown ssrc: {}", ssrc))?
|
||||||
|
@ -805,26 +809,32 @@ impl JingleSession {
|
||||||
.static_pad("src")
|
.static_pad("src")
|
||||||
.context("depayloader has no src pad")?;
|
.context("depayloader has no src pad")?;
|
||||||
|
|
||||||
if let Some(participant_bin) =
|
if let Some(participant_id) = source.participant_id {
|
||||||
pipeline.by_name(&format!("participant_{}", source.participant_id))
|
handle.block_on(conference.ensure_participant(&participant_id))?;
|
||||||
{
|
if let Some(participant_bin) =
|
||||||
let sink_pad_name = match source.media_type {
|
pipeline.by_name(&format!("participant_{}", participant_id))
|
||||||
MediaType::Audio => "audio",
|
{
|
||||||
MediaType::Video => "video",
|
let sink_pad_name = match source.media_type {
|
||||||
};
|
MediaType::Audio => "audio",
|
||||||
if let Some(sink_pad) = participant_bin.static_pad(sink_pad_name) {
|
MediaType::Video => "video",
|
||||||
debug!("linking depayloader to participant bin");
|
};
|
||||||
src_pad.link(&sink_pad)?;
|
if let Some(sink_pad) = participant_bin.static_pad(sink_pad_name) {
|
||||||
|
debug!("linking depayloader to participant bin");
|
||||||
|
src_pad.link(&sink_pad)?;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn!(
|
||||||
|
"no {} sink pad in {} participant bin",
|
||||||
|
sink_pad_name, participant_id
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
warn!(
|
debug!("no participant bin for {}", participant_id);
|
||||||
"no {} sink pad in {} participant bin",
|
|
||||||
sink_pad_name, source.participant_id
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
debug!("no participant bin for {}", source.participant_id);
|
debug!("not looking for participant bin, source is owned by JVB");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !src_pad.is_linked() {
|
if !src_pad.is_linked() {
|
||||||
|
@ -861,38 +871,43 @@ impl JingleSession {
|
||||||
)?;
|
)?;
|
||||||
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?;
|
audio_sink_element.set_property("min-ptime", 10i64 * 1000 * 1000)?;
|
||||||
audio_sink_element.set_property("ssrc", audio_ssrc)?;
|
audio_sink_element.set_property("ssrc", audio_ssrc)?;
|
||||||
audio_sink_element.set_property("auto-header-extension", false)?;
|
if audio_sink_element.has_property("auto-header-extension", None) {
|
||||||
audio_sink_element.connect("request-extension", false, move |values| {
|
audio_sink_element.set_property("auto-header-extension", false)?;
|
||||||
let f = || {
|
audio_sink_element.connect("request-extension", false, move |values| {
|
||||||
let ext_id: u32 = values[1].get()?;
|
let f = || {
|
||||||
let ext_uri: String = values[2].get()?;
|
let ext_id: u32 = values[1].get()?;
|
||||||
debug!(
|
let ext_uri: String = values[2].get()?;
|
||||||
"audio payloader requested extension: {} {}",
|
debug!(
|
||||||
ext_id, ext_uri
|
"audio payloader requested extension: {} {}",
|
||||||
);
|
ext_id, ext_uri
|
||||||
let hdrext =
|
);
|
||||||
RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?;
|
let hdrext =
|
||||||
hdrext.set_id(ext_id);
|
RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?;
|
||||||
if ext_uri == RTP_HDREXT_ABS_SEND_TIME {
|
hdrext.set_id(ext_id);
|
||||||
|
if ext_uri == RTP_HDREXT_ABS_SEND_TIME {
|
||||||
|
}
|
||||||
|
else if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL {
|
||||||
|
}
|
||||||
|
else if ext_uri == RTP_HDREXT_TRANSPORT_CC {
|
||||||
|
// hdrext.set_property("n-streams", 2u32)?;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bail!("unknown rtp hdrext: {}", ext_uri);
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(hdrext)
|
||||||
|
};
|
||||||
|
match f() {
|
||||||
|
Ok(hdrext) => Some(hdrext.to_value()),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("request-extension: {:?}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
else if ext_uri == RTP_HDREXT_SSRC_AUDIO_LEVEL {
|
})?;
|
||||||
}
|
}
|
||||||
else if ext_uri == RTP_HDREXT_TRANSPORT_CC {
|
else {
|
||||||
// hdrext.set_property("n-streams", 2u32)?;
|
debug!("audio payloader: no rtp header extension support");
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
bail!("unknown rtp hdrext: {}", ext_uri);
|
|
||||||
}
|
|
||||||
Ok::<_, anyhow::Error>(hdrext)
|
|
||||||
};
|
|
||||||
match f() {
|
|
||||||
Ok(hdrext) => Some(hdrext.to_value()),
|
|
||||||
Err(e) => {
|
|
||||||
warn!("request-extension: {:?}", e);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
pipeline.add(&audio_sink_element)?;
|
pipeline.add(&audio_sink_element)?;
|
||||||
|
|
||||||
let video_sink_element = match conference.config.video_codec.as_str() {
|
let video_sink_element = match conference.config.video_codec.as_str() {
|
||||||
|
@ -926,43 +941,47 @@ impl JingleSession {
|
||||||
other => bail!("unsupported video codec: {}", other),
|
other => bail!("unsupported video codec: {}", other),
|
||||||
};
|
};
|
||||||
video_sink_element.set_property("ssrc", video_ssrc)?;
|
video_sink_element.set_property("ssrc", video_ssrc)?;
|
||||||
video_sink_element.set_property("auto-header-extension", false)?;
|
if video_sink_element.has_property("auto-header-extension", None) {
|
||||||
video_sink_element.connect("request-extension", false, move |values| {
|
video_sink_element.set_property("auto-header-extension", false)?;
|
||||||
let f = || {
|
video_sink_element.connect("request-extension", false, move |values| {
|
||||||
let ext_id: u32 = values[1].get()?;
|
let f = || {
|
||||||
let ext_uri: String = values[2].get()?;
|
let ext_id: u32 = values[1].get()?;
|
||||||
debug!(
|
let ext_uri: String = values[2].get()?;
|
||||||
"video payloader requested extension: {} {}",
|
debug!(
|
||||||
ext_id, ext_uri
|
"video payloader requested extension: {} {}",
|
||||||
);
|
ext_id, ext_uri
|
||||||
let hdrext =
|
);
|
||||||
RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?;
|
let hdrext =
|
||||||
hdrext.set_id(ext_id);
|
RTPHeaderExtension::create_from_uri(&ext_uri).context("failed to create hdrext")?;
|
||||||
if ext_uri == RTP_HDREXT_ABS_SEND_TIME {
|
hdrext.set_id(ext_id);
|
||||||
|
if ext_uri == RTP_HDREXT_ABS_SEND_TIME {
|
||||||
|
}
|
||||||
|
else if ext_uri == RTP_HDREXT_TRANSPORT_CC {
|
||||||
|
// hdrext.set_property("n-streams", 2u32)?;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bail!("unknown rtp hdrext: {}", ext_uri);
|
||||||
|
}
|
||||||
|
Ok::<_, anyhow::Error>(hdrext)
|
||||||
|
};
|
||||||
|
match f() {
|
||||||
|
Ok(hdrext) => Some(hdrext.to_value()),
|
||||||
|
Err(e) => {
|
||||||
|
warn!("request-extension: {:?}", e);
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
else if ext_uri == RTP_HDREXT_TRANSPORT_CC {
|
})?;
|
||||||
// hdrext.set_property("n-streams", 2u32)?;
|
}
|
||||||
}
|
else {
|
||||||
else {
|
debug!("video payloader: no rtp header extension support");
|
||||||
bail!("unknown rtp hdrext: {}", ext_uri);
|
}
|
||||||
}
|
|
||||||
Ok::<_, anyhow::Error>(hdrext)
|
|
||||||
};
|
|
||||||
match f() {
|
|
||||||
Ok(hdrext) => Some(hdrext.to_value()),
|
|
||||||
Err(e) => {
|
|
||||||
warn!("request-extension: {:?}", e);
|
|
||||||
None
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
pipeline.add(&video_sink_element)?;
|
pipeline.add(&video_sink_element)?;
|
||||||
|
|
||||||
let mut audio_caps = gstreamer::Caps::builder("application/x-rtp");
|
let mut audio_caps = gstreamer::Caps::builder("application/x-rtp");
|
||||||
// TODO: fails to negotiate
|
if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
||||||
// if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_SSRC_AUDIO_LEVEL);
|
||||||
// audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_SSRC_AUDIO_LEVEL);
|
}
|
||||||
// }
|
|
||||||
if let Some(hdrext) = audio_hdrext_transport_cc {
|
if let Some(hdrext) = audio_hdrext_transport_cc {
|
||||||
audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_TRANSPORT_CC);
|
audio_caps = audio_caps.field(&format!("extmap-{}", hdrext), RTP_HDREXT_TRANSPORT_CC);
|
||||||
}
|
}
|
||||||
|
@ -981,13 +1000,19 @@ impl JingleSession {
|
||||||
video_capsfilter.set_property("caps", video_caps.build())?;
|
video_capsfilter.set_property("caps", video_caps.build())?;
|
||||||
pipeline.add(&video_capsfilter)?;
|
pipeline.add(&video_capsfilter)?;
|
||||||
|
|
||||||
debug!("linking video payloader -> rtpbin");
|
let rtpfunnel = gstreamer::ElementFactory::make("rtpfunnel", None)?;
|
||||||
video_sink_element.link(&video_capsfilter)?;
|
pipeline.add(&rtpfunnel)?;
|
||||||
video_capsfilter.link_pads(None, &rtpbin, Some("send_rtp_sink_0"))?;
|
|
||||||
|
|
||||||
debug!("linking audio payloader -> rtpbin");
|
debug!("linking video payloader -> rtpfunnel");
|
||||||
|
video_sink_element.link(&video_capsfilter)?;
|
||||||
|
video_capsfilter.link(&rtpfunnel)?;
|
||||||
|
|
||||||
|
debug!("linking audio payloader -> rtpfunnel");
|
||||||
audio_sink_element.link(&audio_capsfilter)?;
|
audio_sink_element.link(&audio_capsfilter)?;
|
||||||
audio_capsfilter.link_pads(None, &rtpbin, Some("send_rtp_sink_1"))?;
|
audio_capsfilter.link(&rtpfunnel)?;
|
||||||
|
|
||||||
|
debug!("linking rtpfunnel -> rtpbin");
|
||||||
|
rtpfunnel.link_pads(None, &rtpbin, Some("send_rtp_sink_0"))?;
|
||||||
|
|
||||||
debug!("link dtlssrtpdec -> rtpbin");
|
debug!("link dtlssrtpdec -> rtpbin");
|
||||||
dtlssrtpdec.link_pads(Some("rtp_src"), &rtpbin, Some("recv_rtp_sink_0"))?;
|
dtlssrtpdec.link_pads(Some("rtp_src"), &rtpbin, Some("recv_rtp_sink_0"))?;
|
||||||
|
@ -996,8 +1021,6 @@ impl JingleSession {
|
||||||
debug!("linking rtpbin -> dtlssrtpenc");
|
debug!("linking rtpbin -> dtlssrtpenc");
|
||||||
rtpbin.link_pads(Some("send_rtp_src_0"), &dtlssrtpenc, Some("rtp_sink_0"))?;
|
rtpbin.link_pads(Some("send_rtp_src_0"), &dtlssrtpenc, Some("rtp_sink_0"))?;
|
||||||
rtpbin.link_pads(Some("send_rtcp_src_0"), &dtlssrtpenc, Some("rtcp_sink_0"))?;
|
rtpbin.link_pads(Some("send_rtcp_src_0"), &dtlssrtpenc, Some("rtcp_sink_0"))?;
|
||||||
rtpbin.link_pads(Some("send_rtp_src_1"), &dtlssrtpenc, Some("rtp_sink_1"))?;
|
|
||||||
rtpbin.link_pads(Some("send_rtcp_src_1"), &dtlssrtpenc, Some("rtcp_sink_1"))?;
|
|
||||||
|
|
||||||
debug!("linking ice src -> dtlssrtpdec");
|
debug!("linking ice src -> dtlssrtpdec");
|
||||||
nicesrc.link(&dtlssrtpdec)?;
|
nicesrc.link(&dtlssrtpdec)?;
|
||||||
|
@ -1177,15 +1200,12 @@ impl JingleSession {
|
||||||
};
|
};
|
||||||
|
|
||||||
if initiate_content.name.0 == "audio" {
|
if initiate_content.name.0 == "audio" {
|
||||||
// TODO: fails to negotiate
|
if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
||||||
// if let Some(hdrext) = audio_hdrext_ssrc_audio_level {
|
description.hdrexts.push(RtpHdrext::new(
|
||||||
// if audio_hdrext_supported {
|
hdrext.to_string(),
|
||||||
// description.hdrexts.push(RtpHdrext::new(hdrext.to_string(), RTP_HDREXT_SSRC_AUDIO_LEVEL.to_owned()));
|
RTP_HDREXT_SSRC_AUDIO_LEVEL.to_owned(),
|
||||||
// }
|
));
|
||||||
// else {
|
}
|
||||||
// debug!("ssrc-audio-level hdrext requested but not supported");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if let Some(hdrext) = audio_hdrext_transport_cc {
|
if let Some(hdrext) = audio_hdrext_transport_cc {
|
||||||
description.hdrexts.push(RtpHdrext::new(
|
description.hdrexts.push(RtpHdrext::new(
|
||||||
hdrext.to_string(),
|
hdrext.to_string(),
|
||||||
|
@ -1276,21 +1296,24 @@ impl JingleSession {
|
||||||
.context("missing ssrc-info")?
|
.context("missing ssrc-info")?
|
||||||
.owner
|
.owner
|
||||||
.clone();
|
.clone();
|
||||||
if owner == "jvb" {
|
|
||||||
debug!("skipping ssrc (owner = jvb)");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc);
|
debug!("adding ssrc to remote_ssrc_map: {:?}", ssrc);
|
||||||
self.remote_ssrc_map.insert(
|
self.remote_ssrc_map.insert(
|
||||||
ssrc.id.parse()?,
|
ssrc.id.parse()?,
|
||||||
Source {
|
Source {
|
||||||
ssrc: ssrc.id.parse()?,
|
ssrc: ssrc.id.parse()?,
|
||||||
participant_id: owner
|
participant_id: if owner == "jvb" {
|
||||||
.split('/')
|
None
|
||||||
.nth(1)
|
}
|
||||||
.context("invalid ssrc-info owner")?
|
else {
|
||||||
.to_owned(),
|
Some(
|
||||||
|
owner
|
||||||
|
.split('/')
|
||||||
|
.nth(1)
|
||||||
|
.context("invalid ssrc-info owner")?
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
},
|
||||||
media_type: if description.media == "audio" {
|
media_type: if description.media == "audio" {
|
||||||
MediaType::Audio
|
MediaType::Audio
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
pub mod colibri;
|
mod colibri;
|
||||||
mod conference;
|
mod conference;
|
||||||
mod connection;
|
|
||||||
mod jingle;
|
mod jingle;
|
||||||
mod pinger;
|
mod pinger;
|
||||||
mod source;
|
mod source;
|
||||||
|
@ -10,9 +9,10 @@ mod xmpp;
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
colibri::ColibriMessage,
|
colibri::ColibriMessage,
|
||||||
conference::{JitsiConference, JitsiConferenceConfig, Participant},
|
conference::{Feature, JitsiConference, JitsiConferenceConfig, Participant},
|
||||||
connection::JitsiConnection,
|
|
||||||
source::MediaType,
|
source::MediaType,
|
||||||
|
stanza_filter::StanzaFilter,
|
||||||
|
xmpp::connection::{Authentication, Connection},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "tracing-subscriber")]
|
#[cfg(feature = "tracing-subscriber")]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Source {
|
pub(crate) struct Source {
|
||||||
pub(crate) ssrc: u32,
|
pub(crate) ssrc: u32,
|
||||||
pub(crate) participant_id: String,
|
pub(crate) participant_id: Option<String>,
|
||||||
pub(crate) media_type: MediaType,
|
pub(crate) media_type: MediaType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use async_trait::async_trait;
|
||||||
use xmpp_parsers::Element;
|
use xmpp_parsers::Element;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub(crate) trait StanzaFilter {
|
pub trait StanzaFilter {
|
||||||
fn filter(&self, element: &Element) -> bool;
|
fn filter(&self, element: &Element) -> bool;
|
||||||
async fn take(&self, element: Element) -> Result<()>;
|
async fn take(&self, element: Element) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,10 @@ use xmpp_parsers::{
|
||||||
BareJid, Element, FullJid, Jid,
|
BareJid, Element, FullJid, Jid,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{pinger::Pinger, stanza_filter::StanzaFilter, util::generate_id, xmpp};
|
||||||
conference::{JitsiConference, JitsiConferenceConfig},
|
|
||||||
pinger::Pinger,
|
|
||||||
stanza_filter::StanzaFilter,
|
|
||||||
util::generate_id,
|
|
||||||
xmpp,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum JitsiConnectionState {
|
enum ConnectionState {
|
||||||
OpeningPreAuthentication,
|
OpeningPreAuthentication,
|
||||||
ReceivingFeaturesPreAuthentication,
|
ReceivingFeaturesPreAuthentication,
|
||||||
Authenticating,
|
Authenticating,
|
||||||
|
@ -44,35 +38,41 @@ enum JitsiConnectionState {
|
||||||
Idle,
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct JitsiConnectionInner {
|
struct ConnectionInner {
|
||||||
state: JitsiConnectionState,
|
state: ConnectionState,
|
||||||
xmpp_domain: BareJid,
|
|
||||||
jid: Option<FullJid>,
|
jid: Option<FullJid>,
|
||||||
|
xmpp_domain: BareJid,
|
||||||
|
authentication: Authentication,
|
||||||
external_services: Vec<xmpp::extdisco::Service>,
|
external_services: Vec<xmpp::extdisco::Service>,
|
||||||
connected_tx: Option<oneshot::Sender<Result<()>>>,
|
connected_tx: Option<oneshot::Sender<Result<()>>>,
|
||||||
stanza_filters: Vec<Box<dyn StanzaFilter + Send + Sync>>,
|
stanza_filters: Vec<Box<dyn StanzaFilter + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for JitsiConnectionInner {
|
impl fmt::Debug for ConnectionInner {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("JitsiConnectionInner")
|
f.debug_struct("ConnectionInner")
|
||||||
.field("state", &self.state)
|
.field("state", &self.state)
|
||||||
.field("xmpp_domain", &self.xmpp_domain)
|
|
||||||
.field("jid", &self.jid)
|
.field("jid", &self.jid)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct JitsiConnection {
|
pub struct Connection {
|
||||||
tx: mpsc::Sender<Element>,
|
pub(crate) tx: mpsc::Sender<Element>,
|
||||||
inner: Arc<Mutex<JitsiConnectionInner>>,
|
inner: Arc<Mutex<ConnectionInner>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JitsiConnection {
|
pub enum Authentication {
|
||||||
|
Anonymous,
|
||||||
|
Plain { username: String, password: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
websocket_url: &str,
|
websocket_url: &str,
|
||||||
xmpp_domain: &str,
|
xmpp_domain: &str,
|
||||||
|
authentication: Authentication,
|
||||||
) -> Result<(Self, impl Future<Output = ()>)> {
|
) -> Result<(Self, impl Future<Output = ()>)> {
|
||||||
let websocket_url: Uri = websocket_url.parse().context("invalid WebSocket URL")?;
|
let websocket_url: Uri = websocket_url.parse().context("invalid WebSocket URL")?;
|
||||||
let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?;
|
let xmpp_domain: BareJid = xmpp_domain.parse().context("invalid XMPP domain")?;
|
||||||
|
@ -88,10 +88,11 @@ impl JitsiConnection {
|
||||||
let (sink, stream) = websocket.split();
|
let (sink, stream) = websocket.split();
|
||||||
let (tx, rx) = mpsc::channel(64);
|
let (tx, rx) = mpsc::channel(64);
|
||||||
|
|
||||||
let inner = Arc::new(Mutex::new(JitsiConnectionInner {
|
let inner = Arc::new(Mutex::new(ConnectionInner {
|
||||||
state: JitsiConnectionState::OpeningPreAuthentication,
|
state: ConnectionState::OpeningPreAuthentication,
|
||||||
xmpp_domain,
|
|
||||||
jid: None,
|
jid: None,
|
||||||
|
xmpp_domain,
|
||||||
|
authentication,
|
||||||
external_services: vec![],
|
external_services: vec![],
|
||||||
connected_tx: None,
|
connected_tx: None,
|
||||||
stanza_filters: vec![],
|
stanza_filters: vec![],
|
||||||
|
@ -102,8 +103,8 @@ impl JitsiConnection {
|
||||||
inner: inner.clone(),
|
inner: inner.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let writer = JitsiConnection::write_loop(rx, sink);
|
let writer = Connection::write_loop(rx, sink);
|
||||||
let reader = JitsiConnection::read_loop(inner, tx, stream);
|
let reader = Connection::read_loop(inner, tx, stream);
|
||||||
|
|
||||||
let background = async move {
|
let background = async move {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
|
@ -115,6 +116,11 @@ impl JitsiConnection {
|
||||||
Ok((connection, background))
|
Ok((connection, background))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn add_stanza_filter(&self, stanza_filter: impl StanzaFilter + Send + Sync + 'static) {
|
||||||
|
let mut locked_inner = self.inner.lock().await;
|
||||||
|
locked_inner.stanza_filters.push(Box::new(stanza_filter));
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn connect(&self) -> Result<()> {
|
pub async fn connect(&self) -> Result<()> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
|
@ -128,48 +134,14 @@ impl JitsiConnection {
|
||||||
rx.await?
|
rx.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn join_conference(
|
pub async fn jid(&self) -> Option<FullJid> {
|
||||||
&self,
|
let mut locked_inner = self.inner.lock().await;
|
||||||
glib_main_context: glib::MainContext,
|
locked_inner.jid.clone()
|
||||||
config: JitsiConferenceConfig,
|
}
|
||||||
) -> Result<JitsiConference> {
|
|
||||||
let conference_stanza = xmpp::jitsi::Conference {
|
|
||||||
machine_uid: Uuid::new_v4().to_string(),
|
|
||||||
room: config.muc.to_string(),
|
|
||||||
properties: hashmap! {
|
|
||||||
// Disable voice processing
|
|
||||||
"stereo".to_string() => "true".to_string(),
|
|
||||||
"startBitrate".to_string() => "800".to_string(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let iq =
|
pub async fn external_services(&self) -> Vec<xmpp::extdisco::Service> {
|
||||||
Iq::from_set(generate_id(), conference_stanza).with_to(Jid::Full(config.focus.clone()));
|
let mut locked_inner = self.inner.lock().await;
|
||||||
self.tx.send(iq.into()).await?;
|
locked_inner.external_services.clone()
|
||||||
|
|
||||||
let conference = {
|
|
||||||
let mut locked_inner = self.inner.lock().await;
|
|
||||||
let conference = JitsiConference::new(
|
|
||||||
glib_main_context,
|
|
||||||
locked_inner
|
|
||||||
.jid
|
|
||||||
.as_ref()
|
|
||||||
.context("not connected (no jid)")?
|
|
||||||
.clone(),
|
|
||||||
self.tx.clone(),
|
|
||||||
config,
|
|
||||||
locked_inner.external_services.clone(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
locked_inner
|
|
||||||
.stanza_filters
|
|
||||||
.push(Box::new(conference.clone()));
|
|
||||||
conference
|
|
||||||
};
|
|
||||||
|
|
||||||
conference.connected().await?;
|
|
||||||
|
|
||||||
Ok(conference)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn write_loop<S>(rx: mpsc::Receiver<Element>, mut sink: S) -> Result<()>
|
async fn write_loop<S>(rx: mpsc::Receiver<Element>, mut sink: S) -> Result<()>
|
||||||
|
@ -189,7 +161,7 @@ impl JitsiConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn read_loop<S>(
|
async fn read_loop<S>(
|
||||||
inner: Arc<Mutex<JitsiConnectionInner>>,
|
inner: Arc<Mutex<ConnectionInner>>,
|
||||||
tx: mpsc::Sender<Element>,
|
tx: mpsc::Sender<Element>,
|
||||||
mut stream: S,
|
mut stream: S,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
|
@ -217,7 +189,7 @@ impl JitsiConnection {
|
||||||
|
|
||||||
let mut locked_inner = inner.lock().await;
|
let mut locked_inner = inner.lock().await;
|
||||||
|
|
||||||
use JitsiConnectionState::*;
|
use ConnectionState::*;
|
||||||
match locked_inner.state {
|
match locked_inner.state {
|
||||||
OpeningPreAuthentication => {
|
OpeningPreAuthentication => {
|
||||||
Open::try_from(element)?;
|
Open::try_from(element)?;
|
||||||
|
@ -225,9 +197,22 @@ impl JitsiConnection {
|
||||||
locked_inner.state = ReceivingFeaturesPreAuthentication;
|
locked_inner.state = ReceivingFeaturesPreAuthentication;
|
||||||
},
|
},
|
||||||
ReceivingFeaturesPreAuthentication => {
|
ReceivingFeaturesPreAuthentication => {
|
||||||
let auth = Auth {
|
let auth = match &locked_inner.authentication {
|
||||||
mechanism: Mechanism::Anonymous,
|
Authentication::Anonymous => Auth {
|
||||||
data: vec![],
|
mechanism: Mechanism::Anonymous,
|
||||||
|
data: vec![],
|
||||||
|
},
|
||||||
|
Authentication::Plain { username, password } => {
|
||||||
|
let mut data = Vec::with_capacity(username.len() + password.len() + 2);
|
||||||
|
data.push(0u8);
|
||||||
|
data.extend_from_slice(username.as_bytes());
|
||||||
|
data.push(0u8);
|
||||||
|
data.extend_from_slice(password.as_bytes());
|
||||||
|
Auth {
|
||||||
|
mechanism: Mechanism::Plain,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
tx.send(auth.into()).await?;
|
tx.send(auth.into()).await?;
|
||||||
locked_inner.state = Authenticating;
|
locked_inner.state = Authenticating;
|
||||||
|
@ -241,8 +226,10 @@ impl JitsiConnection {
|
||||||
},
|
},
|
||||||
OpeningPostAuthentication => {
|
OpeningPostAuthentication => {
|
||||||
Open::try_from(element)?;
|
Open::try_from(element)?;
|
||||||
info!("Logged in anonymously");
|
match &locked_inner.authentication {
|
||||||
|
Authentication::Anonymous => info!("Logged in anonymously"),
|
||||||
|
Authentication::Plain { .. } => info!("Logged in with PLAIN"),
|
||||||
|
}
|
||||||
locked_inner.state = ReceivingFeaturesPostAuthentication;
|
locked_inner.state = ReceivingFeaturesPostAuthentication;
|
||||||
},
|
},
|
||||||
ReceivingFeaturesPostAuthentication => {
|
ReceivingFeaturesPostAuthentication => {
|
||||||
|
@ -250,28 +237,33 @@ impl JitsiConnection {
|
||||||
tx.send(iq.into()).await?;
|
tx.send(iq.into()).await?;
|
||||||
locked_inner.state = Binding;
|
locked_inner.state = Binding;
|
||||||
},
|
},
|
||||||
Binding => {
|
Binding => match Iq::try_from(element) {
|
||||||
let iq = Iq::try_from(element)?;
|
Ok(iq) => {
|
||||||
let jid = if let IqType::Result(Some(element)) = iq.payload {
|
let jid = if let IqType::Result(Some(element)) = iq.payload {
|
||||||
let bind = BindResponse::try_from(element)?;
|
let bind = BindResponse::try_from(element)?;
|
||||||
FullJid::try_from(bind)?
|
FullJid::try_from(bind)?
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
bail!("bind failed");
|
bail!("bind failed");
|
||||||
};
|
};
|
||||||
info!("My JID: {}", jid);
|
info!("My JID: {}", jid);
|
||||||
locked_inner.jid = Some(jid.clone());
|
locked_inner.jid = Some(jid.clone());
|
||||||
|
|
||||||
locked_inner.stanza_filters.push(Box::new(Pinger {
|
locked_inner.stanza_filters.push(Box::new(Pinger {
|
||||||
jid: jid.clone(),
|
jid: jid.clone(),
|
||||||
tx: tx.clone(),
|
tx: tx.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let iq = Iq::from_get(generate_id(), DiscoInfoQuery { node: None })
|
let iq = Iq::from_get(generate_id(), DiscoInfoQuery { node: None })
|
||||||
.with_from(Jid::Full(jid.clone()))
|
.with_from(Jid::Full(jid.clone()))
|
||||||
.with_to(Jid::Bare(locked_inner.xmpp_domain.clone()));
|
.with_to(Jid::Bare(locked_inner.xmpp_domain.clone()));
|
||||||
tx.send(iq.into()).await?;
|
tx.send(iq.into()).await?;
|
||||||
locked_inner.state = Discovering;
|
locked_inner.state = Discovering;
|
||||||
|
},
|
||||||
|
Err(e) => debug!(
|
||||||
|
"received unexpected element while waiting for bind response: {}",
|
||||||
|
e
|
||||||
|
),
|
||||||
},
|
},
|
||||||
Discovering => {
|
Discovering => {
|
||||||
let iq = Iq::try_from(element)?;
|
let iq = Iq::try_from(element)?;
|
|
@ -26,7 +26,7 @@ impl From<ServicesQuery> for Element {
|
||||||
impl IqGetPayload for ServicesQuery {}
|
impl IqGetPayload for ServicesQuery {}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct Service {
|
pub struct Service {
|
||||||
pub(crate) r#type: String,
|
pub(crate) r#type: String,
|
||||||
pub(crate) name: Option<String>,
|
pub(crate) name: Option<String>,
|
||||||
pub(crate) host: String,
|
pub(crate) host: String,
|
||||||
|
|
|
@ -37,3 +37,19 @@ impl From<Conference> for Element {
|
||||||
builder.build()
|
builder.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct JsonMessage {
|
||||||
|
pub(crate) payload: serde_json::Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<JsonMessage> for Element {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(message: JsonMessage) -> Result<Element> {
|
||||||
|
Ok(
|
||||||
|
Element::builder("json-message", ns::JITSI_JITMEET)
|
||||||
|
.append(serde_json::to_string(&message.payload)?)
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod connection;
|
||||||
pub(crate) mod extdisco;
|
pub(crate) mod extdisco;
|
||||||
pub(crate) mod jitsi;
|
pub(crate) mod jitsi;
|
||||||
mod ns;
|
mod ns;
|
||||||
|
|
|
@ -2,3 +2,5 @@
|
||||||
pub(crate) const EXTDISCO: &str = "urn:xmpp:extdisco:2";
|
pub(crate) const EXTDISCO: &str = "urn:xmpp:extdisco:2";
|
||||||
|
|
||||||
pub(crate) const JITSI_FOCUS: &str = "http://jitsi.org/protocol/focus";
|
pub(crate) const JITSI_FOCUS: &str = "http://jitsi.org/protocol/focus";
|
||||||
|
|
||||||
|
pub(crate) const JITSI_JITMEET: &str = "http://jitsi.org/jitmeet";
|
||||||
|
|
Loading…
Reference in New Issue