screencapturekit/stream/sc_stream.rs
1//! Swift FFI based `SCStream` implementation
2//!
3//! This is the primary (and only) implementation in v1.0+.
4//! All `ScreenCaptureKit` operations use direct Swift FFI bindings.
5
6use std::collections::HashMap;
7use std::ffi::{c_void, CStr};
8use std::fmt;
9use std::sync::Mutex;
10
11use crate::error::SCError;
12use crate::stream::delegate_trait::SCStreamDelegateTrait;
13use crate::utils::completion::UnitCompletion;
14use crate::{
15 dispatch_queue::DispatchQueue,
16 ffi,
17 stream::{
18 configuration::SCStreamConfiguration, content_filter::SCContentFilter,
19 output_trait::SCStreamOutputTrait, output_type::SCStreamOutputType,
20 },
21};
22
23// Handler entry with reference count
24struct HandlerEntry {
25 handler: Box<dyn SCStreamOutputTrait>,
26 ref_count: usize,
27}
28
29// Global registry for output handlers with reference counting
30static HANDLER_REGISTRY: Mutex<Option<HashMap<usize, HandlerEntry>>> = Mutex::new(None);
31static NEXT_HANDLER_ID: Mutex<usize> = Mutex::new(1);
32
33// Global registry for stream delegates (keyed by stream pointer) with reference counting
34struct DelegateEntry {
35 delegate: Box<dyn SCStreamDelegateTrait>,
36 ref_count: usize,
37}
38static DELEGATE_REGISTRY: Mutex<Option<HashMap<usize, DelegateEntry>>> = Mutex::new(None);
39
40/// Increment a handler's ref count
41#[allow(clippy::significant_drop_tightening)]
42fn increment_handler_ref_count(id: usize) {
43 let mut registry = HANDLER_REGISTRY.lock().unwrap();
44 let Some(handlers) = registry.as_mut() else {
45 return;
46 };
47 if let Some(entry) = handlers.get_mut(&id) {
48 entry.ref_count += 1;
49 }
50}
51
52/// Decrement a handler's ref count, returning true if entry was removed (`ref_count` reached 0)
53#[allow(clippy::significant_drop_tightening)]
54fn decrement_handler_ref_count(id: usize) -> bool {
55 let mut registry = HANDLER_REGISTRY.lock().unwrap();
56 let Some(handlers) = registry.as_mut() else {
57 return false;
58 };
59 let Some(entry) = handlers.get_mut(&id) else {
60 return false;
61 };
62
63 entry.ref_count = entry.ref_count.saturating_sub(1);
64 if entry.ref_count == 0 {
65 handlers.remove(&id);
66 true
67 } else {
68 false
69 }
70}
71
72/// Increment a delegate's ref count
73#[allow(clippy::significant_drop_tightening)]
74fn increment_delegate_ref_count(stream_key: usize) {
75 let Ok(mut registry) = DELEGATE_REGISTRY.lock() else {
76 return;
77 };
78 let Some(delegates) = registry.as_mut() else {
79 return;
80 };
81 if let Some(entry) = delegates.get_mut(&stream_key) {
82 entry.ref_count += 1;
83 }
84}
85
86/// Decrement a delegate's ref count, returning true if entry was removed (`ref_count` reached 0)
87#[allow(clippy::significant_drop_tightening)]
88fn decrement_delegate_ref_count(stream_key: usize) -> bool {
89 let Ok(mut registry) = DELEGATE_REGISTRY.lock() else {
90 return false;
91 };
92 let Some(delegates) = registry.as_mut() else {
93 return false;
94 };
95 let Some(entry) = delegates.get_mut(&stream_key) else {
96 return false;
97 };
98
99 entry.ref_count = entry.ref_count.saturating_sub(1);
100 if entry.ref_count == 0 {
101 delegates.remove(&stream_key);
102 true
103 } else {
104 false
105 }
106}
107
108// C callback for stream errors that dispatches to registered delegate
109extern "C" fn delegate_error_callback(stream: *const c_void, error_code: i32, msg: *const i8) {
110 let message = if msg.is_null() {
111 "Unknown error".to_string()
112 } else {
113 unsafe { CStr::from_ptr(msg) }
114 .to_str()
115 .unwrap_or("Unknown error")
116 .to_string()
117 };
118
119 let error = if error_code != 0 {
120 crate::error::SCStreamErrorCode::from_raw(error_code).map_or_else(
121 || SCError::StreamError(format!("{message} (code: {error_code})")),
122 |code| SCError::SCStreamError {
123 code,
124 message: Some(message.clone()),
125 },
126 )
127 } else {
128 SCError::StreamError(message.clone())
129 };
130
131 // Look up delegate in registry and call it
132 let stream_key = stream as usize;
133 if let Ok(registry) = DELEGATE_REGISTRY.lock() {
134 if let Some(ref delegates) = *registry {
135 if let Some(entry) = delegates.get(&stream_key) {
136 entry.delegate.did_stop_with_error(error);
137 entry.delegate.stream_did_stop(Some(message));
138 return;
139 }
140 }
141 }
142
143 // Fallback to logging if no delegate registered
144 eprintln!("SCStream error: {error}");
145}
146
147// C callback that retrieves handler from registry
148extern "C" fn sample_handler(
149 _stream: *const c_void,
150 sample_buffer: *const c_void,
151 output_type: i32,
152) {
153 // Mutex poisoning is unrecoverable in C callback context; unwrap is appropriate
154 let registry = HANDLER_REGISTRY.lock().unwrap();
155 if let Some(handlers) = registry.as_ref() {
156 if handlers.is_empty() {
157 // No handlers registered - release the buffer that Swift passed us
158 unsafe { crate::cm::ffi::cm_sample_buffer_release(sample_buffer.cast_mut()) };
159 return;
160 }
161
162 let output_type_enum = match output_type {
163 0 => SCStreamOutputType::Screen,
164 1 => SCStreamOutputType::Audio,
165 2 => SCStreamOutputType::Microphone,
166 _ => {
167 eprintln!("Unknown output type: {output_type}");
168 // Unknown type - release the buffer
169 unsafe { crate::cm::ffi::cm_sample_buffer_release(sample_buffer.cast_mut()) };
170 return;
171 }
172 };
173
174 let handler_count = handlers.len();
175
176 // Call all registered handlers
177 for (idx, (_id, entry)) in handlers.iter().enumerate() {
178 // Convert raw pointer to CMSampleBuffer
179 let buffer = unsafe { crate::cm::CMSampleBuffer::from_ptr(sample_buffer.cast_mut()) };
180
181 // For all handlers except the last, we need to retain the buffer
182 if idx < handler_count - 1 {
183 // Retain the buffer so it's not released when this handler's buffer is dropped
184 unsafe { crate::cm::ffi::cm_sample_buffer_retain(sample_buffer.cast_mut()) };
185 }
186 // The last handler will release the original retained reference from Swift
187
188 entry
189 .handler
190 .did_output_sample_buffer(buffer, output_type_enum);
191 }
192 } else {
193 // No registry - release the buffer
194 unsafe { crate::cm::ffi::cm_sample_buffer_release(sample_buffer.cast_mut()) };
195 }
196}
197
198/// `SCStream` is a lightweight wrapper around the Swift `SCStream` instance.
199/// It provides direct FFI access to `ScreenCaptureKit` functionality.
200///
201/// This is the primary and only implementation of `SCStream` in v1.0+.
202/// All `ScreenCaptureKit` operations go through Swift FFI bindings.
203///
204/// # Examples
205///
206/// ```no_run
207/// use screencapturekit::prelude::*;
208///
209/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
210/// // Get shareable content
211/// let content = SCShareableContent::get()?;
212/// let display = &content.displays()[0];
213///
214/// // Create filter and configuration
215/// let filter = SCContentFilter::create()
216/// .with_display(display)
217/// .with_excluding_windows(&[])
218/// .build();
219/// let config = SCStreamConfiguration::new()
220/// .with_width(1920)
221/// .with_height(1080);
222///
223/// // Create and start stream
224/// let mut stream = SCStream::new(&filter, &config);
225/// stream.start_capture()?;
226///
227/// // ... capture frames ...
228///
229/// stream.stop_capture()?;
230/// # Ok(())
231/// # }
232/// ```
233pub struct SCStream {
234 ptr: *const c_void,
235 /// Handler IDs registered by this stream instance, keyed by output type
236 handler_ids: Vec<(usize, SCStreamOutputType)>,
237}
238
239unsafe impl Send for SCStream {}
240unsafe impl Sync for SCStream {}
241
242impl SCStream {
243 /// Create a new stream with a content filter and configuration
244 ///
245 /// # Examples
246 ///
247 /// ```no_run
248 /// use screencapturekit::prelude::*;
249 ///
250 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
251 /// let content = SCShareableContent::get()?;
252 /// let display = &content.displays()[0];
253 /// let filter = SCContentFilter::create()
254 /// .with_display(display)
255 /// .with_excluding_windows(&[])
256 /// .build();
257 /// let config = SCStreamConfiguration::new()
258 /// .with_width(1920)
259 /// .with_height(1080);
260 ///
261 /// let stream = SCStream::new(&filter, &config);
262 /// # Ok(())
263 /// # }
264 /// ```
265 pub fn new(filter: &SCContentFilter, configuration: &SCStreamConfiguration) -> Self {
266 extern "C" fn error_callback(_stream: *const c_void, error_code: i32, msg: *const i8) {
267 let message = if msg.is_null() {
268 "Unknown error"
269 } else {
270 unsafe { CStr::from_ptr(msg) }
271 .to_str()
272 .unwrap_or("Unknown error")
273 };
274
275 if error_code != 0 {
276 if let Some(code) = crate::error::SCStreamErrorCode::from_raw(error_code) {
277 eprintln!("SCStream error ({code}): {message}");
278 } else {
279 eprintln!("SCStream error (code {error_code}): {message}");
280 }
281 } else {
282 eprintln!("SCStream error: {message}");
283 }
284 }
285 let ptr = unsafe {
286 ffi::sc_stream_create(filter.as_ptr(), configuration.as_ptr(), error_callback)
287 };
288 // Note: The Swift bridge should never return null for a valid filter/config,
289 // but we handle it gracefully by creating an empty stream that will fail on use.
290 // This maintains API compatibility while being more defensive.
291 Self {
292 ptr,
293 handler_ids: Vec::new(),
294 }
295 }
296
297 /// Create a new stream with a content filter, configuration, and delegate
298 ///
299 /// The delegate receives callbacks for stream lifecycle events:
300 /// - `did_stop_with_error` - Called when the stream stops due to an error
301 /// - `stream_did_stop` - Called when the stream stops (with optional error message)
302 ///
303 /// # Panics
304 ///
305 /// Panics if the internal delegate registry mutex is poisoned.
306 ///
307 /// # Examples
308 ///
309 /// ```no_run
310 /// use screencapturekit::prelude::*;
311 /// use screencapturekit::stream::delegate_trait::StreamCallbacks;
312 ///
313 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
314 /// let content = SCShareableContent::get()?;
315 /// let display = &content.displays()[0];
316 /// let filter = SCContentFilter::create()
317 /// .with_display(display)
318 /// .with_excluding_windows(&[])
319 /// .build();
320 /// let config = SCStreamConfiguration::new()
321 /// .with_width(1920)
322 /// .with_height(1080);
323 ///
324 /// let delegate = StreamCallbacks::new()
325 /// .on_error(|e| eprintln!("Stream error: {}", e))
326 /// .on_stop(|err| {
327 /// if let Some(msg) = err {
328 /// eprintln!("Stream stopped with error: {}", msg);
329 /// }
330 /// });
331 ///
332 /// let stream = SCStream::new_with_delegate(&filter, &config, delegate);
333 /// stream.start_capture()?;
334 /// # Ok(())
335 /// # }
336 /// ```
337 pub fn new_with_delegate(
338 filter: &SCContentFilter,
339 configuration: &SCStreamConfiguration,
340 delegate: impl SCStreamDelegateTrait + 'static,
341 ) -> Self {
342 let ptr = unsafe {
343 ffi::sc_stream_create(
344 filter.as_ptr(),
345 configuration.as_ptr(),
346 delegate_error_callback,
347 )
348 };
349
350 // Store delegate in registry keyed by stream pointer
351 if !ptr.is_null() {
352 let stream_key = ptr as usize;
353 let mut registry = DELEGATE_REGISTRY.lock().unwrap();
354 if registry.is_none() {
355 *registry = Some(HashMap::new());
356 }
357 registry.as_mut().unwrap().insert(
358 stream_key,
359 DelegateEntry {
360 delegate: Box::new(delegate),
361 ref_count: 1,
362 },
363 );
364 }
365
366 Self {
367 ptr,
368 handler_ids: Vec::new(),
369 }
370 }
371
372 /// Add an output handler to receive captured frames
373 ///
374 /// # Arguments
375 ///
376 /// * `handler` - The handler to receive callbacks. Can be:
377 /// - A struct implementing [`SCStreamOutputTrait`]
378 /// - A closure `|CMSampleBuffer, SCStreamOutputType| { ... }`
379 /// * `of_type` - The type of output to receive (Screen, Audio, or Microphone)
380 ///
381 /// # Returns
382 ///
383 /// Returns `Some(handler_id)` on success, `None` on failure.
384 /// The handler ID can be used with [`remove_output_handler`](Self::remove_output_handler).
385 ///
386 /// # Examples
387 ///
388 /// Using a struct:
389 /// ```rust,no_run
390 /// use screencapturekit::prelude::*;
391 ///
392 /// struct MyHandler;
393 /// impl SCStreamOutputTrait for MyHandler {
394 /// fn did_output_sample_buffer(&self, _sample: CMSampleBuffer, _of_type: SCStreamOutputType) {
395 /// println!("Got frame!");
396 /// }
397 /// }
398 ///
399 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
400 /// # let content = SCShareableContent::get()?;
401 /// # let display = &content.displays()[0];
402 /// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
403 /// # let config = SCStreamConfiguration::default();
404 /// let mut stream = SCStream::new(&filter, &config);
405 /// stream.add_output_handler(MyHandler, SCStreamOutputType::Screen);
406 /// # Ok(())
407 /// # }
408 /// ```
409 ///
410 /// Using a closure:
411 /// ```rust,no_run
412 /// use screencapturekit::prelude::*;
413 ///
414 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
415 /// # let content = SCShareableContent::get()?;
416 /// # let display = &content.displays()[0];
417 /// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
418 /// # let config = SCStreamConfiguration::default();
419 /// let mut stream = SCStream::new(&filter, &config);
420 /// stream.add_output_handler(
421 /// |_sample, _type| println!("Got frame!"),
422 /// SCStreamOutputType::Screen
423 /// );
424 /// # Ok(())
425 /// # }
426 /// ```
427 pub fn add_output_handler(
428 &mut self,
429 handler: impl SCStreamOutputTrait + 'static,
430 of_type: SCStreamOutputType,
431 ) -> Option<usize> {
432 self.add_output_handler_with_queue(handler, of_type, None)
433 }
434
435 /// Add an output handler with a custom dispatch queue
436 ///
437 /// This allows controlling which thread/queue the handler is called on.
438 ///
439 /// # Arguments
440 ///
441 /// * `handler` - The handler to receive callbacks
442 /// * `of_type` - The type of output to receive
443 /// * `queue` - Optional custom dispatch queue for callbacks
444 ///
445 /// # Panics
446 ///
447 /// Panics if the internal handler registry mutex is poisoned.
448 ///
449 /// # Examples
450 ///
451 /// ```rust,no_run
452 /// use screencapturekit::prelude::*;
453 /// use screencapturekit::dispatch_queue::{DispatchQueue, DispatchQoS};
454 ///
455 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
456 /// # let content = SCShareableContent::get()?;
457 /// # let display = &content.displays()[0];
458 /// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
459 /// # let config = SCStreamConfiguration::default();
460 /// let mut stream = SCStream::new(&filter, &config);
461 /// let queue = DispatchQueue::new("com.myapp.capture", DispatchQoS::UserInteractive);
462 ///
463 /// stream.add_output_handler_with_queue(
464 /// |_sample, _type| println!("Got frame on custom queue!"),
465 /// SCStreamOutputType::Screen,
466 /// Some(&queue)
467 /// );
468 /// # Ok(())
469 /// # }
470 /// ```
471 pub fn add_output_handler_with_queue(
472 &mut self,
473 handler: impl SCStreamOutputTrait + 'static,
474 of_type: SCStreamOutputType,
475 queue: Option<&DispatchQueue>,
476 ) -> Option<usize> {
477 // Get next handler ID
478 let handler_id = {
479 // Mutex poisoning is unrecoverable; unwrap is appropriate
480 let mut id_lock = NEXT_HANDLER_ID.lock().unwrap();
481 let id = *id_lock;
482 *id_lock += 1;
483 id
484 };
485
486 // Store handler in registry
487 {
488 // Mutex poisoning is unrecoverable; unwrap is appropriate
489 let mut registry = HANDLER_REGISTRY.lock().unwrap();
490 if registry.is_none() {
491 *registry = Some(HashMap::new());
492 }
493 // We just ensured registry is Some above
494 registry.as_mut().unwrap().insert(
495 handler_id,
496 HandlerEntry {
497 handler: Box::new(handler),
498 ref_count: 1,
499 },
500 );
501 }
502
503 // Convert output type to int for Swift
504 let output_type_int = match of_type {
505 SCStreamOutputType::Screen => 0,
506 SCStreamOutputType::Audio => 1,
507 SCStreamOutputType::Microphone => 2,
508 };
509
510 let ok = if let Some(q) = queue {
511 unsafe {
512 ffi::sc_stream_add_stream_output_with_queue(
513 self.ptr,
514 output_type_int,
515 sample_handler,
516 q.as_ptr(),
517 )
518 }
519 } else {
520 unsafe { ffi::sc_stream_add_stream_output(self.ptr, output_type_int, sample_handler) }
521 };
522
523 if ok {
524 self.handler_ids.push((handler_id, of_type));
525 Some(handler_id)
526 } else {
527 // Remove from registry since Swift rejected it
528 HANDLER_REGISTRY
529 .lock()
530 .unwrap()
531 .as_mut()
532 .map(|handlers| handlers.remove(&handler_id));
533 None
534 }
535 }
536
537 /// Remove an output handler
538 ///
539 /// # Arguments
540 ///
541 /// * `id` - The handler ID returned from [`add_output_handler`](Self::add_output_handler)
542 /// * `of_type` - The type of output the handler was registered for
543 ///
544 /// # Panics
545 ///
546 /// Panics if the internal handler registry mutex is poisoned.
547 ///
548 /// # Returns
549 ///
550 /// Returns `true` if the handler was found and removed, `false` otherwise.
551 pub fn remove_output_handler(&mut self, id: usize, of_type: SCStreamOutputType) -> bool {
552 // Remove from our tracking
553 let Some(pos) = self.handler_ids.iter().position(|(hid, _)| *hid == id) else {
554 return false;
555 };
556 self.handler_ids.remove(pos);
557
558 // Decrement ref count in global registry, remove from Swift if this was the last reference
559 if decrement_handler_ref_count(id) {
560 let output_type_int = match of_type {
561 SCStreamOutputType::Screen => 0,
562 SCStreamOutputType::Audio => 1,
563 SCStreamOutputType::Microphone => 2,
564 };
565 unsafe { ffi::sc_stream_remove_stream_output(self.ptr, output_type_int) }
566 } else {
567 true
568 }
569 }
570
571 /// Start capturing screen content
572 ///
573 /// This method blocks until the capture operation completes or fails.
574 ///
575 /// # Errors
576 ///
577 /// Returns `SCError::CaptureStartFailed` if the capture fails to start.
578 pub fn start_capture(&self) -> Result<(), SCError> {
579 let (completion, context) = UnitCompletion::new();
580 unsafe { ffi::sc_stream_start_capture(self.ptr, context, UnitCompletion::callback) };
581 completion.wait().map_err(SCError::CaptureStartFailed)
582 }
583
584 /// Stop capturing screen content
585 ///
586 /// This method blocks until the capture operation completes or fails.
587 ///
588 /// # Errors
589 ///
590 /// Returns `SCError::CaptureStopFailed` if the capture fails to stop.
591 pub fn stop_capture(&self) -> Result<(), SCError> {
592 let (completion, context) = UnitCompletion::new();
593 unsafe { ffi::sc_stream_stop_capture(self.ptr, context, UnitCompletion::callback) };
594 completion.wait().map_err(SCError::CaptureStopFailed)
595 }
596
597 /// Update the stream configuration
598 ///
599 /// This method blocks until the configuration update completes or fails.
600 ///
601 /// # Errors
602 ///
603 /// Returns `SCError::StreamError` if the configuration update fails.
604 pub fn update_configuration(
605 &self,
606 configuration: &SCStreamConfiguration,
607 ) -> Result<(), SCError> {
608 let (completion, context) = UnitCompletion::new();
609 unsafe {
610 ffi::sc_stream_update_configuration(
611 self.ptr,
612 configuration.as_ptr(),
613 context,
614 UnitCompletion::callback,
615 );
616 }
617 completion.wait().map_err(SCError::StreamError)
618 }
619
620 /// Update the content filter
621 ///
622 /// This method blocks until the filter update completes or fails.
623 ///
624 /// # Errors
625 ///
626 /// Returns `SCError::StreamError` if the filter update fails.
627 pub fn update_content_filter(&self, filter: &SCContentFilter) -> Result<(), SCError> {
628 let (completion, context) = UnitCompletion::new();
629 unsafe {
630 ffi::sc_stream_update_content_filter(
631 self.ptr,
632 filter.as_ptr(),
633 context,
634 UnitCompletion::callback,
635 );
636 }
637 completion.wait().map_err(SCError::StreamError)
638 }
639
640 /// Get the synchronization clock for this stream (macOS 13.0+)
641 ///
642 /// Returns the `CMClock` used to synchronize the stream's output.
643 /// This is useful for coordinating multiple streams or synchronizing
644 /// with other media.
645 ///
646 /// Returns `None` if the clock is not available (e.g., stream not started
647 /// or macOS version too old).
648 #[cfg(feature = "macos_13_0")]
649 pub fn synchronization_clock(&self) -> Option<crate::cm::CMClock> {
650 let ptr = unsafe { ffi::sc_stream_get_synchronization_clock(self.ptr) };
651 if ptr.is_null() {
652 None
653 } else {
654 Some(crate::cm::CMClock::from_ptr(ptr))
655 }
656 }
657
658 /// Add a recording output to the stream (macOS 15.0+)
659 ///
660 /// Starts recording if the stream is already capturing, otherwise recording
661 /// will start when capture begins. The recording is written to the file URL
662 /// specified in the `SCRecordingOutputConfiguration`.
663 ///
664 /// # Errors
665 ///
666 /// Returns `SCError::StreamError` if adding the recording output fails.
667 #[cfg(feature = "macos_15_0")]
668 pub fn add_recording_output(
669 &self,
670 recording_output: &crate::recording_output::SCRecordingOutput,
671 ) -> Result<(), SCError> {
672 let (completion, context) = UnitCompletion::new();
673 unsafe {
674 ffi::sc_stream_add_recording_output(
675 self.ptr,
676 recording_output.as_ptr(),
677 UnitCompletion::callback,
678 context,
679 );
680 }
681 completion.wait().map_err(SCError::StreamError)
682 }
683
684 /// Remove a recording output from the stream (macOS 15.0+)
685 ///
686 /// Stops recording if the stream is currently recording.
687 ///
688 /// # Errors
689 ///
690 /// Returns `SCError::StreamError` if removing the recording output fails.
691 #[cfg(feature = "macos_15_0")]
692 pub fn remove_recording_output(
693 &self,
694 recording_output: &crate::recording_output::SCRecordingOutput,
695 ) -> Result<(), SCError> {
696 let (completion, context) = UnitCompletion::new();
697 unsafe {
698 ffi::sc_stream_remove_recording_output(
699 self.ptr,
700 recording_output.as_ptr(),
701 UnitCompletion::callback,
702 context,
703 );
704 }
705 completion.wait().map_err(SCError::StreamError)
706 }
707
708 /// Returns the raw pointer to the underlying Swift `SCStream` instance.
709 #[allow(dead_code)]
710 pub(crate) fn as_ptr(&self) -> *const c_void {
711 self.ptr
712 }
713}
714
715impl Drop for SCStream {
716 fn drop(&mut self) {
717 // Clean up all registered handlers (decrement ref counts)
718 for (id, of_type) in std::mem::take(&mut self.handler_ids) {
719 if decrement_handler_ref_count(id) {
720 // This was the last reference, tell Swift to remove the output
721 let output_type_int = match of_type {
722 SCStreamOutputType::Screen => 0,
723 SCStreamOutputType::Audio => 1,
724 SCStreamOutputType::Microphone => 2,
725 };
726 unsafe { ffi::sc_stream_remove_stream_output(self.ptr, output_type_int) };
727 }
728 }
729
730 // Clean up delegate from registry (decrement ref count)
731 if !self.ptr.is_null() {
732 decrement_delegate_ref_count(self.ptr as usize);
733 }
734
735 if !self.ptr.is_null() {
736 unsafe { ffi::sc_stream_release(self.ptr) };
737 }
738 }
739}
740
741impl Clone for SCStream {
742 /// Clone the stream reference.
743 ///
744 /// Cloning an `SCStream` creates a new reference to the same underlying
745 /// Swift `SCStream` object. The cloned stream shares the same handlers
746 /// as the original - they receive frames from the same capture session.
747 ///
748 /// Both the original and cloned stream share the same capture state, so:
749 /// - Starting capture on one affects both
750 /// - Stopping capture on one affects both
751 /// - Configuration updates affect both
752 /// - Handlers receive the same frames
753 ///
754 /// # Examples
755 ///
756 /// ```rust,no_run
757 /// use screencapturekit::prelude::*;
758 ///
759 /// # fn example() -> Result<(), Box<dyn std::error::Error>> {
760 /// # let content = SCShareableContent::get()?;
761 /// # let display = &content.displays()[0];
762 /// # let filter = SCContentFilter::create().with_display(display).with_excluding_windows(&[]).build();
763 /// # let config = SCStreamConfiguration::default();
764 /// let mut stream = SCStream::new(&filter, &config);
765 /// stream.add_output_handler(|_, _| println!("Handler 1"), SCStreamOutputType::Screen);
766 ///
767 /// // Clone shares the same handlers
768 /// let stream2 = stream.clone();
769 /// // Both stream and stream2 will receive frames via Handler 1
770 /// # Ok(())
771 /// # }
772 /// ```
773 fn clone(&self) -> Self {
774 // Increment delegate ref count if one exists for this stream
775 if !self.ptr.is_null() {
776 increment_delegate_ref_count(self.ptr as usize);
777 }
778
779 // Increment handler ref counts for all handlers this stream references
780 for (id, _) in &self.handler_ids {
781 increment_handler_ref_count(*id);
782 }
783
784 unsafe {
785 Self {
786 ptr: crate::ffi::sc_stream_retain(self.ptr),
787 handler_ids: self.handler_ids.clone(),
788 }
789 }
790 }
791}
792
793impl fmt::Debug for SCStream {
794 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795 f.debug_struct("SCStream")
796 .field("ptr", &self.ptr)
797 .field("handler_ids", &self.handler_ids)
798 .finish()
799 }
800}
801
802impl fmt::Display for SCStream {
803 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804 write!(f, "SCStream")
805 }
806}