use crate::output::{EncodedPacket, EncodedPacketType, OutputStream, OutputStreamError}; use anyhow::Result; use log::{error, info}; use std::{ os::raw::{c_char, c_void}, slice, time::Duration, }; use tokio::runtime::Runtime; pub struct RTCOutput { stream: OutputStream, runtime: Runtime, } /// cbindgen:prefix-with-name #[repr(C)] pub enum RTCOutputError { ConnectFailed, NetworkError, } impl From for RTCOutputError { fn from(ose: OutputStreamError) -> Self { match ose { OutputStreamError::ConnectFailed => RTCOutputError::ConnectFailed, OutputStreamError::NetworkError => RTCOutputError::NetworkError, OutputStreamError::WriteError(_) => RTCOutputError::NetworkError, } } } // You must call `char_rtc_output_free` on the returned value #[no_mangle] pub extern "C" fn char_rtc_output_new() -> *mut RTCOutput { (|| -> Result<*mut RTCOutput> { let runtime = tokio::runtime::Runtime::new()?; let stream = runtime.block_on(OutputStream::new())?; Ok(Box::into_raw(Box::new(RTCOutput { stream, runtime }))) })() .unwrap_or_else(|e| { error!("Unable to create whip output: {e:?}"); std::ptr::null_mut::() }) } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_free(output: *mut RTCOutput) { info!("Freeing whip output"); if !output.is_null() { drop(Box::from_raw(output)); } } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_bytes_sent(output: *const RTCOutput) -> u64 { let output = output.as_ref().unwrap(); output.stream.bytes_sent() } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_dropped_frames(output: *const RTCOutput) -> i32 { let output = output.as_ref().unwrap(); output.stream.dropped_frames() } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_congestion(output: *const RTCOutput) -> f64 { let output = output.as_ref().unwrap(); output.stream.congestion() } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_connect_time_ms(output: *const RTCOutput) -> i32 { let output = output.as_ref().unwrap(); output.stream.connect_time().as_millis() as i32 } #[no_mangle] pub unsafe extern "C" fn char_rtc_output_connect( output: *const RTCOutput, url: *const c_char, bearer_token: *const c_char, ) { let output = output.as_ref().unwrap(); let url = std::ffi::CStr::from_ptr(url).to_str().unwrap().to_owned(); let bearer_token = if !bearer_token.is_null() { Some( std::ffi::CStr::from_ptr(bearer_token) .to_str() .unwrap() .to_owned(), ) } else { None }; output.runtime.spawn(async move { output.stream.connect(&url, bearer_token.as_deref()).await; }); } // Once closed, you cannot call `char_rtc_output_connect` again #[no_mangle] pub unsafe extern "C" fn char_rtc_output_close(output: *const RTCOutput) { let output = output.as_ref().unwrap(); info!("Closing whip output"); output .runtime .block_on(async { info!("I'm on a thread!"); output.stream.close().await }) .unwrap_or_else(|e| error!("Failed closing whip output: {e:?}")) } /// Write an audio or video packet to the whip output #[no_mangle] pub unsafe extern "C" fn char_rtc_output_write( output: *const RTCOutput, data: *const u8, size: usize, duration: u64, is_audio: bool, ) -> bool { let output = output.as_ref().unwrap(); let slice: &[u8] = slice::from_raw_parts(data, size); let encoded_packet = EncodedPacket { data: slice.to_owned(), duration: Duration::from_micros(duration), typ: if is_audio { EncodedPacketType::Audio } else { EncodedPacketType::Video }, }; output .stream .write(encoded_packet) .map(|_| true) .unwrap_or_else(|e| { error!("Failed to write packets to whip output: {e:?}"); false }) } pub struct ErrorCallbackUserdata(*mut c_void); unsafe impl Send for ErrorCallbackUserdata {} unsafe impl Sync for ErrorCallbackUserdata {} impl ErrorCallbackUserdata { fn as_ptr(&self) -> *mut c_void { self.0 } } type RTCOutputErrorCallback = unsafe extern "C" fn(user_data: *mut c_void, error: RTCOutputError); #[no_mangle] pub unsafe extern "C" fn char_rtc_output_set_error_callback( output: *const RTCOutput, cb: RTCOutputErrorCallback, user_data: *mut c_void, ) { let output = output.as_ref().unwrap(); let user_data = ErrorCallbackUserdata(user_data); output .stream .set_error_callback(Box::new(move |error| cb(user_data.as_ptr(), error.into()))) }