use std::sync::Arc;
use moq_net::Session;
use url::Url;
use crate::error::MoqError;
use crate::ffi::Task;
use crate::origin::MoqOriginProducer;
struct Client {
config: moq_native::ClientConfig,
publish: Option<Arc<MoqOriginProducer>>,
consume: Option<Arc<MoqOriginProducer>>,
}
impl Client {
async fn connect(&self, url: Url) -> Result<Arc<MoqSession>, MoqError> {
let client = self.config.clone().init().map_err(map_connect_error)?;
let publish = self.publish.as_ref().map(|o| o.inner().consume());
let consume = self.consume.as_ref().map(|o| o.inner().clone());
let session = client
.with_publish(publish)
.with_consume(consume)
.connect(url)
.await
.map_err(map_connect_error)?;
Ok(Arc::new(MoqSession::new(session)))
}
}
fn map_connect_error(err: moq_native::Error) -> MoqError {
match err.connect_error() {
Some(moq_native::ConnectError::Unauthorized) => MoqError::Unauthorized,
Some(moq_native::ConnectError::Forbidden) => MoqError::Forbidden,
_ => MoqError::Connect(format!("{err}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_native_auth_connect_errors() {
assert!(matches!(
map_connect_error(moq_native::ConnectError::Unauthorized.into()),
MoqError::Unauthorized
));
assert!(matches!(
map_connect_error(moq_native::ConnectError::Forbidden.into()),
MoqError::Forbidden
));
}
}
#[derive(uniffi::Object)]
pub struct MoqClient {
task: Task<Client>,
}
#[uniffi::export]
impl MoqClient {
#[uniffi::constructor]
pub fn new() -> Arc<Self> {
let _guard = crate::ffi::RUNTIME.enter();
Arc::new(Self {
task: Task::new(Client {
config: moq_native::ClientConfig::default(),
publish: None,
consume: None,
}),
})
}
pub fn set_tls_disable_verify(&self, disable: bool) {
if let Some(mut state) = self.task.lock() {
state.config.tls.disable_verify = Some(disable);
}
}
pub fn set_tls_roots(&self, paths: Vec<String>) {
if let Some(mut state) = self.task.lock() {
state.config.tls.root = paths.into_iter().map(Into::into).collect();
}
}
pub fn set_tls_fingerprints(&self, fingerprints: Vec<String>) {
if let Some(mut state) = self.task.lock() {
state.config.tls.fingerprint = fingerprints;
}
}
pub fn set_bind(&self, addr: String) -> Result<(), MoqError> {
let parsed: std::net::SocketAddr = addr
.parse()
.map_err(|err| MoqError::Bind(format!("invalid bind address: {err}")))?;
if let Some(mut state) = self.task.lock() {
state.config.bind = parsed;
}
Ok(())
}
pub fn set_publish(&self, origin: Option<Arc<MoqOriginProducer>>) {
if let Some(mut state) = self.task.lock() {
state.publish = origin;
}
}
pub fn set_consume(&self, origin: Option<Arc<MoqOriginProducer>>) {
if let Some(mut state) = self.task.lock() {
state.consume = origin;
}
}
pub async fn connect(&self, url: String) -> Result<Arc<MoqSession>, MoqError> {
let url = Url::parse(&url)?;
self.task.run(|state| async move { state.connect(url).await }).await
}
pub fn cancel(&self) {
self.task.cancel();
}
}
#[derive(uniffi::Object)]
pub struct MoqSession {
inner: Option<moq_net::Session>,
closed: Task<Session>,
}
impl MoqSession {
pub(crate) fn new(session: moq_net::Session) -> Self {
Self {
inner: Some(session.clone()),
closed: Task::new(session),
}
}
}
impl Drop for MoqSession {
fn drop(&mut self) {
let _guard = crate::ffi::RUNTIME.enter();
self.inner.take();
}
}
#[uniffi::export]
impl MoqSession {
pub async fn closed(&self) -> Result<(), MoqError> {
self.closed
.run(|session| async move { session.closed().await.map_err(Into::into) })
.await
}
pub fn cancel(&self, code: u32) {
let _guard = crate::ffi::RUNTIME.enter();
if let Some(inner) = &self.inner {
inner.clone().close(moq_net::Error::Remote(code));
}
}
pub fn shutdown(&self) {
self.cancel(0);
}
}