@@ -70,6 +70,7 @@ use crate::error::SCError;
7070use crate :: shareable_content:: SCShareableContent ;
7171use crate :: stream:: configuration:: SCStreamConfiguration ;
7272use crate :: stream:: content_filter:: SCContentFilter ;
73+ use crate :: stream:: output_type:: SCStreamOutputType ;
7374use crate :: utils:: completion:: { error_from_cstr, AsyncCompletion , AsyncCompletionFuture } ;
7475use std:: ffi:: c_void;
7576use std:: future:: Future ;
@@ -304,7 +305,7 @@ impl AsyncSCShareableContent {
304305/// stale ones, but means consumers that fall behind will miss intermediate
305306/// frames rather than blocking the capture callback.
306307struct AsyncSampleIteratorState {
307- buffer : std:: collections:: VecDeque < crate :: cm:: CMSampleBuffer > ,
308+ buffer : std:: collections:: VecDeque < ( crate :: cm:: CMSampleBuffer , SCStreamOutputType ) > ,
308309 waker : Option < Waker > ,
309310 closed : bool ,
310311 capacity : usize ,
@@ -320,7 +321,7 @@ impl crate::stream::output_trait::SCStreamOutputTrait for AsyncSampleSender {
320321 fn did_output_sample_buffer (
321322 & self ,
322323 sample_buffer : crate :: cm:: CMSampleBuffer ,
323- _of_type : crate :: stream :: output_type :: SCStreamOutputType ,
324+ of_type : SCStreamOutputType ,
324325 ) {
325326 let Ok ( mut state) = self . inner . lock ( ) else {
326327 return ;
@@ -331,7 +332,7 @@ impl crate::stream::output_trait::SCStreamOutputTrait for AsyncSampleSender {
331332 state. buffer . pop_front ( ) ;
332333 }
333334
334- state. buffer . push_back ( sample_buffer) ;
335+ state. buffer . push_back ( ( sample_buffer, of_type ) ) ;
335336
336337 if let Some ( waker) = state. waker . take ( ) {
337338 waker. wake ( ) ;
@@ -369,7 +370,7 @@ impl Future for NextSample<'_> {
369370 return Poll :: Ready ( None ) ;
370371 } ;
371372
372- if let Some ( sample) = state. buffer . pop_front ( ) {
373+ if let Some ( ( sample, _of_type ) ) = state. buffer . pop_front ( ) {
373374 return Poll :: Ready ( Some ( sample) ) ;
374375 }
375376
@@ -392,11 +393,54 @@ impl Future for NextSample<'_> {
392393 }
393394}
394395
396+ /// Future for getting the next sample buffer together with its output type.
397+ ///
398+ /// Like [`NextSample`], but yields the [`SCStreamOutputType`] alongside the
399+ /// buffer so consumers of a multi-output stream (e.g. screen + audio via
400+ /// [`AsyncSCStream::add_output_type`]) can tell frames apart. Returned by
401+ /// [`AsyncSCStream::next_typed`].
402+ pub struct NextSampleTyped < ' a > {
403+ state : & ' a Arc < Mutex < AsyncSampleIteratorState > > ,
404+ }
405+
406+ impl std:: fmt:: Debug for NextSampleTyped < ' _ > {
407+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
408+ f. debug_struct ( "NextSampleTyped" ) . finish_non_exhaustive ( )
409+ }
410+ }
411+
412+ impl Future for NextSampleTyped < ' _ > {
413+ type Output = Option < ( crate :: cm:: CMSampleBuffer , SCStreamOutputType ) > ;
414+
415+ fn poll ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Self :: Output > {
416+ let Ok ( mut state) = self . state . lock ( ) else {
417+ return Poll :: Ready ( None ) ;
418+ } ;
419+
420+ if let Some ( sample) = state. buffer . pop_front ( ) {
421+ return Poll :: Ready ( Some ( sample) ) ;
422+ }
423+
424+ if state. closed {
425+ Poll :: Ready ( None )
426+ } else {
427+ // See `NextSample::poll` for the lost-wakeup rationale.
428+ let waker = cx. waker ( ) ;
429+ match state. waker {
430+ Some ( ref existing) if existing. will_wake ( waker) => { }
431+ _ => state. waker = Some ( waker. clone ( ) ) ,
432+ }
433+ Poll :: Pending
434+ }
435+ }
436+ }
437+
395438// SAFETY: `AsyncSampleSender` holds `Arc<Mutex<AsyncSampleIteratorState>>`.
396- // `AsyncSampleIteratorState` contains `VecDeque<CMSampleBuffer>` and `Option<Waker>`;
397- // `CMSampleBuffer` has its own `unsafe impl Send` (it is an Apple-owned handle
398- // safe to transfer across threads) and `Waker` is `Send + Sync`, so the whole
399- // `Arc<Mutex<...>>` is safe to send and share across threads.
439+ // `AsyncSampleIteratorState` buffers `(CMSampleBuffer, SCStreamOutputType)`
440+ // pairs plus an `Option<Waker>` and `Option<SCError>`; `CMSampleBuffer` has its
441+ // own `unsafe impl Send` (it is an Apple-owned handle safe to transfer across
442+ // threads) and the rest are `Send + Sync`, so the whole `Arc<Mutex<...>>` is
443+ // safe to send and share across threads.
400444unsafe impl Send for AsyncSampleSender { }
401445unsafe impl Sync for AsyncSampleSender { }
402446
@@ -602,16 +646,61 @@ impl AsyncSCStream {
602646
603647 /// Get the next sample buffer asynchronously
604648 ///
605- /// Returns `None` when the stream is closed.
649+ /// Returns `None` when the stream is closed. For a multi-output stream
650+ /// (see [`add_output_type`](Self::add_output_type)) use
651+ /// [`next_typed`](Self::next_typed) to also learn each sample's
652+ /// [`SCStreamOutputType`].
606653 pub fn next ( & self ) -> NextSample < ' _ > {
607654 NextSample {
608655 state : & self . iterator_state ,
609656 }
610657 }
611658
659+ /// Get the next sample buffer together with its output type.
660+ ///
661+ /// Use this when the stream carries more than one output type (e.g. screen
662+ /// and audio) and you need to tell the samples apart. Returns `None` when
663+ /// the stream is closed.
664+ pub fn next_typed ( & self ) -> NextSampleTyped < ' _ > {
665+ NextSampleTyped {
666+ state : & self . iterator_state ,
667+ }
668+ }
669+
670+ /// Also deliver samples of an additional output type.
671+ ///
672+ /// By default an [`AsyncSCStream`] carries the single output type passed to
673+ /// [`new`](Self::new). Call this to capture more than one type from one
674+ /// stream — for example add [`SCStreamOutputType::Audio`] to a stream
675+ /// created for [`SCStreamOutputType::Screen`] to capture audio and video
676+ /// together. Samples from every registered type share the same lossy
677+ /// buffer; use [`next_typed`](Self::next_typed) /
678+ /// [`try_next_typed`](Self::try_next_typed) to distinguish them.
679+ ///
680+ /// Returns `true` if the output type was registered. Registration can fail
681+ /// if the stream configuration does not enable that type (e.g. audio
682+ /// capture was not configured).
683+ pub fn add_output_type ( & mut self , output_type : SCStreamOutputType ) -> bool {
684+ let sender = AsyncSampleSender {
685+ inner : Arc :: clone ( & self . iterator_state ) ,
686+ } ;
687+ self . stream . add_output_handler ( sender, output_type) . is_some ( )
688+ }
689+
612690 /// Try to get a sample without waiting
613691 #[ must_use]
614692 pub fn try_next ( & self ) -> Option < crate :: cm:: CMSampleBuffer > {
693+ self . iterator_state
694+ . lock ( )
695+ . ok ( ) ?
696+ . buffer
697+ . pop_front ( )
698+ . map ( |( buffer, _of_type) | buffer)
699+ }
700+
701+ /// Try to get a sample together with its output type, without waiting.
702+ #[ must_use]
703+ pub fn try_next_typed ( & self ) -> Option < ( crate :: cm:: CMSampleBuffer , SCStreamOutputType ) > {
615704 self . iterator_state . lock ( ) . ok ( ) ?. buffer . pop_front ( )
616705 }
617706
0 commit comments