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}