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    path::{Path, PathBuf},
8    sync::Arc,
9};
10
11use crate::{
12    data::{
13        object::ObsObjectTrait,
14        output::{ObsOutputRef, ObsOutputTraitSealed},
15    },
16    forward_obs_object_impl, forward_obs_output_impl, impl_signal_manager, run_with_obs,
17    runtime::ObsRuntime,
18    unsafe_send::{Sendable, SmartPointerSendable},
19    utils::{ObsCalldataExt, ObsError, ObsString, OutputInfo},
20};
21
22#[derive(Debug, Clone)]
23/// A reference to an OBS output.
24///
25/// This struct is used specifically for the replay buffer to manage saving the buffer to a file
26/// and configuring special settings, which are specific to the replay buffer
27///
28/// The output is associated with video and audio encoders that convert
29/// raw media to the required format before sending/storing.
30pub struct ObsReplayBufferOutputRef {
31    /// Disconnect signals first
32    replay_signal_manager: Arc<ObsReplayOutputSignals>,
33
34    output: ObsOutputRef,
35}
36
37impl ObsOutputTraitSealed for ObsReplayBufferOutputRef {
38    fn new(mut output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
39        output.id = ObsString::new("replay_buffer");
40        let output = ObsOutputRef::new(output, runtime.clone())?;
41
42        let replay_signal_manager = ObsReplayOutputSignals::new(&output.as_ptr(), runtime)?;
43        Ok(Self {
44            replay_signal_manager: Arc::new(replay_signal_manager),
45            output,
46        })
47    }
48}
49
50forward_obs_object_impl!(ObsReplayBufferOutputRef, output, *mut libobs::obs_output);
51forward_obs_output_impl!(ObsReplayBufferOutputRef, output);
52
53impl_signal_manager!(|ptr: SmartPointerSendable<*mut libobs::obs_output>| {
54    unsafe {
55        // Safety: Again, it carries a reference of the drop guard so we must have a valid pointer
56        libobs::obs_output_get_signal_handler(ptr.get_ptr())
57    }
58}, ObsReplayOutputSignals for *mut libobs::obs_output, [
59    "saved": {}
60]);
61
62impl ObsReplayBufferOutputRef {
63    pub fn replay_signals(&self) -> &Arc<ObsReplayOutputSignals> {
64        &self.replay_signal_manager
65    }
66    /// Saves the current replay buffer content to disk.
67    ///
68    /// # Implementation Details
69    /// This method:
70    /// 1. Accesses the OBS procedure handler for the output
71    /// 2. Calls the "save" procedure to trigger saving the replay
72    /// 3. Calls the "get_last_replay" procedure to retrieve the saved file path
73    /// 4. Extracts the path string from the calldata and returns it
74    ///
75    /// # Returns
76    /// * `Ok(Box<Path>)` - The path to the saved replay file
77    /// * `Err(ObsError)` - Various errors that might occur during the saving process:
78    ///   - Failure to get procedure handler
79    ///   - Failure to call "save" procedure
80    ///   - Failure to call "get_last_replay" procedure
81    ///   - Failure to extract the path from calldata
82    pub fn save_buffer(&self) -> Result<Box<Path>, ObsError> {
83        log::trace!("Saving replay buffer...");
84        let output_ptr = self.as_ptr();
85
86        log::trace!("Getting procedure handler for replay buffer output...");
87        let proc_handler = run_with_obs!(self.runtime().clone(), (output_ptr), move || {
88            // Safety: At this point, output_ptr MUST be a valid pointer as we haven't released the output yet.
89            let ph = unsafe { libobs::obs_output_get_proc_handler(output_ptr.get_ptr()) };
90            if ph.is_null() {
91                return Err(ObsError::OutputSaveBufferFailure(
92                    "Failed to get proc handler.".to_string(),
93                ));
94            }
95            Ok(Sendable(ph))
96        })??;
97
98        log::trace!("Calling 'save' procedure on replay buffer output...");
99        // Safety: we know that the proc handler is valid because we got it from OBS earlier
100        unsafe { self.runtime().call_proc_handler(&proc_handler, "save")? };
101
102        log::trace!("Waiting for 'saved' signal from replay buffer output...");
103        self.replay_signals()
104            .on_saved()?
105            .blocking_recv()
106            .map_err(|_e| {
107                ObsError::OutputSaveBufferFailure(
108                    "Failed to receive saved replay buffer path.".to_string(),
109                )
110            })?;
111
112        log::trace!("Retrieving last replay path from replay buffer output...");
113        // Safety: We know that the proc handler is valid because we got it from OBS earlier
114        let mut calldata = unsafe {
115            self.runtime()
116                .call_proc_handler(&proc_handler, "get_last_replay")?
117        };
118
119        log::trace!("Extracting path from calldata...");
120        let path = calldata.get_string("path")?;
121        let path = PathBuf::from(path);
122
123        Ok(path.into_boxed_path())
124    }
125}