use std::{
collections::HashMap,
ffi::CStr,
fmt::Debug,
sync::{Arc, RwLock},
};
use crate::{
data::object::ObsObjectTrait,
encoders::{audio::ObsAudioEncoder, video::ObsVideoEncoder},
enums::ObsOutputStopSignal,
macros::trait_with_optional_send_sync,
run_with_obs,
runtime::ObsRuntime,
utils::{AudioEncoderInfo, ObsError, OutputInfo, VideoEncoderInfo},
};
use super::ObsOutputSignals;
trait_with_optional_send_sync! {
pub(crate) trait ObsOutputTraitSealed: Debug {
fn new(output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError>
where
Self: Sized;
}
}
#[allow(private_bounds)]
pub trait ObsOutputTrait: ObsOutputTraitSealed + ObsObjectTrait<*mut libobs::obs_output_t> {
fn signals(&self) -> &Arc<ObsOutputSignals>;
fn video_encoder(&self) -> &Arc<RwLock<Option<Arc<ObsVideoEncoder>>>>;
fn audio_encoders(&self) -> &Arc<RwLock<HashMap<usize, Arc<ObsAudioEncoder>>>>;
fn get_current_video_encoder(&self) -> Result<Option<Arc<ObsVideoEncoder>>, ObsError> {
let curr = self
.video_encoder()
.read()
.map_err(|e| ObsError::LockError(e.to_string()))?;
Ok(curr.clone())
}
fn create_and_set_video_encoder(
&mut self,
info: VideoEncoderInfo,
) -> Result<Arc<ObsVideoEncoder>, ObsError> {
if self.is_active()? {
return Err(ObsError::OutputAlreadyActive);
}
let video_enc = ObsVideoEncoder::new_from_info(info, self.runtime().clone())?;
self.set_video_encoder(video_enc.clone())?;
Ok(video_enc)
}
fn set_video_encoder(&mut self, encoder: Arc<ObsVideoEncoder>) -> Result<(), ObsError> {
if self.is_active()? {
return Err(ObsError::OutputAlreadyActive);
}
let output_ptr = self.as_ptr();
let encoder_ptr = encoder.as_ptr();
let runtime = self.runtime().clone();
run_with_obs!(runtime, (output_ptr, encoder_ptr), move || {
unsafe {
libobs::obs_output_set_video_encoder(output_ptr.get_ptr(), encoder_ptr.get_ptr());
}
})?;
self.video_encoder()
.write()
.map_err(|e| ObsError::LockError(e.to_string()))?
.replace(encoder);
Ok(())
}
fn create_and_set_audio_encoder(
&mut self,
info: AudioEncoderInfo,
mixer_idx: usize,
) -> Result<Arc<ObsAudioEncoder>, ObsError> {
if self.is_active()? {
return Err(ObsError::OutputAlreadyActive);
}
let audio_enc = ObsAudioEncoder::new_from_info(info, mixer_idx, self.runtime().clone())?;
self.set_audio_encoder(audio_enc.clone(), mixer_idx)?;
Ok(audio_enc)
}
fn set_audio_encoder(
&mut self,
encoder: Arc<ObsAudioEncoder>,
mixer_idx: usize,
) -> Result<(), ObsError> {
if self.is_active()? {
return Err(ObsError::OutputAlreadyActive);
}
let encoder_ptr = encoder.as_ptr();
let output_ptr = self.as_ptr();
let runtime = self.runtime().clone();
run_with_obs!(runtime, (output_ptr, encoder_ptr), move || {
unsafe {
libobs::obs_output_set_audio_encoder(
output_ptr.get_ptr(),
encoder_ptr.get_ptr(),
mixer_idx,
);
}
})?;
self.audio_encoders()
.write()
.map_err(|e| ObsError::LockError(e.to_string()))?
.insert(mixer_idx, encoder);
Ok(())
}
fn start(&self) -> Result<(), ObsError> {
if self.is_active()? {
return Err(ObsError::OutputAlreadyActive);
}
let vid_encoder_ptr = self
.video_encoder()
.read()
.map_err(|e| ObsError::LockError(e.to_string()))?
.as_ref()
.map(|enc| enc.as_ptr());
let audio_encoder_pointers = self
.audio_encoders()
.read()
.map_err(|e| ObsError::LockError(e.to_string()))?
.values()
.map(|enc| enc.as_ptr())
.collect::<Vec<_>>();
let output_ptr = self.as_ptr();
let runtime = self.runtime().clone();
let res = run_with_obs!(
runtime,
(output_ptr, vid_encoder_ptr, audio_encoder_pointers),
move || {
if let Some(vid_encoder_ptr) = vid_encoder_ptr {
unsafe {
libobs::obs_encoder_set_video(
vid_encoder_ptr.get_ptr(),
libobs::obs_get_video(),
);
}
}
for audio_encoder_ptr in audio_encoder_pointers {
unsafe {
libobs::obs_encoder_set_audio(
audio_encoder_ptr.get_ptr(),
libobs::obs_get_audio(),
);
}
}
unsafe {
libobs::obs_output_start(output_ptr.get_ptr())
}
}
)?;
if res {
return Ok(());
}
let runtime = self.runtime().clone();
let err = run_with_obs!(runtime, (output_ptr), move || {
let err = unsafe {
libobs::obs_output_get_last_error(output_ptr.get_ptr())
};
if err.is_null() {
return "Unknown error".to_string();
}
let err = unsafe { CStr::from_ptr(err) };
let err = err.to_string_lossy().to_string();
err
})?;
Err(ObsError::OutputStartFailure(Some(err)))
}
fn set_paused(&self, should_pause: bool) -> Result<(), ObsError> {
if !self.is_active()? {
return Err(ObsError::OutputPauseFailure(Some(
"Output is not active.".to_string(),
)));
}
let output_ptr = self.as_ptr();
let runtime = self.runtime().clone();
let mut rx = if should_pause {
self.signals().on_pause()?
} else {
self.signals().on_unpause()?
};
let res = run_with_obs!(runtime, (output_ptr), move || {
unsafe {
libobs::obs_output_pause(output_ptr.get_ptr(), should_pause)
}
})?;
if res {
rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
Ok(())
} else {
let runtime = self.runtime().clone();
let err = run_with_obs!(runtime, (output_ptr), move || {
let err = unsafe {
libobs::obs_output_get_last_error(output_ptr.get_ptr())
};
if err.is_null() {
return None;
}
let err = unsafe { CStr::from_ptr(err) };
let err = err.to_string_lossy().to_string();
Some(err)
})?;
Err(ObsError::OutputPauseFailure(err))
}
}
fn pause(&self) -> Result<(), ObsError> {
self.set_paused(true)
}
fn unpause(&self) -> Result<(), ObsError> {
self.set_paused(false)
}
fn stop(&mut self) -> Result<(), ObsError> {
let output_ptr = self.as_ptr();
let runtime = self.runtime().clone();
let output_active = run_with_obs!(runtime, (output_ptr), move || {
unsafe {
libobs::obs_output_active(output_ptr.get_ptr())
}
})?;
if !output_active {
return Err(ObsError::OutputStopFailure(Some(
"Output is not active.".to_string(),
)));
}
let mut rx = self.signals().on_stop()?;
let mut rx_deactivate = self.signals().on_deactivate()?;
let runtime = self.runtime().clone();
run_with_obs!(runtime, (output_ptr), move || {
unsafe {
libobs::obs_output_stop(output_ptr.get_ptr())
}
})?;
let signal = rx.blocking_recv().map_err(|_| ObsError::NoSenderError)?;
log::trace!("Received stop signal: {:?}", signal);
if signal != ObsOutputStopSignal::Success {
return Err(ObsError::OutputStopFailure(Some(signal.to_string())));
}
rx_deactivate
.blocking_recv()
.map_err(|_| ObsError::NoSenderError)?;
Ok(())
}
fn is_active(&self) -> Result<bool, ObsError> {
let output_ptr = self.as_ptr();
let runtime = self.runtime().clone();
let output_active = run_with_obs!(runtime, (output_ptr), move || {
unsafe {
libobs::obs_output_active(output_ptr.get_ptr())
}
})?;
Ok(output_active)
}
}