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}