libobs_wrapper\data\output/replay_buffer.rs
1//! Provides functionality for working with OBS replay buffers.
2//!
3//! This module extends the ObsOutputRef to provide replay buffer capabilities.
4//! A replay buffer is a special type of output that continuously records
5//! the last N seconds of content, allowing the user to save this buffer on demand. This must be configured. More documentation soon.
6use std::{
7 ffi::c_char,
8 mem::MaybeUninit,
9 path::{Path, PathBuf},
10};
11
12use libobs::{calldata_get_string, calldata_t, obs_output_get_proc_handler, proc_handler_call};
13
14use crate::{
15 run_with_obs,
16 utils::{ObsError, ObsString},
17};
18
19use super::ObsOutputRef;
20
21/// Defines functionality specific to replay buffer outputs.
22///
23/// This trait provides methods for working with replay buffers in OBS,
24/// which are special outputs that continuously record content and allow
25/// on-demand saving of recent footage.
26#[cfg_attr(not(feature = "blocking"), async_trait::async_trait)]
27pub trait ReplayBufferOutput {
28 /// Saves the current replay buffer content to disk.
29 ///
30 /// This method triggers the replay buffer to save its content to a file
31 /// and returns the path to the saved file.
32 ///
33 /// # Returns
34 /// * `Result<Box<Path>, ObsError>` - On success, returns the path to the saved
35 /// replay file. On failure, returns an error describing what went wrong.
36 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
37 async fn save_buffer(&self) -> Result<Box<Path>, ObsError>;
38}
39
40/// Implementation of the ReplayBufferOutput trait for ObsOutputRef.
41///
42/// This implementation allows any ObsOutputRef configured as a replay buffer
43/// to save its content to disk via a simple API call.
44#[cfg_attr(not(feature = "blocking"), async_trait::async_trait)]
45impl ReplayBufferOutput for ObsOutputRef {
46 /// Saves the current replay buffer content to disk.
47 ///
48 /// # Implementation Details
49 /// This method:
50 /// 1. Accesses the OBS procedure handler for the output
51 /// 2. Calls the "save" procedure to trigger saving the replay
52 /// 3. Calls the "get_last_replay" procedure to retrieve the saved file path
53 /// 4. Extracts the path string from the calldata and returns it
54 ///
55 /// # Returns
56 /// * `Ok(Box<Path>)` - The path to the saved replay file
57 /// * `Err(ObsError)` - Various errors that might occur during the saving process:
58 /// - Failure to get procedure handler
59 /// - Failure to call "save" procedure
60 /// - Failure to call "get_last_replay" procedure
61 /// - Failure to extract the path from calldata
62 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
63 async fn save_buffer(&self) -> Result<Box<Path>, ObsError> {
64 let output_ptr = self.output.clone();
65
66 let path = run_with_obs!(self.runtime, (output_ptr), move || {
67 let ph = unsafe { obs_output_get_proc_handler(output_ptr) };
68 if ph.is_null() {
69 return Err(ObsError::OutputSaveBufferFailure(
70 "Failed to get proc handler.".to_string(),
71 ));
72 }
73
74 let name = ObsString::new("save");
75 let call_success = unsafe {
76 let mut calldata = MaybeUninit::<calldata_t>::zeroed();
77 proc_handler_call(ph, name.as_ptr().0, calldata.as_mut_ptr())
78 };
79
80 if !call_success {
81 return Err(ObsError::OutputSaveBufferFailure(
82 "Failed to call proc handler.".to_string(),
83 ));
84 }
85
86 let func_get = ObsString::new("get_last_replay");
87 let last_replay = unsafe {
88 let mut calldata = MaybeUninit::<calldata_t>::zeroed();
89 let success = proc_handler_call(ph, func_get.as_ptr().0, calldata.as_mut_ptr());
90
91 if !success {
92 return Err(ObsError::OutputSaveBufferFailure(
93 "Failed to call get_last_replay.".to_string(),
94 ));
95 }
96
97 calldata.assume_init()
98 };
99
100 let path_get = ObsString::new("path");
101
102 let mut s = MaybeUninit::<*const c_char>::uninit();
103
104 let res =
105 unsafe { calldata_get_string(&last_replay, path_get.as_ptr().0, s.as_mut_ptr()) };
106 if !res {
107 return Err(ObsError::OutputSaveBufferFailure(
108 "Failed to get path from last replay.".to_string(),
109 ));
110 }
111
112 let s: *const c_char = unsafe { s.assume_init() };
113 let path = unsafe { std::ffi::CStr::from_ptr(s) }.to_str().unwrap();
114
115 Ok(PathBuf::from(path))
116 })
117 .await??;
118
119 Ok(path.into_boxed_path())
120 }
121}