@@ -325,86 +325,206 @@ impl AsyncSCContentSharingPicker {
325325 }
326326}
327327
328- /// Async wrapper for `SCStream`
328+ /// Async wrapper for `SCStream` with async frame iteration
329329///
330- /// Provides async methods for stream lifecycle operations .
330+ /// Provides async methods for stream lifecycle and frame iteration .
331331/// **Executor-agnostic** - works with any async runtime.
332332///
333- /// Note: Stream handlers still use callbacks and are not awaitable.
334- /// This wrapper provides async start/stop/update operations.
333+ /// # Examples
334+ ///
335+ /// ```rust,no_run
336+ /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
337+ /// use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
338+ /// use screencapturekit::stream::configuration::SCStreamConfiguration;
339+ /// use screencapturekit::stream::content_filter::SCContentFilter;
340+ /// use screencapturekit::stream::output_type::SCStreamOutputType;
341+ ///
342+ /// let content = AsyncSCShareableContent::get().await?;
343+ /// let display = &content.displays()[0];
344+ /// let filter = SCContentFilter::build().display(display).exclude_windows(&[]).build();
345+ /// let config = SCStreamConfiguration::build();
346+ ///
347+ /// let (mut stream, frames) = AsyncSCStream::new(&filter, &config, 30, SCStreamOutputType::Screen);
348+ /// stream.start_capture().await?;
349+ ///
350+ /// // Process frames asynchronously
351+ /// while let Some(frame) = frames.next().await {
352+ /// println!("Got frame!");
353+ /// }
354+ /// # Ok(())
355+ /// # }
356+ /// ```
335357pub struct AsyncSCStream {
336358 stream : crate :: stream:: SCStream ,
337359}
338360
339- impl AsyncSCStream {
340- /// Create a new async stream
341- ///
342- /// This is synchronous - the stream is created immediately.
343- #[ must_use]
344- pub fn new ( filter : & SCContentFilter , config : & SCStreamConfiguration ) -> Self {
345- Self {
346- stream : crate :: stream:: SCStream :: new ( filter, config) ,
361+ /// Async stream of sample buffers
362+ ///
363+ /// Provides async iteration over captured frames.
364+ /// Created by [`AsyncSCStream::new`].
365+ pub struct SampleBufferStream {
366+ inner : Arc < Mutex < SampleBufferStreamState > > ,
367+ }
368+
369+ struct SampleBufferStreamState {
370+ buffer : std:: collections:: VecDeque < crate :: cm:: CMSampleBuffer > ,
371+ waker : Option < Waker > ,
372+ closed : bool ,
373+ capacity : usize ,
374+ }
375+
376+ /// Internal sender for sample buffer stream
377+ struct SampleBufferSender {
378+ inner : Arc < Mutex < SampleBufferStreamState > > ,
379+ }
380+
381+ impl crate :: stream:: output_trait:: SCStreamOutputTrait for SampleBufferSender {
382+ fn did_output_sample_buffer (
383+ & self ,
384+ sample_buffer : crate :: cm:: CMSampleBuffer ,
385+ _of_type : crate :: stream:: output_type:: SCStreamOutputType ,
386+ ) {
387+ let Ok ( mut state) = self . inner . lock ( ) else {
388+ return ;
389+ } ;
390+
391+ // Drop oldest if at capacity
392+ if state. buffer . len ( ) >= state. capacity {
393+ state. buffer . pop_front ( ) ;
394+ }
395+
396+ state. buffer . push_back ( sample_buffer) ;
397+
398+ if let Some ( waker) = state. waker . take ( ) {
399+ waker. wake ( ) ;
400+ }
401+ }
402+ }
403+
404+ impl Drop for SampleBufferSender {
405+ fn drop ( & mut self ) {
406+ if let Ok ( mut state) = self . inner . lock ( ) {
407+ state. closed = true ;
408+ if let Some ( waker) = state. waker . take ( ) {
409+ waker. wake ( ) ;
410+ }
347411 }
348412 }
413+ }
349414
350- /// Add an output handler (synchronous)
415+ impl SampleBufferStream {
416+ /// Get the next sample buffer asynchronously
351417 ///
352- /// Handlers use callbacks and cannot be made async.
353- pub fn add_output_handler (
354- & mut self ,
355- handler : impl crate :: stream:: output_trait:: SCStreamOutputTrait + ' static ,
356- of_type : crate :: stream:: output_type:: SCStreamOutputType ,
357- ) -> Option < usize > {
358- self . stream . add_output_handler ( handler, of_type)
418+ /// Returns `None` when the stream is closed.
419+ pub fn next ( & self ) -> SampleBufferNext < ' _ > {
420+ SampleBufferNext { stream : self }
359421 }
360422
361- /// Add an output handler with a specific dispatch queue (synchronous)
362- pub fn add_output_handler_with_queue (
363- & mut self ,
364- handler : impl crate :: stream:: output_trait:: SCStreamOutputTrait + ' static ,
365- of_type : crate :: stream:: output_type:: SCStreamOutputType ,
366- queue : Option < & crate :: dispatch_queue:: DispatchQueue > ,
367- ) -> Option < usize > {
368- self . stream
369- . add_output_handler_with_queue ( handler, of_type, queue)
423+ /// Try to get a sample without waiting
424+ #[ must_use]
425+ pub fn try_next ( & self ) -> Option < crate :: cm:: CMSampleBuffer > {
426+ self . inner . lock ( ) . ok ( ) ?. buffer . pop_front ( )
370427 }
371428
372- /// Remove an output handler (synchronous)
373- pub fn remove_output_handler (
374- & mut self ,
375- id : usize ,
376- of_type : crate :: stream:: output_type:: SCStreamOutputType ,
377- ) -> bool {
378- self . stream . remove_output_handler ( id, of_type)
429+ /// Check if the stream has been closed
430+ #[ must_use]
431+ pub fn is_closed ( & self ) -> bool {
432+ self . inner . lock ( ) . map ( |s| s. closed ) . unwrap_or ( true )
379433 }
380434
381- /// Asynchronously start capture
382- ///
383- /// Runs the start operation on a separate thread to avoid blocking the async runtime.
384- /// **Executor-agnostic** - works with any async runtime.
385- ///
386- /// # Errors
435+ /// Get the number of buffered samples
436+ #[ must_use]
437+ pub fn len ( & self ) -> usize {
438+ self . inner . lock ( ) . map ( |s| s. buffer . len ( ) ) . unwrap_or ( 0 )
439+ }
440+
441+ /// Check if the buffer is empty
442+ #[ must_use]
443+ pub fn is_empty ( & self ) -> bool {
444+ self . len ( ) == 0
445+ }
446+
447+ /// Clear all buffered samples
448+ pub fn clear ( & self ) {
449+ if let Ok ( mut state) = self . inner . lock ( ) {
450+ state. buffer . clear ( ) ;
451+ }
452+ }
453+ }
454+
455+ /// Future for getting the next sample buffer
456+ pub struct SampleBufferNext < ' a > {
457+ stream : & ' a SampleBufferStream ,
458+ }
459+
460+ impl Future for SampleBufferNext < ' _ > {
461+ type Output = Option < crate :: cm:: CMSampleBuffer > ;
462+
463+ fn poll ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
464+ let Ok ( mut state) = self . stream . inner . lock ( ) else {
465+ return Poll :: Ready ( None ) ;
466+ } ;
467+
468+ if let Some ( sample) = state. buffer . pop_front ( ) {
469+ return Poll :: Ready ( Some ( sample) ) ;
470+ }
471+
472+ if state. closed {
473+ Poll :: Ready ( None )
474+ } else {
475+ state. waker = Some ( cx. waker ( ) . clone ( ) ) ;
476+ Poll :: Pending
477+ }
478+ }
479+ }
480+
481+ unsafe impl Send for SampleBufferStream { }
482+ unsafe impl Sync for SampleBufferStream { }
483+ unsafe impl Send for SampleBufferSender { }
484+ unsafe impl Sync for SampleBufferSender { }
485+
486+ impl AsyncSCStream {
487+ /// Create a new async stream with frame iteration
387488 ///
388- /// Returns an error if the capture fails to start .
489+ /// Returns both the stream and an async iterator for frames .
389490 ///
390- /// # Examples
491+ /// # Arguments
391492 ///
392- /// ```rust,no_run
393- /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
394- /// use screencapturekit::async_api::{AsyncSCShareableContent, AsyncSCStream};
395- /// use screencapturekit::stream::configuration::SCStreamConfiguration;
396- /// use screencapturekit::stream::content_filter::SCContentFilter;
493+ /// * `filter` - Content filter specifying what to capture
494+ /// * `config` - Stream configuration
495+ /// * `buffer_capacity` - Max frames to buffer (oldest dropped when full)
496+ /// * `output_type` - Type of output (Screen, Audio, Microphone)
497+ #[ must_use]
498+ pub fn new (
499+ filter : & SCContentFilter ,
500+ config : & SCStreamConfiguration ,
501+ buffer_capacity : usize ,
502+ output_type : crate :: stream:: output_type:: SCStreamOutputType ,
503+ ) -> ( Self , SampleBufferStream ) {
504+ let state = Arc :: new ( Mutex :: new ( SampleBufferStreamState {
505+ buffer : std:: collections:: VecDeque :: with_capacity ( buffer_capacity) ,
506+ waker : None ,
507+ closed : false ,
508+ capacity : buffer_capacity,
509+ } ) ) ;
510+
511+ let sender = SampleBufferSender {
512+ inner : Arc :: clone ( & state) ,
513+ } ;
514+
515+ let mut stream = crate :: stream:: SCStream :: new ( filter, config) ;
516+ stream. add_output_handler ( sender, output_type) ;
517+
518+ let receiver = SampleBufferStream { inner : state } ;
519+
520+ ( Self { stream } , receiver)
521+ }
522+
523+ /// Start capture asynchronously
397524 ///
398- /// let content = AsyncSCShareableContent::get().await?;
399- /// let display = &content.displays()[0];
400- /// let filter = SCContentFilter::build().display(display).build();
401- /// let config = SCStreamConfiguration::build();
525+ /// # Errors
402526 ///
403- /// let mut stream = AsyncSCStream::new(&filter, &config);
404- /// stream.start_capture().await?;
405- /// # Ok(())
406- /// # }
407- /// ```
527+ /// Returns an error if capture fails to start.
408528 pub async fn start_capture ( & self ) -> Result < ( ) , SCError > {
409529 let stream_ptr = std:: ptr:: addr_of!( self . stream) as usize ;
410530 BlockingFuture :: new ( move || {
@@ -413,14 +533,11 @@ impl AsyncSCStream {
413533 } ) . await
414534 }
415535
416- /// Asynchronously stop capture
417- ///
418- /// Runs the stop operation on a separate thread to avoid blocking the async runtime.
419- /// **Executor-agnostic** - works with any async runtime.
536+ /// Stop capture asynchronously
420537 ///
421538 /// # Errors
422539 ///
423- /// Returns an error if the capture fails to stop.
540+ /// Returns an error if capture fails to stop.
424541 pub async fn stop_capture ( & self ) -> Result < ( ) , SCError > {
425542 let stream_ptr = std:: ptr:: addr_of!( self . stream) as usize ;
426543 BlockingFuture :: new ( move || {
@@ -429,9 +546,7 @@ impl AsyncSCStream {
429546 } ) . await
430547 }
431548
432- /// Asynchronously update the stream configuration
433- ///
434- /// **Executor-agnostic** - works with any async runtime.
549+ /// Update stream configuration asynchronously
435550 ///
436551 /// # Errors
437552 ///
@@ -445,9 +560,7 @@ impl AsyncSCStream {
445560 } ) . await
446561 }
447562
448- /// Asynchronously update the content filter
449- ///
450- /// **Executor-agnostic** - works with any async runtime.
563+ /// Update content filter asynchronously
451564 ///
452565 /// # Errors
453566 ///
@@ -466,17 +579,6 @@ impl AsyncSCStream {
466579 pub fn inner ( & self ) -> & crate :: stream:: SCStream {
467580 & self . stream
468581 }
469-
470- /// Get a mutable reference to the underlying stream
471- pub fn inner_mut ( & mut self ) -> & mut crate :: stream:: SCStream {
472- & mut self . stream
473- }
474-
475- /// Consume this wrapper and return the underlying stream
476- #[ must_use]
477- pub fn into_inner ( self ) -> crate :: stream:: SCStream {
478- self . stream
479- }
480582}
481583
482584/// Async wrapper for `SCRecordingOutput` (macOS 15.0+)
0 commit comments