libobs_wrapper\data\output/
mod.rs1use 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 libobs::obs_output_release(output.0);
39});
40
41#[derive(Debug, Clone)]
42pub struct ObsOutputRef {
57 signal_manager: Arc<ObsOutputSignals>,
59
60 settings: Arc<RwLock<ImmutableObsData>>,
62
63 hotkey_data: Arc<RwLock<ImmutableObsData>>,
65
66 curr_video_encoder: Arc<RwLock<Option<Arc<ObsVideoEncoder>>>>,
68
69 audio_encoders: Arc<RwLock<HashMap<usize, Arc<ObsAudioEncoder>>>>,
71
72 id: ObsString,
74
75 name: ObsString,
77
78 runtime: ObsRuntime,
79
80 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 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 let new_settings_ptr = run_with_obs!(runtime, (output), move || {
141 let new_settings_ptr = unsafe {
142 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 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 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]);