Skip to main content

gstreamer_app/
app_src.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{
4    mem, panic,
5    pin::Pin,
6    ptr,
7    sync::{Arc, Mutex},
8    task::{Context, Poll, Waker},
9};
10
11#[cfg(not(panic = "abort"))]
12use std::sync::atomic::{AtomicBool, Ordering};
13
14use futures_sink::Sink;
15use glib::{
16    ffi::{gboolean, gpointer},
17    prelude::*,
18    translate::*,
19};
20
21use crate::{AppSrc, ffi};
22
23#[allow(clippy::type_complexity)]
24pub struct AppSrcCallbacks {
25    need_data: Option<Box<dyn FnMut(&AppSrc, u32) + Send + 'static>>,
26    enough_data: Option<Box<dyn Fn(&AppSrc) + Send + Sync + 'static>>,
27    seek_data: Option<Box<dyn Fn(&AppSrc, u64) -> bool + Send + Sync + 'static>>,
28    #[cfg(not(panic = "abort"))]
29    panicked: AtomicBool,
30    callbacks: ffi::GstAppSrcCallbacks,
31}
32
33unsafe impl Send for AppSrcCallbacks {}
34unsafe impl Sync for AppSrcCallbacks {}
35
36impl AppSrcCallbacks {
37    pub fn builder() -> AppSrcCallbacksBuilder {
38        skip_assert_initialized!();
39
40        AppSrcCallbacksBuilder {
41            need_data: None,
42            enough_data: None,
43            seek_data: None,
44        }
45    }
46}
47
48#[allow(clippy::type_complexity)]
49#[must_use = "The builder must be built to be used"]
50pub struct AppSrcCallbacksBuilder {
51    need_data: Option<Box<dyn FnMut(&AppSrc, u32) + Send + 'static>>,
52    enough_data: Option<Box<dyn Fn(&AppSrc) + Send + Sync + 'static>>,
53    seek_data: Option<Box<dyn Fn(&AppSrc, u64) -> bool + Send + Sync + 'static>>,
54}
55
56impl AppSrcCallbacksBuilder {
57    pub fn need_data<F: FnMut(&AppSrc, u32) + Send + 'static>(self, need_data: F) -> Self {
58        Self {
59            need_data: Some(Box::new(need_data)),
60            ..self
61        }
62    }
63
64    pub fn need_data_if<F: FnMut(&AppSrc, u32) + Send + 'static>(
65        self,
66        need_data: F,
67        predicate: bool,
68    ) -> Self {
69        if predicate {
70            self.need_data(need_data)
71        } else {
72            self
73        }
74    }
75
76    pub fn need_data_if_some<F: FnMut(&AppSrc, u32) + Send + 'static>(
77        self,
78        need_data: Option<F>,
79    ) -> Self {
80        if let Some(need_data) = need_data {
81            self.need_data(need_data)
82        } else {
83            self
84        }
85    }
86
87    pub fn enough_data<F: Fn(&AppSrc) + Send + Sync + 'static>(self, enough_data: F) -> Self {
88        Self {
89            enough_data: Some(Box::new(enough_data)),
90            ..self
91        }
92    }
93
94    pub fn enough_data_if<F: Fn(&AppSrc) + Send + Sync + 'static>(
95        self,
96        enough_data: F,
97        predicate: bool,
98    ) -> Self {
99        if predicate {
100            self.enough_data(enough_data)
101        } else {
102            self
103        }
104    }
105
106    pub fn enough_data_if_some<F: Fn(&AppSrc) + Send + Sync + 'static>(
107        self,
108        enough_data: Option<F>,
109    ) -> Self {
110        if let Some(enough_data) = enough_data {
111            self.enough_data(enough_data)
112        } else {
113            self
114        }
115    }
116
117    pub fn seek_data<F: Fn(&AppSrc, u64) -> bool + Send + Sync + 'static>(
118        self,
119        seek_data: F,
120    ) -> Self {
121        Self {
122            seek_data: Some(Box::new(seek_data)),
123            ..self
124        }
125    }
126
127    pub fn seek_data_if<F: Fn(&AppSrc, u64) -> bool + Send + Sync + 'static>(
128        self,
129        seek_data: F,
130        predicate: bool,
131    ) -> Self {
132        if predicate {
133            self.seek_data(seek_data)
134        } else {
135            self
136        }
137    }
138
139    pub fn seek_data_if_some<F: Fn(&AppSrc, u64) -> bool + Send + Sync + 'static>(
140        self,
141        seek_data: Option<F>,
142    ) -> Self {
143        if let Some(seek_data) = seek_data {
144            self.seek_data(seek_data)
145        } else {
146            self
147        }
148    }
149
150    #[must_use = "Building the callbacks without using them has no effect"]
151    pub fn build(self) -> AppSrcCallbacks {
152        let have_need_data = self.need_data.is_some();
153        let have_enough_data = self.enough_data.is_some();
154        let have_seek_data = self.seek_data.is_some();
155
156        AppSrcCallbacks {
157            need_data: self.need_data,
158            enough_data: self.enough_data,
159            seek_data: self.seek_data,
160            #[cfg(not(panic = "abort"))]
161            panicked: AtomicBool::new(false),
162            callbacks: ffi::GstAppSrcCallbacks {
163                need_data: if have_need_data {
164                    Some(trampoline_need_data)
165                } else {
166                    None
167                },
168                enough_data: if have_enough_data {
169                    Some(trampoline_enough_data)
170                } else {
171                    None
172                },
173                seek_data: if have_seek_data {
174                    Some(trampoline_seek_data)
175                } else {
176                    None
177                },
178                _gst_reserved: [
179                    ptr::null_mut(),
180                    ptr::null_mut(),
181                    ptr::null_mut(),
182                    ptr::null_mut(),
183                ],
184            },
185        }
186    }
187}
188
189unsafe extern "C" fn trampoline_need_data(
190    appsrc: *mut ffi::GstAppSrc,
191    length: u32,
192    callbacks: gpointer,
193) {
194    unsafe {
195        let callbacks = callbacks as *mut AppSrcCallbacks;
196        let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
197
198        #[cfg(not(panic = "abort"))]
199        if (*callbacks).panicked.load(Ordering::Relaxed) {
200            let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
201            gst::subclass::post_panic_error_message(
202                element.upcast_ref(),
203                element.upcast_ref(),
204                None,
205            );
206            return;
207        }
208
209        if let Some(ref mut need_data) = (*callbacks).need_data {
210            let result =
211                panic::catch_unwind(panic::AssertUnwindSafe(|| need_data(&element, length)));
212            match result {
213                Ok(result) => result,
214                Err(err) => {
215                    #[cfg(panic = "abort")]
216                    {
217                        unreachable!("{err:?}");
218                    }
219                    #[cfg(not(panic = "abort"))]
220                    {
221                        (*callbacks).panicked.store(true, Ordering::Relaxed);
222                        gst::subclass::post_panic_error_message(
223                            element.upcast_ref(),
224                            element.upcast_ref(),
225                            Some(err),
226                        );
227                    }
228                }
229            }
230        }
231    }
232}
233
234unsafe extern "C" fn trampoline_enough_data(appsrc: *mut ffi::GstAppSrc, callbacks: gpointer) {
235    unsafe {
236        let callbacks = callbacks as *const AppSrcCallbacks;
237        let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
238
239        #[cfg(not(panic = "abort"))]
240        if (*callbacks).panicked.load(Ordering::Relaxed) {
241            let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
242            gst::subclass::post_panic_error_message(
243                element.upcast_ref(),
244                element.upcast_ref(),
245                None,
246            );
247            return;
248        }
249
250        if let Some(ref enough_data) = (*callbacks).enough_data {
251            let result = panic::catch_unwind(panic::AssertUnwindSafe(|| enough_data(&element)));
252            match result {
253                Ok(result) => result,
254                Err(err) => {
255                    #[cfg(panic = "abort")]
256                    {
257                        unreachable!("{err:?}");
258                    }
259                    #[cfg(not(panic = "abort"))]
260                    {
261                        (*callbacks).panicked.store(true, Ordering::Relaxed);
262                        gst::subclass::post_panic_error_message(
263                            element.upcast_ref(),
264                            element.upcast_ref(),
265                            Some(err),
266                        );
267                    }
268                }
269            }
270        }
271    }
272}
273
274unsafe extern "C" fn trampoline_seek_data(
275    appsrc: *mut ffi::GstAppSrc,
276    offset: u64,
277    callbacks: gpointer,
278) -> gboolean {
279    unsafe {
280        let callbacks = callbacks as *const AppSrcCallbacks;
281        let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
282
283        #[cfg(not(panic = "abort"))]
284        if (*callbacks).panicked.load(Ordering::Relaxed) {
285            let element: Borrowed<AppSrc> = from_glib_borrow(appsrc);
286            gst::subclass::post_panic_error_message(
287                element.upcast_ref(),
288                element.upcast_ref(),
289                None,
290            );
291            return false.into_glib();
292        }
293
294        let ret = if let Some(ref seek_data) = (*callbacks).seek_data {
295            let result =
296                panic::catch_unwind(panic::AssertUnwindSafe(|| seek_data(&element, offset)));
297            match result {
298                Ok(result) => result,
299                Err(err) => {
300                    #[cfg(panic = "abort")]
301                    {
302                        unreachable!("{err:?}");
303                    }
304                    #[cfg(not(panic = "abort"))]
305                    {
306                        (*callbacks).panicked.store(true, Ordering::Relaxed);
307                        gst::subclass::post_panic_error_message(
308                            element.upcast_ref(),
309                            element.upcast_ref(),
310                            Some(err),
311                        );
312
313                        false
314                    }
315                }
316            }
317        } else {
318            false
319        };
320
321        ret.into_glib()
322    }
323}
324
325unsafe extern "C" fn destroy_callbacks(ptr: gpointer) {
326    unsafe {
327        let _ = Box::<AppSrcCallbacks>::from_raw(ptr as *mut _);
328    }
329}
330
331impl AppSrc {
332    // rustdoc-stripper-ignore-next
333    /// Creates a new builder-pattern struct instance to construct [`AppSrc`] objects.
334    ///
335    /// This method returns an instance of [`AppSrcBuilder`](crate::builders::AppSrcBuilder) which can be used to create [`AppSrc`] objects.
336    pub fn builder<'a>() -> AppSrcBuilder<'a> {
337        assert_initialized_main_thread!();
338        AppSrcBuilder {
339            builder: gst::Object::builder(),
340            callbacks: None,
341            automatic_eos: None,
342        }
343    }
344
345    /// Set callbacks which will be executed when data is needed, enough data has
346    /// been collected or when a seek should be performed.
347    /// This is an alternative to using the signals, it has lower overhead and is thus
348    /// less expensive, but also less flexible.
349    ///
350    /// If callbacks are installed, no signals will be emitted for performance
351    /// reasons.
352    ///
353    /// Before 1.16.3 it was not possible to change the callbacks in a thread-safe
354    /// way.
355    ///
356    /// Since 1.28.3 it is allowed to set the `callbacks` to [`None`] to unset them.
357    ///
358    /// Note that `gst_app_src_set_callbacks()` and
359    /// `gst_app_src_set_simple_callbacks()` are mutually exclusive and setting one
360    /// will unset the other.
361    /// ## `callbacks`
362    /// the callbacks
363    /// ## `notify`
364    /// a destroy notify function
365    #[doc(alias = "gst_app_src_set_callbacks")]
366    pub fn set_callbacks(&self, callbacks: AppSrcCallbacks) {
367        unsafe {
368            let src = self.to_glib_none().0;
369            #[allow(clippy::manual_dangling_ptr)]
370            #[cfg(not(feature = "v1_18"))]
371            {
372                static SET_ONCE_QUARK: std::sync::OnceLock<glib::Quark> =
373                    std::sync::OnceLock::new();
374
375                let set_once_quark = SET_ONCE_QUARK
376                    .get_or_init(|| glib::Quark::from_str("gstreamer-rs-app-src-callbacks"));
377
378                // This is not thread-safe before 1.16.3, see
379                // https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/merge_requests/570
380                if gst::version() < (1, 16, 3, 0) {
381                    if !glib::gobject_ffi::g_object_get_qdata(
382                        src as *mut _,
383                        set_once_quark.into_glib(),
384                    )
385                    .is_null()
386                    {
387                        panic!("AppSrc callbacks can only be set once");
388                    }
389
390                    glib::gobject_ffi::g_object_set_qdata(
391                        src as *mut _,
392                        set_once_quark.into_glib(),
393                        1 as *mut _,
394                    );
395                }
396            }
397
398            ffi::gst_app_src_set_callbacks(
399                src,
400                mut_override(&callbacks.callbacks),
401                Box::into_raw(Box::new(callbacks)) as *mut _,
402                Some(destroy_callbacks),
403            );
404        }
405    }
406
407    /// Configure the `min` and `max` latency in `src`. If `min` is set to -1, the
408    /// default latency calculations for pseudo-live sources will be used.
409    /// ## `min`
410    /// the min latency
411    /// ## `max`
412    /// the max latency
413    #[doc(alias = "gst_app_src_set_latency")]
414    pub fn set_latency(
415        &self,
416        min: impl Into<Option<gst::ClockTime>>,
417        max: impl Into<Option<gst::ClockTime>>,
418    ) {
419        unsafe {
420            ffi::gst_app_src_set_latency(
421                self.to_glib_none().0,
422                min.into().into_glib(),
423                max.into().into_glib(),
424            );
425        }
426    }
427
428    /// Retrieve the min and max latencies in `min` and `max` respectively.
429    ///
430    /// # Returns
431    ///
432    ///
433    /// ## `min`
434    /// the min latency
435    ///
436    /// ## `max`
437    /// the max latency
438    #[doc(alias = "get_latency")]
439    #[doc(alias = "gst_app_src_get_latency")]
440    pub fn latency(&self) -> (Option<gst::ClockTime>, Option<gst::ClockTime>) {
441        unsafe {
442            let mut min = mem::MaybeUninit::uninit();
443            let mut max = mem::MaybeUninit::uninit();
444            ffi::gst_app_src_get_latency(self.to_glib_none().0, min.as_mut_ptr(), max.as_mut_ptr());
445            (from_glib(min.assume_init()), from_glib(max.assume_init()))
446        }
447    }
448
449    #[doc(alias = "do-timestamp")]
450    #[doc(alias = "gst_base_src_set_do_timestamp")]
451    pub fn set_do_timestamp(&self, timestamp: bool) {
452        unsafe {
453            gst_base::ffi::gst_base_src_set_do_timestamp(
454                self.as_ptr() as *mut gst_base::ffi::GstBaseSrc,
455                timestamp.into_glib(),
456            );
457        }
458    }
459
460    #[doc(alias = "do-timestamp")]
461    #[doc(alias = "gst_base_src_get_do_timestamp")]
462    pub fn do_timestamp(&self) -> bool {
463        unsafe {
464            from_glib(gst_base::ffi::gst_base_src_get_do_timestamp(
465                self.as_ptr() as *mut gst_base::ffi::GstBaseSrc
466            ))
467        }
468    }
469
470    #[doc(alias = "do-timestamp")]
471    pub fn connect_do_timestamp_notify<F: Fn(&Self) + Send + Sync + 'static>(
472        &self,
473        f: F,
474    ) -> glib::SignalHandlerId {
475        unsafe extern "C" fn notify_do_timestamp_trampoline<
476            F: Fn(&AppSrc) + Send + Sync + 'static,
477        >(
478            this: *mut ffi::GstAppSrc,
479            _param_spec: glib::ffi::gpointer,
480            f: glib::ffi::gpointer,
481        ) {
482            unsafe {
483                let f: &F = &*(f as *const F);
484                f(&AppSrc::from_glib_borrow(this))
485            }
486        }
487        unsafe {
488            let f: Box<F> = Box::new(f);
489            glib::signal::connect_raw(
490                self.as_ptr() as *mut _,
491                b"notify::do-timestamp\0".as_ptr() as *const _,
492                Some(mem::transmute::<*const (), unsafe extern "C" fn()>(
493                    notify_do_timestamp_trampoline::<F> as *const (),
494                )),
495                Box::into_raw(f),
496            )
497        }
498    }
499
500    #[doc(alias = "set-automatic-eos")]
501    #[doc(alias = "gst_base_src_set_automatic_eos")]
502    pub fn set_automatic_eos(&self, automatic_eos: bool) {
503        unsafe {
504            gst_base::ffi::gst_base_src_set_automatic_eos(
505                self.as_ptr() as *mut gst_base::ffi::GstBaseSrc,
506                automatic_eos.into_glib(),
507            );
508        }
509    }
510
511    pub fn sink(&self) -> AppSrcSink {
512        AppSrcSink::new(self)
513    }
514}
515
516// rustdoc-stripper-ignore-next
517/// A [builder-pattern] type to construct [`AppSrc`] objects.
518///
519/// [builder-pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
520#[must_use = "The builder must be built to be used"]
521pub struct AppSrcBuilder<'a> {
522    builder: gst::gobject::GObjectBuilder<'a, AppSrc>,
523    callbacks: Option<AppSrcCallbacks>,
524    automatic_eos: Option<bool>,
525}
526
527impl<'a> AppSrcBuilder<'a> {
528    // rustdoc-stripper-ignore-next
529    /// Build the [`AppSrc`].
530    ///
531    /// # Panics
532    ///
533    /// This panics if the [`AppSrc`] doesn't have all the given properties or
534    /// property values of the wrong type are provided.
535    #[must_use = "Building the object from the builder is usually expensive and is not expected to have side effects"]
536    pub fn build(self) -> AppSrc {
537        let appsrc = self.builder.build().unwrap();
538
539        if let Some(callbacks) = self.callbacks {
540            appsrc.set_callbacks(callbacks);
541        }
542
543        if let Some(automatic_eos) = self.automatic_eos {
544            appsrc.set_automatic_eos(automatic_eos);
545        }
546
547        appsrc
548    }
549
550    pub fn automatic_eos(self, automatic_eos: bool) -> Self {
551        Self {
552            automatic_eos: Some(automatic_eos),
553            ..self
554        }
555    }
556
557    pub fn block(self, block: bool) -> Self {
558        Self {
559            builder: self.builder.property("block", block),
560            ..self
561        }
562    }
563
564    pub fn callbacks(self, callbacks: AppSrcCallbacks) -> Self {
565        Self {
566            callbacks: Some(callbacks),
567            ..self
568        }
569    }
570
571    pub fn caps(self, caps: &'a gst::Caps) -> Self {
572        Self {
573            builder: self.builder.property("caps", caps),
574            ..self
575        }
576    }
577
578    pub fn do_timestamp(self, do_timestamp: bool) -> Self {
579        Self {
580            builder: self.builder.property("do-timestamp", do_timestamp),
581            ..self
582        }
583    }
584
585    pub fn duration(self, duration: u64) -> Self {
586        Self {
587            builder: self.builder.property("duration", duration),
588            ..self
589        }
590    }
591
592    pub fn format(self, format: gst::Format) -> Self {
593        Self {
594            builder: self.builder.property("format", format),
595            ..self
596        }
597    }
598
599    #[cfg(feature = "v1_18")]
600    #[cfg_attr(docsrs, doc(cfg(feature = "v1_18")))]
601    pub fn handle_segment_change(self, handle_segment_change: bool) -> Self {
602        Self {
603            builder: self
604                .builder
605                .property("handle-segment-change", handle_segment_change),
606            ..self
607        }
608    }
609
610    pub fn is_live(self, is_live: bool) -> Self {
611        Self {
612            builder: self.builder.property("is-live", is_live),
613            ..self
614        }
615    }
616
617    #[cfg(feature = "v1_20")]
618    #[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
619    pub fn leaky_type(self, leaky_type: crate::AppLeakyType) -> Self {
620        Self {
621            builder: self.builder.property("leaky-type", leaky_type),
622            ..self
623        }
624    }
625
626    #[cfg(feature = "v1_20")]
627    #[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
628    pub fn max_buffers(self, max_buffers: u64) -> Self {
629        Self {
630            builder: self.builder.property("max-buffers", max_buffers),
631            ..self
632        }
633    }
634
635    pub fn max_bytes(self, max_bytes: u64) -> Self {
636        Self {
637            builder: self.builder.property("max-bytes", max_bytes),
638            ..self
639        }
640    }
641
642    pub fn max_latency(self, max_latency: i64) -> Self {
643        Self {
644            builder: self.builder.property("max-latency", max_latency),
645            ..self
646        }
647    }
648
649    #[cfg(feature = "v1_20")]
650    #[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
651    pub fn max_time(self, max_time: gst::ClockTime) -> Self {
652        Self {
653            builder: self.builder.property("max-time", max_time),
654            ..self
655        }
656    }
657
658    pub fn min_latency(self, min_latency: i64) -> Self {
659        Self {
660            builder: self.builder.property("min-latency", min_latency),
661            ..self
662        }
663    }
664
665    pub fn min_percent(self, min_percent: u32) -> Self {
666        Self {
667            builder: self.builder.property("min-percent", min_percent),
668            ..self
669        }
670    }
671
672    pub fn size(self, size: i64) -> Self {
673        Self {
674            builder: self.builder.property("size", size),
675            ..self
676        }
677    }
678
679    pub fn stream_type(self, stream_type: crate::AppStreamType) -> Self {
680        Self {
681            builder: self.builder.property("stream-type", stream_type),
682            ..self
683        }
684    }
685
686    #[cfg(feature = "v1_28")]
687    #[cfg_attr(docsrs, doc(cfg(feature = "v1_28")))]
688    pub fn silent(self, silent: bool) -> Self {
689        Self {
690            builder: self.builder.property("silent", silent),
691            ..self
692        }
693    }
694
695    // rustdoc-stripper-ignore-next
696    /// Sets property `name` to the given value `value`.
697    ///
698    /// Overrides any default or previously defined value for `name`.
699    #[inline]
700    pub fn property(self, name: &'a str, value: impl Into<glib::Value> + 'a) -> Self {
701        Self {
702            builder: self.builder.property(name, value),
703            ..self
704        }
705    }
706
707    // rustdoc-stripper-ignore-next
708    /// Sets property `name` to the given string value `value`.
709    #[inline]
710    pub fn property_from_str(self, name: &'a str, value: &'a str) -> Self {
711        Self {
712            builder: self.builder.property_from_str(name, value),
713            ..self
714        }
715    }
716
717    gst::impl_builder_gvalue_extra_setters!(property_and_name);
718}
719
720#[derive(Debug)]
721pub struct AppSrcSink {
722    app_src: glib::WeakRef<AppSrc>,
723    waker_reference: Arc<Mutex<Option<Waker>>>,
724}
725
726impl AppSrcSink {
727    fn new(app_src: &AppSrc) -> Self {
728        skip_assert_initialized!();
729
730        let waker_reference = Arc::new(Mutex::new(None as Option<Waker>));
731
732        app_src.set_callbacks(
733            AppSrcCallbacks::builder()
734                .need_data({
735                    let waker_reference = Arc::clone(&waker_reference);
736
737                    move |_, _| {
738                        if let Some(waker) = waker_reference.lock().unwrap().take() {
739                            waker.wake();
740                        }
741                    }
742                })
743                .build(),
744        );
745
746        Self {
747            app_src: app_src.downgrade(),
748            waker_reference,
749        }
750    }
751}
752
753impl Drop for AppSrcSink {
754    fn drop(&mut self) {
755        #[cfg(not(feature = "v1_18"))]
756        {
757            // This is not thread-safe before 1.16.3, see
758            // https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/merge_requests/570
759            if gst::version() >= (1, 16, 3, 0)
760                && let Some(app_src) = self.app_src.upgrade()
761            {
762                app_src.set_callbacks(AppSrcCallbacks::builder().build());
763            }
764        }
765    }
766}
767
768impl Sink<gst::Sample> for AppSrcSink {
769    type Error = gst::FlowError;
770
771    fn poll_ready(self: Pin<&mut Self>, context: &mut Context) -> Poll<Result<(), Self::Error>> {
772        let mut waker = self.waker_reference.lock().unwrap();
773
774        let Some(app_src) = self.app_src.upgrade() else {
775            return Poll::Ready(Err(gst::FlowError::Eos));
776        };
777
778        let current_level_bytes = app_src.current_level_bytes();
779        let max_bytes = app_src.max_bytes();
780
781        if current_level_bytes >= max_bytes && max_bytes != 0 {
782            waker.replace(context.waker().to_owned());
783
784            Poll::Pending
785        } else {
786            Poll::Ready(Ok(()))
787        }
788    }
789
790    fn start_send(self: Pin<&mut Self>, sample: gst::Sample) -> Result<(), Self::Error> {
791        let Some(app_src) = self.app_src.upgrade() else {
792            return Err(gst::FlowError::Eos);
793        };
794
795        app_src.push_sample(&sample)?;
796
797        Ok(())
798    }
799
800    fn poll_flush(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
801        Poll::Ready(Ok(()))
802    }
803
804    fn poll_close(self: Pin<&mut Self>, _: &mut Context) -> Poll<Result<(), Self::Error>> {
805        let Some(app_src) = self.app_src.upgrade() else {
806            return Poll::Ready(Ok(()));
807        };
808
809        app_src.end_of_stream()?;
810
811        Poll::Ready(Ok(()))
812    }
813}
814
815#[cfg(test)]
816mod tests {
817    use std::sync::atomic::{AtomicUsize, Ordering};
818
819    use futures_util::{sink::SinkExt, stream::StreamExt};
820    use gst::prelude::*;
821
822    use super::*;
823
824    #[test]
825    fn test_app_src_sink() {
826        gst::init().unwrap();
827
828        let appsrc = gst::ElementFactory::make("appsrc").build().unwrap();
829        let fakesink = gst::ElementFactory::make("fakesink")
830            .property("signal-handoffs", true)
831            .build()
832            .unwrap();
833
834        let pipeline = gst::Pipeline::new();
835        pipeline.add(&appsrc).unwrap();
836        pipeline.add(&fakesink).unwrap();
837
838        appsrc.link(&fakesink).unwrap();
839
840        let mut bus_stream = pipeline.bus().unwrap().stream();
841        let mut app_src_sink = appsrc.dynamic_cast::<AppSrc>().unwrap().sink();
842
843        let sample_quantity = 5;
844
845        let samples = (0..sample_quantity)
846            .map(|_| gst::Sample::builder().buffer(&gst::Buffer::new()).build())
847            .collect::<Vec<gst::Sample>>();
848
849        let mut sample_stream = futures_util::stream::iter(samples).map(Ok);
850
851        let handoff_count_reference = Arc::new(AtomicUsize::new(0));
852
853        fakesink.connect("handoff", false, {
854            let handoff_count_reference = Arc::clone(&handoff_count_reference);
855
856            move |_| {
857                handoff_count_reference.fetch_add(1, Ordering::AcqRel);
858
859                None
860            }
861        });
862
863        pipeline.set_state(gst::State::Playing).unwrap();
864
865        futures_executor::block_on(app_src_sink.send_all(&mut sample_stream)).unwrap();
866        futures_executor::block_on(app_src_sink.close()).unwrap();
867
868        while let Some(message) = futures_executor::block_on(bus_stream.next()) {
869            match message.view() {
870                gst::MessageView::Eos(_) => break,
871                gst::MessageView::Error(_) => unreachable!(),
872                _ => continue,
873            }
874        }
875
876        pipeline.set_state(gst::State::Null).unwrap();
877
878        assert_eq!(
879            handoff_count_reference.load(Ordering::Acquire),
880            sample_quantity
881        );
882    }
883
884    #[test]
885    fn builder_caps_lt() {
886        gst::init().unwrap();
887
888        let caps = &gst::Caps::new_any();
889        {
890            let stream_type = "random-access".to_owned();
891            let appsrc = AppSrc::builder()
892                .property_from_str("stream-type", &stream_type)
893                .caps(caps)
894                .build();
895            assert_eq!(
896                appsrc.property::<crate::AppStreamType>("stream-type"),
897                crate::AppStreamType::RandomAccess
898            );
899            assert!(appsrc.property::<gst::Caps>("caps").is_any());
900        }
901
902        let stream_type = &"random-access".to_owned();
903        {
904            let caps = &gst::Caps::new_any();
905            let appsrc = AppSrc::builder()
906                .property_from_str("stream-type", stream_type)
907                .caps(caps)
908                .build();
909            assert_eq!(
910                appsrc.property::<crate::AppStreamType>("stream-type"),
911                crate::AppStreamType::RandomAccess
912            );
913            assert!(appsrc.property::<gst::Caps>("caps").is_any());
914        }
915    }
916}