libobs_wrapper\data\output/
mod.rs

1use libobs::obs_output;
2use std::collections::HashMap;
3use std::ptr;
4use std::sync::{Arc, RwLock};
5
6use crate::data::object::{inner_fn_update_settings, ObsObjectTrait, ObsObjectTraitPrivate};
7use crate::data::ImmutableObsData;
8use crate::data::ObsDataPointers;
9use crate::runtime::ObsRuntime;
10use crate::unsafe_send::{Sendable, SmartPointerSendable};
11use crate::utils::{ObsDropGuard, OutputInfo};
12use crate::{impl_obs_drop, impl_signal_manager, run_with_obs};
13
14use crate::{
15    encoders::{audio::ObsAudioEncoder, video::ObsVideoEncoder},
16    utils::{ObsError, ObsString},
17};
18
19use super::ObsData;
20
21pub(crate) mod macros;
22mod traits;
23pub use traits::*;
24
25mod replay_buffer;
26pub use replay_buffer::*;
27
28#[derive(Debug)]
29struct _ObsOutputDropGuard {
30    output: Sendable<*mut obs_output>,
31    runtime: ObsRuntime,
32}
33
34impl ObsDropGuard for _ObsOutputDropGuard {}
35
36impl_obs_drop!(_ObsOutputDropGuard, (output), move || unsafe {
37    // Safety: We are in the runtime and drop guards are always constructed from valid output pointers. Also this guard should be in an Arc, so no double free should happen.
38    libobs::obs_output_release(output.0);
39});
40
41#[derive(Debug, Clone)]
42/// A reference to an OBS output.
43///
44/// This struct represents an output in OBS, which is responsible for
45/// outputting encoded audio and video data to a destination such as:
46/// - A file (recording)
47/// - A streaming service (RTMP, etc.)
48/// - A replay buffer
49///
50/// The output is associated with video and audio encoders that convert
51/// raw media to the required format before sending/storing.
52///
53/// If encoders are attached to this struct, they are stored internally, so they will not get removed
54/// As of right now, there is no way to remove the encoders again, rather you'll need to replace them with another encoder or drop this struct and
55/// recreate a output.
56pub struct ObsOutputRef {
57    /// Disconnect signals first
58    signal_manager: Arc<ObsOutputSignals>,
59
60    /// Settings for the output
61    settings: Arc<RwLock<ImmutableObsData>>,
62
63    /// Hotkey configuration data for the output
64    hotkey_data: Arc<RwLock<ImmutableObsData>>,
65
66    /// Video encoders attached to this output
67    curr_video_encoder: Arc<RwLock<Option<Arc<ObsVideoEncoder>>>>,
68
69    /// Audio encoders attached to this output
70    audio_encoders: Arc<RwLock<HashMap<usize, Arc<ObsAudioEncoder>>>>,
71
72    /// The type identifier of this output
73    id: ObsString,
74
75    /// The unique name of this output
76    name: ObsString,
77
78    runtime: ObsRuntime,
79
80    /// Pointer to the underlying OBS output
81    output: SmartPointerSendable<*mut obs_output>,
82}
83
84impl ObsOutputTraitSealed for ObsOutputRef {
85    fn new(output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
86        let OutputInfo {
87            id,
88            name,
89            settings,
90            hotkey_data,
91        } = output;
92
93        let settings_ptr = settings.as_ref().map(|x| x.as_ptr());
94        let hotkey_data_ptr = hotkey_data.as_ref().map(|x| x.as_ptr());
95
96        let output = run_with_obs!(
97            runtime,
98            (id, name, settings_ptr, hotkey_data_ptr),
99            move || {
100                let settings_raw_ptr = match settings_ptr {
101                    Some(s) => s.get_ptr(),
102                    None => ptr::null_mut(),
103                };
104
105                let hotkey_data_raw_ptr = match hotkey_data_ptr {
106                    Some(h) => h.get_ptr(),
107                    None => ptr::null_mut(),
108                };
109
110                let id_ptr = id.as_ptr().0;
111                let name_ptr = name.as_ptr().0;
112
113                let output = unsafe {
114                    // Safety: All pointers are valid because we are keeping them in this scope and because we are using smart pointers for ObsData
115                    libobs::obs_output_create(
116                        id_ptr,
117                        name_ptr,
118                        settings_raw_ptr,
119                        hotkey_data_raw_ptr,
120                    )
121                };
122
123                if output.is_null() {
124                    return Err(ObsError::NullPointer(None));
125                }
126
127                Ok(Sendable(output))
128            }
129        )??;
130
131        let output = SmartPointerSendable::new(
132            output.0,
133            Arc::new(_ObsOutputDropGuard {
134                output: output.clone(),
135                runtime: runtime.clone(),
136            }),
137        );
138
139        // We are getting the settings from OBS because OBS will have updated it with default values.
140        let new_settings_ptr = run_with_obs!(runtime, (output), move || {
141            let new_settings_ptr = unsafe {
142                // Safety: At this point, the output can't be released because we are using a SmartPointer.
143                libobs::obs_output_get_settings(output.get_ptr())
144            };
145
146            if new_settings_ptr.is_null() {
147                return Err(ObsError::NullPointer(None));
148            }
149
150            Ok(Sendable(new_settings_ptr))
151        })??;
152
153        let settings = ImmutableObsData::from_raw_pointer(new_settings_ptr, runtime.clone());
154
155        // We are creating the hotkey data here because even it is null, OBS would create it nonetheless.
156        // https://github.com/obsproject/obs-studio/blob/d97e5ad820abcccf826faf897df4c7f511857cd4/libobs/obs.c#L2629
157        let hotkey_data = match hotkey_data {
158            Some(h) => h,
159            None => ImmutableObsData::new(&runtime)?,
160        };
161
162        let signal_manager = ObsOutputSignals::new(&output, runtime.clone())?;
163        Ok(Self {
164            settings: Arc::new(RwLock::new(settings)),
165            hotkey_data: Arc::new(RwLock::new(hotkey_data)),
166
167            curr_video_encoder: Arc::new(RwLock::new(None)),
168            audio_encoders: Arc::new(RwLock::new(HashMap::new())),
169
170            output: output.clone(),
171            id,
172            name,
173
174            runtime,
175            signal_manager: Arc::new(signal_manager),
176        })
177    }
178}
179
180impl ObsObjectTraitPrivate for ObsOutputRef {
181    fn __internal_replace_settings(&self, settings: ImmutableObsData) -> Result<(), ObsError> {
182        self.settings
183            .write()
184            .map_err(|_| ObsError::LockError("Failed to acquire write lock on settings".into()))
185            .map(|mut settings_lock| {
186                *settings_lock = settings;
187            })
188    }
189
190    fn __internal_replace_hotkey_data(
191        &self,
192        hotkey_data: ImmutableObsData,
193    ) -> Result<(), ObsError> {
194        self.hotkey_data
195            .write()
196            .map_err(|_| ObsError::LockError("Failed to acquire write lock on hotkey data".into()))
197            .map(|mut hotkey_lock| {
198                *hotkey_lock = hotkey_data;
199            })
200    }
201}
202
203impl ObsObjectTrait<*mut libobs::obs_output> for ObsOutputRef {
204    fn name(&self) -> ObsString {
205        self.name.clone()
206    }
207
208    fn id(&self) -> ObsString {
209        self.id.clone()
210    }
211
212    fn runtime(&self) -> &ObsRuntime {
213        &self.runtime
214    }
215
216    fn settings(&self) -> Result<ImmutableObsData, ObsError> {
217        let r = self
218            .settings
219            .read()
220            .map_err(|_| ObsError::LockError("Failed to acquire read lock on settings".into()))?;
221
222        Ok(r.clone())
223    }
224
225    fn hotkey_data(&self) -> Result<ImmutableObsData, ObsError> {
226        let r = self.hotkey_data.read().map_err(|_| {
227            ObsError::LockError("Failed to acquire read lock on hotkey data".into())
228        })?;
229
230        Ok(r.clone())
231    }
232
233    fn update_settings(&self, settings: ObsData) -> Result<(), ObsError> {
234        if self.is_active()? {
235            return Err(ObsError::OutputAlreadyActive);
236        }
237
238        inner_fn_update_settings!(self, libobs::obs_output_update, settings)
239    }
240
241    fn as_ptr(&self) -> SmartPointerSendable<*mut obs_output> {
242        self.output.clone()
243    }
244}
245
246impl ObsOutputTrait for ObsOutputRef {
247    fn signals(&self) -> &Arc<ObsOutputSignals> {
248        &self.signal_manager
249    }
250
251    fn video_encoder(&self) -> &Arc<RwLock<Option<Arc<ObsVideoEncoder>>>> {
252        &self.curr_video_encoder
253    }
254
255    fn audio_encoders(&self) -> &Arc<RwLock<HashMap<usize, Arc<ObsAudioEncoder>>>> {
256        &self.audio_encoders
257    }
258}
259
260impl_signal_manager!(|ptr: SmartPointerSendable<*mut libobs::obs_output>| unsafe {
261    // Safety: We are using a smart pointer, so it is fine
262    libobs::obs_output_get_signal_handler(ptr.get_ptr())
263}, ObsOutputSignals for *mut libobs::obs_output, [
264    "start": {},
265    "stop": {code: crate::enums::ObsOutputStopSignal},
266    "pause": {},
267    "unpause": {},
268    "starting": {},
269    "stopping": {},
270    "activate": {},
271    "deactivate": {},
272    "reconnect": {},
273    "reconnect_success": {}
274]);