screencapturekit/stream/
delegate_trait.rs

1//! Delegate trait for stream lifecycle events
2//!
3//! Defines the interface for receiving stream state change notifications.
4//!
5//! Use [`SCStream::new_with_delegate`](crate::stream::SCStream::new_with_delegate)
6//! to create a stream with a delegate that receives error callbacks.
7
8use crate::error::SCError;
9
10/// Trait for handling stream lifecycle events
11///
12/// Implement this trait to receive notifications about stream state changes,
13/// errors, and video effects.
14///
15/// # Examples
16///
17/// ## Using a struct
18///
19/// ```
20/// use screencapturekit::stream::delegate_trait::SCStreamDelegateTrait;
21/// use screencapturekit::error::SCError;
22///
23/// struct MyDelegate;
24///
25/// impl SCStreamDelegateTrait for MyDelegate {
26///     fn stream_did_stop(&self, error: Option<String>) {
27///         if let Some(err) = error {
28///             eprintln!("Stream stopped with error: {}", err);
29///         } else {
30///             println!("Stream stopped normally");
31///         }
32///     }
33///
34///     fn did_stop_with_error(&self, error: SCError) {
35///         eprintln!("Stream error: {}", error);
36///     }
37/// }
38/// ```
39///
40/// ## Using closures
41///
42/// Use [`StreamCallbacks`] to create a delegate from closures:
43///
44/// ```rust,no_run
45/// use screencapturekit::prelude::*;
46/// use screencapturekit::stream::delegate_trait::StreamCallbacks;
47///
48/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
49/// # let content = SCShareableContent::get()?;
50/// # let display = &content.displays()[0];
51/// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
52/// # let config = SCStreamConfiguration::default();
53///
54/// let delegate = StreamCallbacks::new()
55///     .on_stop(|error| {
56///         if let Some(e) = error {
57///             eprintln!("Stream stopped with error: {}", e);
58///         }
59///     })
60///     .on_error(|error| eprintln!("Error: {}", error));
61///
62/// let stream = SCStream::new_with_delegate(&filter, &config, delegate);
63/// # Ok(())
64/// # }
65/// ```
66pub trait SCStreamDelegateTrait: Send {
67    /// Called when video effects start (macOS 14.0+)
68    ///
69    /// Notifies when the stream's overlay video effect (presenter overlay) has started.
70    fn output_video_effect_did_start_for_stream(&self) {}
71
72    /// Called when video effects stop (macOS 14.0+)
73    ///
74    /// Notifies when the stream's overlay video effect (presenter overlay) has stopped.
75    fn output_video_effect_did_stop_for_stream(&self) {}
76
77    /// Called when the stream becomes active (macOS 15.2+)
78    ///
79    /// Notifies the first time any window that was being shared in the stream
80    /// is re-opened after all the windows being shared were closed.
81    /// When all the windows being shared are closed, the client will receive
82    /// `stream_did_become_inactive`.
83    fn stream_did_become_active(&self) {}
84
85    /// Called when the stream becomes inactive (macOS 15.2+)
86    ///
87    /// Notifies when all the windows that are currently being shared are exited.
88    /// This callback occurs for all content filter types.
89    fn stream_did_become_inactive(&self) {}
90
91    /// Called when stream stops with an error
92    fn did_stop_with_error(&self, _error: SCError) {}
93
94    /// Called when stream stops
95    ///
96    /// # Parameters
97    ///
98    /// - `error`: Optional error message if the stream stopped due to an error
99    fn stream_did_stop(&self, _error: Option<String>) {}
100}
101
102/// A simple error handler wrapper for closures
103///
104/// Allows using a closure as a stream delegate that only handles errors.
105///
106/// # Examples
107///
108/// ```rust,no_run
109/// use screencapturekit::prelude::*;
110/// use screencapturekit::stream::delegate_trait::ErrorHandler;
111///
112/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
113/// # let content = SCShareableContent::get()?;
114/// # let display = &content.displays()[0];
115/// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
116/// # let config = SCStreamConfiguration::default();
117///
118/// let error_handler = ErrorHandler::new(|error| {
119///     eprintln!("Stream error: {}", error);
120/// });
121///
122/// let stream = SCStream::new_with_delegate(&filter, &config, error_handler);
123/// # Ok(())
124/// # }
125/// ```
126pub struct ErrorHandler<F>
127where
128    F: Fn(SCError) + Send + 'static,
129{
130    handler: F,
131}
132
133impl<F> ErrorHandler<F>
134where
135    F: Fn(SCError) + Send + 'static,
136{
137    /// Create a new error handler from a closure
138    pub fn new(handler: F) -> Self {
139        Self { handler }
140    }
141}
142
143impl<F> SCStreamDelegateTrait for ErrorHandler<F>
144where
145    F: Fn(SCError) + Send + 'static,
146{
147    fn did_stop_with_error(&self, error: SCError) {
148        (self.handler)(error);
149    }
150}
151
152/// Builder for closure-based stream delegate
153///
154/// Provides a convenient way to create a stream delegate using closures
155/// instead of implementing the [`SCStreamDelegateTrait`] trait.
156///
157/// # Examples
158///
159/// ```rust,no_run
160/// use screencapturekit::prelude::*;
161/// use screencapturekit::stream::delegate_trait::StreamCallbacks;
162///
163/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
164/// # let content = SCShareableContent::get()?;
165/// # let display = &content.displays()[0];
166/// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
167/// # let config = SCStreamConfiguration::default();
168///
169/// // Create delegate with multiple callbacks
170/// let delegate = StreamCallbacks::new()
171///     .on_stop(|error| {
172///         if let Some(e) = error {
173///             eprintln!("Stream stopped with error: {}", e);
174///         } else {
175///             println!("Stream stopped normally");
176///         }
177///     })
178///     .on_error(|error| eprintln!("Stream error: {}", error))
179///     .on_active(|| println!("Stream became active"))
180///     .on_inactive(|| println!("Stream became inactive"));
181///
182/// let stream = SCStream::new_with_delegate(&filter, &config, delegate);
183/// # Ok(())
184/// # }
185/// ```
186#[allow(clippy::struct_field_names)]
187pub struct StreamCallbacks {
188    on_stop: Option<Box<dyn Fn(Option<String>) + Send + 'static>>,
189    on_error: Option<Box<dyn Fn(SCError) + Send + 'static>>,
190    on_active: Option<Box<dyn Fn() + Send + 'static>>,
191    on_inactive: Option<Box<dyn Fn() + Send + 'static>>,
192    on_video_effect_start: Option<Box<dyn Fn() + Send + 'static>>,
193    on_video_effect_stop: Option<Box<dyn Fn() + Send + 'static>>,
194}
195
196impl StreamCallbacks {
197    /// Create a new empty callbacks builder
198    #[must_use]
199    pub fn new() -> Self {
200        Self {
201            on_stop: None,
202            on_error: None,
203            on_active: None,
204            on_inactive: None,
205            on_video_effect_start: None,
206            on_video_effect_stop: None,
207        }
208    }
209
210    /// Set the callback for when the stream stops
211    #[must_use]
212    pub fn on_stop<F>(mut self, f: F) -> Self
213    where
214        F: Fn(Option<String>) + Send + 'static,
215    {
216        self.on_stop = Some(Box::new(f));
217        self
218    }
219
220    /// Set the callback for when the stream encounters an error
221    #[must_use]
222    pub fn on_error<F>(mut self, f: F) -> Self
223    where
224        F: Fn(SCError) + Send + 'static,
225    {
226        self.on_error = Some(Box::new(f));
227        self
228    }
229
230    /// Set the callback for when the stream becomes active (macOS 15.2+)
231    #[must_use]
232    pub fn on_active<F>(mut self, f: F) -> Self
233    where
234        F: Fn() + Send + 'static,
235    {
236        self.on_active = Some(Box::new(f));
237        self
238    }
239
240    /// Set the callback for when the stream becomes inactive (macOS 15.2+)
241    #[must_use]
242    pub fn on_inactive<F>(mut self, f: F) -> Self
243    where
244        F: Fn() + Send + 'static,
245    {
246        self.on_inactive = Some(Box::new(f));
247        self
248    }
249
250    /// Set the callback for when video effects start (macOS 14.0+)
251    #[must_use]
252    pub fn on_video_effect_start<F>(mut self, f: F) -> Self
253    where
254        F: Fn() + Send + 'static,
255    {
256        self.on_video_effect_start = Some(Box::new(f));
257        self
258    }
259
260    /// Set the callback for when video effects stop (macOS 14.0+)
261    #[must_use]
262    pub fn on_video_effect_stop<F>(mut self, f: F) -> Self
263    where
264        F: Fn() + Send + 'static,
265    {
266        self.on_video_effect_stop = Some(Box::new(f));
267        self
268    }
269}
270
271impl Default for StreamCallbacks {
272    fn default() -> Self {
273        Self::new()
274    }
275}
276
277impl SCStreamDelegateTrait for StreamCallbacks {
278    fn stream_did_stop(&self, error: Option<String>) {
279        if let Some(ref f) = self.on_stop {
280            f(error);
281        }
282    }
283
284    fn did_stop_with_error(&self, error: SCError) {
285        if let Some(ref f) = self.on_error {
286            f(error);
287        }
288    }
289
290    fn stream_did_become_active(&self) {
291        if let Some(ref f) = self.on_active {
292            f();
293        }
294    }
295
296    fn stream_did_become_inactive(&self) {
297        if let Some(ref f) = self.on_inactive {
298            f();
299        }
300    }
301
302    fn output_video_effect_did_start_for_stream(&self) {
303        if let Some(ref f) = self.on_video_effect_start {
304            f();
305        }
306    }
307
308    fn output_video_effect_did_stop_for_stream(&self) {
309        if let Some(ref f) = self.on_video_effect_stop {
310            f();
311        }
312    }
313}