mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-10-31 14:49:24 +00:00
e9b8029685
This commit adds `field_if_any` variants for `IntoIterator` builders fields. The field will only be set if the provided collection is not empty. For `Element` properties and other `Value` based setters, the function takes a parameter to indicate the `ValueType` to use for the resulting `Value`. This allows converting this code: ```rust let webrtcsrc = gst::ElementFactory::make("webrtcsrc") .build() .unwrap(); if !args.audio_codecs.is_empty() { webrtcsrc.set_property("audio-codecs", gst::Array::new(&args.audio_codecs)); } ``` to: ```rust let webrtcsrc = gst::ElementFactory::make("webrtcsrc") .property_if_any::<gst::Array>("audio-codecs", &args.audio_codecs) .build() .unwrap(); ```` Similarly, a new function `field_from_iter()` allows settings the property or field, regardless of whether it is empty or not: ```rust let webrtcsrc = gst::ElementFactory::make("webrtcsrc") .property_from_iter::<gst::Array>("audio-codecs", &args.audio_codecs) .build() .unwrap(); ```` The above will override the default value if `args.audio_codecs` is empty.
863 lines
28 KiB
Rust
863 lines
28 KiB
Rust
// Take a look at the license at the top of the repository in the LICENSE file.
|
|
|
|
use std::{borrow::Cow, future::Future, sync::atomic};
|
|
|
|
use glib::{subclass::prelude::*, translate::*};
|
|
|
|
use super::prelude::*;
|
|
use crate::{
|
|
prelude::*, Element, Event, PadTemplate, QueryRef, StateChange, StateChangeError,
|
|
StateChangeReturn, StateChangeSuccess,
|
|
};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ElementMetadata {
|
|
long_name: Cow<'static, str>,
|
|
classification: Cow<'static, str>,
|
|
description: Cow<'static, str>,
|
|
author: Cow<'static, str>,
|
|
additional: Cow<'static, [(Cow<'static, str>, Cow<'static, str>)]>,
|
|
}
|
|
|
|
impl ElementMetadata {
|
|
pub fn new(long_name: &str, classification: &str, description: &str, author: &str) -> Self {
|
|
Self {
|
|
long_name: Cow::Owned(long_name.into()),
|
|
classification: Cow::Owned(classification.into()),
|
|
description: Cow::Owned(description.into()),
|
|
author: Cow::Owned(author.into()),
|
|
additional: Cow::Borrowed(&[]),
|
|
}
|
|
}
|
|
|
|
pub fn with_additional(
|
|
long_name: &str,
|
|
classification: &str,
|
|
description: &str,
|
|
author: &str,
|
|
additional: &[(&str, &str)],
|
|
) -> Self {
|
|
Self {
|
|
long_name: Cow::Owned(long_name.into()),
|
|
classification: Cow::Owned(classification.into()),
|
|
description: Cow::Owned(description.into()),
|
|
author: Cow::Owned(author.into()),
|
|
additional: additional
|
|
.iter()
|
|
.copied()
|
|
.map(|(key, value)| (Cow::Owned(key.into()), Cow::Owned(value.into())))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
pub const fn with_cow(
|
|
long_name: Cow<'static, str>,
|
|
classification: Cow<'static, str>,
|
|
description: Cow<'static, str>,
|
|
author: Cow<'static, str>,
|
|
additional: Cow<'static, [(Cow<'static, str>, Cow<'static, str>)]>,
|
|
) -> Self {
|
|
Self {
|
|
long_name,
|
|
classification,
|
|
description,
|
|
author,
|
|
additional,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait ElementImpl: ElementImplExt + GstObjectImpl + Send + Sync {
|
|
fn metadata() -> Option<&'static ElementMetadata> {
|
|
None
|
|
}
|
|
|
|
fn pad_templates() -> &'static [PadTemplate] {
|
|
&[]
|
|
}
|
|
|
|
fn change_state(
|
|
&self,
|
|
transition: StateChange,
|
|
) -> Result<StateChangeSuccess, StateChangeError> {
|
|
self.parent_change_state(transition)
|
|
}
|
|
|
|
fn request_new_pad(
|
|
&self,
|
|
templ: &crate::PadTemplate,
|
|
name: Option<&str>,
|
|
caps: Option<&crate::Caps>,
|
|
) -> Option<crate::Pad> {
|
|
self.parent_request_new_pad(templ, name, caps)
|
|
}
|
|
|
|
fn release_pad(&self, pad: &crate::Pad) {
|
|
self.parent_release_pad(pad)
|
|
}
|
|
|
|
fn send_event(&self, event: Event) -> bool {
|
|
self.parent_send_event(event)
|
|
}
|
|
|
|
fn query(&self, query: &mut QueryRef) -> bool {
|
|
self.parent_query(query)
|
|
}
|
|
|
|
fn set_context(&self, context: &crate::Context) {
|
|
self.parent_set_context(context)
|
|
}
|
|
|
|
fn set_clock(&self, clock: Option<&crate::Clock>) -> bool {
|
|
self.parent_set_clock(clock)
|
|
}
|
|
|
|
fn provide_clock(&self) -> Option<crate::Clock> {
|
|
self.parent_provide_clock()
|
|
}
|
|
|
|
fn post_message(&self, msg: crate::Message) -> bool {
|
|
self.parent_post_message(msg)
|
|
}
|
|
}
|
|
|
|
mod sealed {
|
|
pub trait Sealed {}
|
|
impl<T: super::ElementImplExt> Sealed for T {}
|
|
}
|
|
|
|
pub trait ElementImplExt: sealed::Sealed + ObjectSubclass {
|
|
fn parent_change_state(
|
|
&self,
|
|
transition: StateChange,
|
|
) -> Result<StateChangeSuccess, StateChangeError> {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
let f = (*parent_class)
|
|
.change_state
|
|
.expect("Missing parent function `change_state`");
|
|
try_from_glib(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
transition.into_glib(),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn parent_request_new_pad(
|
|
&self,
|
|
templ: &crate::PadTemplate,
|
|
name: Option<&str>,
|
|
caps: Option<&crate::Caps>,
|
|
) -> Option<crate::Pad> {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.request_new_pad
|
|
.map(|f| {
|
|
from_glib_none(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
templ.to_glib_none().0,
|
|
name.to_glib_full(),
|
|
caps.to_glib_none().0,
|
|
))
|
|
})
|
|
.unwrap_or(None)
|
|
}
|
|
}
|
|
|
|
fn parent_release_pad(&self, pad: &crate::Pad) {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.release_pad
|
|
.map(|f| {
|
|
f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
pad.to_glib_none().0,
|
|
)
|
|
})
|
|
.unwrap_or(())
|
|
}
|
|
}
|
|
|
|
fn parent_send_event(&self, event: Event) -> bool {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.send_event
|
|
.map(|f| {
|
|
from_glib(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
event.into_glib_ptr(),
|
|
))
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
fn parent_query(&self, query: &mut QueryRef) -> bool {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.query
|
|
.map(|f| {
|
|
from_glib(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
query.as_mut_ptr(),
|
|
))
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
fn parent_set_context(&self, context: &crate::Context) {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.set_context
|
|
.map(|f| {
|
|
f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
context.to_glib_none().0,
|
|
)
|
|
})
|
|
.unwrap_or(())
|
|
}
|
|
}
|
|
|
|
fn parent_set_clock(&self, clock: Option<&crate::Clock>) -> bool {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.set_clock
|
|
.map(|f| {
|
|
from_glib(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
clock.to_glib_none().0,
|
|
))
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
}
|
|
|
|
fn parent_provide_clock(&self) -> Option<crate::Clock> {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
(*parent_class)
|
|
.provide_clock
|
|
.map(|f| {
|
|
from_glib_none(f(self.obj().unsafe_cast_ref::<Element>().to_glib_none().0))
|
|
})
|
|
.unwrap_or(None)
|
|
}
|
|
}
|
|
|
|
fn parent_post_message(&self, msg: crate::Message) -> bool {
|
|
unsafe {
|
|
let data = Self::type_data();
|
|
let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass;
|
|
|
|
if let Some(f) = (*parent_class).post_message {
|
|
from_glib(f(
|
|
self.obj().unsafe_cast_ref::<Element>().to_glib_none().0,
|
|
msg.into_glib_ptr(),
|
|
))
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline(never)]
|
|
fn panicked(&self) -> &atomic::AtomicBool {
|
|
self.instance_data::<atomic::AtomicBool>(crate::Element::static_type())
|
|
.expect("instance not initialized correctly")
|
|
}
|
|
|
|
fn catch_panic<R, F: FnOnce(&Self) -> R, G: FnOnce() -> R>(&self, fallback: G, f: F) -> R {
|
|
panic_to_error!(self, fallback(), { f(self) })
|
|
}
|
|
|
|
fn catch_panic_future<R, F: FnOnce() -> R, G: Future<Output = R>>(
|
|
&self,
|
|
fallback: F,
|
|
fut: G,
|
|
) -> CatchPanic<Self, F, G> {
|
|
CatchPanic {
|
|
self_: self.ref_counted().downgrade(),
|
|
fallback: Some(fallback),
|
|
fut,
|
|
}
|
|
}
|
|
|
|
fn catch_panic_pad_function<R, F: FnOnce(&Self) -> R, G: FnOnce() -> R>(
|
|
parent: Option<&crate::Object>,
|
|
fallback: G,
|
|
f: F,
|
|
) -> R {
|
|
let element = parent.unwrap().dynamic_cast_ref::<Self::Type>().unwrap();
|
|
let imp = element.imp();
|
|
|
|
panic_to_error!(imp, fallback(), { f(imp) })
|
|
}
|
|
|
|
fn post_error_message(&self, msg: crate::ErrorMessage) {
|
|
unsafe {
|
|
self.obj()
|
|
.unsafe_cast_ref::<Element>()
|
|
.post_error_message(msg)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: ElementImpl> ElementImplExt for T {}
|
|
|
|
pin_project_lite::pin_project! {
|
|
#[must_use = "futures do nothing unless you `.await` or poll them"]
|
|
pub struct CatchPanic<T: glib::subclass::types::ObjectSubclass, F, G> {
|
|
self_: glib::subclass::ObjectImplWeakRef<T>,
|
|
fallback: Option<F>,
|
|
#[pin]
|
|
fut: G,
|
|
}
|
|
}
|
|
|
|
impl<R, T: ElementImpl, F: FnOnce() -> R, G: Future<Output = R>> Future for CatchPanic<T, F, G> {
|
|
type Output = R;
|
|
|
|
fn poll(
|
|
self: std::pin::Pin<&mut Self>,
|
|
cx: &mut std::task::Context<'_>,
|
|
) -> std::task::Poll<Self::Output> {
|
|
let this = self.project();
|
|
|
|
let Some(self_) = this.self_.upgrade() else {
|
|
return std::task::Poll::Ready((this
|
|
.fallback
|
|
.take()
|
|
.expect("Future polled after resolving"))(
|
|
));
|
|
};
|
|
|
|
panic_to_error!(
|
|
&*self_,
|
|
std::task::Poll::Ready(this.fallback.take().expect("Future polled after resolving")()),
|
|
{
|
|
let fut = this.fut;
|
|
fut.poll(cx)
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
unsafe impl<T: ElementImpl> IsSubclassable<T> for Element {
|
|
fn class_init(klass: &mut glib::Class<Self>) {
|
|
Self::parent_class_init::<T>(klass);
|
|
let klass = klass.as_mut();
|
|
klass.change_state = Some(element_change_state::<T>);
|
|
klass.request_new_pad = Some(element_request_new_pad::<T>);
|
|
klass.release_pad = Some(element_release_pad::<T>);
|
|
klass.send_event = Some(element_send_event::<T>);
|
|
klass.query = Some(element_query::<T>);
|
|
klass.set_context = Some(element_set_context::<T>);
|
|
klass.set_clock = Some(element_set_clock::<T>);
|
|
klass.provide_clock = Some(element_provide_clock::<T>);
|
|
klass.post_message = Some(element_post_message::<T>);
|
|
|
|
unsafe {
|
|
for pad_template in T::pad_templates() {
|
|
ffi::gst_element_class_add_pad_template(klass, pad_template.to_glib_none().0);
|
|
}
|
|
|
|
if let Some(metadata) = T::metadata() {
|
|
ffi::gst_element_class_set_metadata(
|
|
klass,
|
|
metadata.long_name.to_glib_none().0,
|
|
metadata.classification.to_glib_none().0,
|
|
metadata.description.to_glib_none().0,
|
|
metadata.author.to_glib_none().0,
|
|
);
|
|
|
|
for (key, value) in &metadata.additional[..] {
|
|
ffi::gst_element_class_add_metadata(
|
|
klass,
|
|
key.to_glib_none().0,
|
|
value.to_glib_none().0,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) {
|
|
Self::parent_instance_init::<T>(instance);
|
|
|
|
instance.set_instance_data(Self::static_type(), atomic::AtomicBool::new(false));
|
|
}
|
|
}
|
|
|
|
unsafe extern "C" fn element_change_state<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
transition: ffi::GstStateChange,
|
|
) -> ffi::GstStateChangeReturn {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
// *Never* fail downwards state changes, this causes bugs in GStreamer
|
|
// and leads to crashes and deadlocks.
|
|
let transition = from_glib(transition);
|
|
let fallback = match transition {
|
|
StateChange::PlayingToPaused | StateChange::PausedToReady | StateChange::ReadyToNull => {
|
|
StateChangeReturn::Success
|
|
}
|
|
_ => StateChangeReturn::Failure,
|
|
};
|
|
|
|
panic_to_error!(imp, fallback, { imp.change_state(transition).into() }).into_glib()
|
|
}
|
|
|
|
unsafe extern "C" fn element_request_new_pad<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
templ: *mut ffi::GstPadTemplate,
|
|
name: *const libc::c_char,
|
|
caps: *const ffi::GstCaps,
|
|
) -> *mut ffi::GstPad {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
let caps = Option::<crate::Caps>::from_glib_borrow(caps);
|
|
let name = Option::<String>::from_glib_none(name);
|
|
|
|
// XXX: This is effectively unsafe but the best we can do
|
|
// See https://bugzilla.gnome.org/show_bug.cgi?id=791193
|
|
let pad = panic_to_error!(imp, None, {
|
|
imp.request_new_pad(
|
|
&from_glib_borrow(templ),
|
|
name.as_deref(),
|
|
caps.as_ref().as_ref(),
|
|
)
|
|
});
|
|
|
|
// Ensure that the pad is owned by the element now, if a pad was returned
|
|
if let Some(ref pad) = pad {
|
|
assert_eq!(
|
|
pad.parent().as_ref(),
|
|
Some(&*crate::Object::from_glib_borrow(
|
|
ptr as *mut ffi::GstObject
|
|
))
|
|
);
|
|
}
|
|
|
|
pad.to_glib_none().0
|
|
}
|
|
|
|
unsafe extern "C" fn element_release_pad<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
pad: *mut ffi::GstPad,
|
|
) {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
// If we get a floating reference passed simply return here. It can't be stored inside this
|
|
// element, and if we continued to use it we would take ownership of this floating reference.
|
|
if glib::gobject_ffi::g_object_is_floating(pad as *mut glib::gobject_ffi::GObject)
|
|
!= glib::ffi::GFALSE
|
|
{
|
|
return;
|
|
}
|
|
|
|
panic_to_error!(imp, (), { imp.release_pad(&from_glib_none(pad)) })
|
|
}
|
|
|
|
unsafe extern "C" fn element_send_event<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
event: *mut ffi::GstEvent,
|
|
) -> glib::ffi::gboolean {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
panic_to_error!(imp, false, { imp.send_event(from_glib_full(event)) }).into_glib()
|
|
}
|
|
|
|
unsafe extern "C" fn element_query<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
query: *mut ffi::GstQuery,
|
|
) -> glib::ffi::gboolean {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
let query = QueryRef::from_mut_ptr(query);
|
|
|
|
panic_to_error!(imp, false, { imp.query(query) }).into_glib()
|
|
}
|
|
|
|
unsafe extern "C" fn element_set_context<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
context: *mut ffi::GstContext,
|
|
) {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
panic_to_error!(imp, (), { imp.set_context(&from_glib_borrow(context)) })
|
|
}
|
|
|
|
unsafe extern "C" fn element_set_clock<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
clock: *mut ffi::GstClock,
|
|
) -> glib::ffi::gboolean {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
let clock = Option::<crate::Clock>::from_glib_borrow(clock);
|
|
|
|
panic_to_error!(imp, false, { imp.set_clock(clock.as_ref().as_ref()) }).into_glib()
|
|
}
|
|
|
|
unsafe extern "C" fn element_provide_clock<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
) -> *mut ffi::GstClock {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
panic_to_error!(imp, None, { imp.provide_clock() }).into_glib_ptr()
|
|
}
|
|
|
|
unsafe extern "C" fn element_post_message<T: ElementImpl>(
|
|
ptr: *mut ffi::GstElement,
|
|
msg: *mut ffi::GstMessage,
|
|
) -> glib::ffi::gboolean {
|
|
let instance = &*(ptr as *mut T::Instance);
|
|
let imp = instance.imp();
|
|
|
|
// Can't catch panics here as posting the error message would cause
|
|
// this code to be called again recursively forever.
|
|
imp.post_message(from_glib_full(msg)).into_glib()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::sync::{atomic, Arc, Mutex, OnceLock};
|
|
|
|
use super::*;
|
|
use crate::ElementFactory;
|
|
|
|
pub mod imp {
|
|
use super::*;
|
|
|
|
pub struct TestElement {
|
|
pub(super) srcpad: crate::Pad,
|
|
pub(super) sinkpad: crate::Pad,
|
|
pub(super) n_buffers: atomic::AtomicU32,
|
|
pub(super) reached_playing: atomic::AtomicBool,
|
|
pub(super) array: Arc<Mutex<Vec<String>>>,
|
|
}
|
|
|
|
impl TestElement {
|
|
fn sink_chain(
|
|
&self,
|
|
_pad: &crate::Pad,
|
|
buffer: crate::Buffer,
|
|
) -> Result<crate::FlowSuccess, crate::FlowError> {
|
|
self.n_buffers.fetch_add(1, atomic::Ordering::SeqCst);
|
|
self.srcpad.push(buffer)
|
|
}
|
|
|
|
fn sink_event(&self, _pad: &crate::Pad, event: crate::Event) -> bool {
|
|
self.srcpad.push_event(event)
|
|
}
|
|
|
|
fn sink_query(&self, _pad: &crate::Pad, query: &mut crate::QueryRef) -> bool {
|
|
self.srcpad.peer_query(query)
|
|
}
|
|
|
|
fn src_event(&self, _pad: &crate::Pad, event: crate::Event) -> bool {
|
|
self.sinkpad.push_event(event)
|
|
}
|
|
|
|
fn src_query(&self, _pad: &crate::Pad, query: &mut crate::QueryRef) -> bool {
|
|
self.sinkpad.peer_query(query)
|
|
}
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for TestElement {
|
|
const NAME: &'static str = "TestElement";
|
|
type Type = super::TestElement;
|
|
type ParentType = Element;
|
|
|
|
fn with_class(klass: &Self::Class) -> Self {
|
|
let templ = klass.pad_template("sink").unwrap();
|
|
let sinkpad = crate::Pad::builder_from_template(&templ)
|
|
.chain_function(|pad, parent, buffer| {
|
|
TestElement::catch_panic_pad_function(
|
|
parent,
|
|
|| Err(crate::FlowError::Error),
|
|
|identity| identity.sink_chain(pad, buffer),
|
|
)
|
|
})
|
|
.event_function(|pad, parent, event| {
|
|
TestElement::catch_panic_pad_function(
|
|
parent,
|
|
|| false,
|
|
|identity| identity.sink_event(pad, event),
|
|
)
|
|
})
|
|
.query_function(|pad, parent, query| {
|
|
TestElement::catch_panic_pad_function(
|
|
parent,
|
|
|| false,
|
|
|identity| identity.sink_query(pad, query),
|
|
)
|
|
})
|
|
.build();
|
|
|
|
let templ = klass.pad_template("src").unwrap();
|
|
let srcpad = crate::Pad::builder_from_template(&templ)
|
|
.event_function(|pad, parent, event| {
|
|
TestElement::catch_panic_pad_function(
|
|
parent,
|
|
|| false,
|
|
|identity| identity.src_event(pad, event),
|
|
)
|
|
})
|
|
.query_function(|pad, parent, query| {
|
|
TestElement::catch_panic_pad_function(
|
|
parent,
|
|
|| false,
|
|
|identity| identity.src_query(pad, query),
|
|
)
|
|
})
|
|
.build();
|
|
|
|
Self {
|
|
n_buffers: atomic::AtomicU32::new(0),
|
|
reached_playing: atomic::AtomicBool::new(false),
|
|
srcpad,
|
|
sinkpad,
|
|
array: Arc::new(Mutex::new(vec![
|
|
"default0".to_string(),
|
|
"default1".to_string(),
|
|
])),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ObjectImpl for TestElement {
|
|
fn constructed(&self) {
|
|
self.parent_constructed();
|
|
|
|
let element = self.obj();
|
|
element.add_pad(&self.sinkpad).unwrap();
|
|
element.add_pad(&self.srcpad).unwrap();
|
|
}
|
|
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
static PROPERTIES: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new();
|
|
PROPERTIES.get_or_init(|| vec![crate::ParamSpecArray::builder("array").build()])
|
|
}
|
|
|
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
|
match pspec.name() {
|
|
"array" => {
|
|
let value = value.get::<crate::Array>().unwrap();
|
|
let mut array = self.array.lock().unwrap();
|
|
array.clear();
|
|
array.extend(value.iter().map(|v| v.get().unwrap()));
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
match pspec.name() {
|
|
"array" => crate::Array::new(&*self.array.lock().unwrap()).to_value(),
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GstObjectImpl for TestElement {}
|
|
|
|
impl ElementImpl for TestElement {
|
|
fn metadata() -> Option<&'static ElementMetadata> {
|
|
static ELEMENT_METADATA: std::sync::OnceLock<ElementMetadata> =
|
|
std::sync::OnceLock::new();
|
|
|
|
Some(ELEMENT_METADATA.get_or_init(|| {
|
|
ElementMetadata::new(
|
|
"Test Element",
|
|
"Generic",
|
|
"Does nothing",
|
|
"Sebastian Dröge <sebastian@centricular.com>",
|
|
)
|
|
}))
|
|
}
|
|
|
|
fn pad_templates() -> &'static [PadTemplate] {
|
|
static PAD_TEMPLATES: std::sync::OnceLock<Vec<PadTemplate>> =
|
|
std::sync::OnceLock::new();
|
|
|
|
PAD_TEMPLATES.get_or_init(|| {
|
|
let caps = crate::Caps::new_any();
|
|
vec![
|
|
PadTemplate::new(
|
|
"src",
|
|
crate::PadDirection::Src,
|
|
crate::PadPresence::Always,
|
|
&caps,
|
|
)
|
|
.unwrap(),
|
|
PadTemplate::new(
|
|
"sink",
|
|
crate::PadDirection::Sink,
|
|
crate::PadPresence::Always,
|
|
&caps,
|
|
)
|
|
.unwrap(),
|
|
]
|
|
})
|
|
}
|
|
|
|
fn change_state(
|
|
&self,
|
|
transition: crate::StateChange,
|
|
) -> Result<crate::StateChangeSuccess, crate::StateChangeError> {
|
|
let res = self.parent_change_state(transition)?;
|
|
|
|
if transition == crate::StateChange::PausedToPlaying {
|
|
self.reached_playing.store(true, atomic::Ordering::SeqCst);
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
glib::wrapper! {
|
|
pub struct TestElement(ObjectSubclass<imp::TestElement>) @extends Element, crate::Object;
|
|
}
|
|
|
|
impl TestElement {
|
|
pub fn new(name: Option<&str>) -> Self {
|
|
glib::Object::builder().property("name", name).build()
|
|
}
|
|
}
|
|
|
|
fn plugin_init(plugin: &crate::Plugin) -> Result<(), glib::BoolError> {
|
|
crate::Element::register(
|
|
Some(plugin),
|
|
"testelement",
|
|
crate::Rank::MARGINAL,
|
|
TestElement::static_type(),
|
|
)
|
|
}
|
|
|
|
crate::plugin_define!(
|
|
rssubclasstestelem,
|
|
env!("CARGO_PKG_DESCRIPTION"),
|
|
plugin_init,
|
|
env!("CARGO_PKG_VERSION"),
|
|
"MPL-2.0",
|
|
env!("CARGO_PKG_NAME"),
|
|
env!("CARGO_PKG_NAME"),
|
|
env!("CARGO_PKG_REPOSITORY"),
|
|
"1970-01-01"
|
|
);
|
|
|
|
#[test]
|
|
fn test_element_subclass() {
|
|
crate::init().unwrap();
|
|
|
|
let element = TestElement::new(Some("test"));
|
|
|
|
assert_eq!(element.name(), "test");
|
|
|
|
assert_eq!(
|
|
element.metadata(crate::ELEMENT_METADATA_LONGNAME),
|
|
Some("Test Element")
|
|
);
|
|
|
|
let pipeline = crate::Pipeline::new();
|
|
let src = ElementFactory::make("fakesrc")
|
|
.property("num-buffers", 100i32)
|
|
.build()
|
|
.unwrap();
|
|
let sink = ElementFactory::make("fakesink").build().unwrap();
|
|
|
|
pipeline
|
|
.add_many([&src, element.upcast_ref(), &sink])
|
|
.unwrap();
|
|
Element::link_many([&src, element.upcast_ref(), &sink]).unwrap();
|
|
|
|
pipeline.set_state(crate::State::Playing).unwrap();
|
|
let bus = pipeline.bus().unwrap();
|
|
|
|
let eos = bus.timed_pop_filtered(crate::ClockTime::NONE, &[crate::MessageType::Eos]);
|
|
assert!(eos.is_some());
|
|
|
|
pipeline.set_state(crate::State::Null).unwrap();
|
|
|
|
let imp = element.imp();
|
|
assert_eq!(imp.n_buffers.load(atomic::Ordering::SeqCst), 100);
|
|
assert!(imp.reached_playing.load(atomic::Ordering::SeqCst));
|
|
}
|
|
|
|
#[test]
|
|
fn property_from_iter_if_any() {
|
|
crate::init().unwrap();
|
|
plugin_register_static().unwrap();
|
|
|
|
let elem = crate::ElementFactory::make("testelement").build().unwrap();
|
|
assert!(elem
|
|
.property::<crate::Array>("array")
|
|
.iter()
|
|
.map(|val| val.get::<&str>().unwrap())
|
|
.eq(["default0", "default1"]));
|
|
|
|
let elem = crate::ElementFactory::make("testelement")
|
|
.property_from_iter::<crate::Array>("array", ["value0", "value1"])
|
|
.build()
|
|
.unwrap();
|
|
assert!(elem
|
|
.property::<crate::Array>("array")
|
|
.iter()
|
|
.map(|val| val.get::<&str>().unwrap())
|
|
.eq(["value0", "value1"]));
|
|
|
|
let array = Vec::<String>::new();
|
|
let elem = crate::ElementFactory::make("testelement")
|
|
.property_if_any::<crate::Array>("array", &array)
|
|
.build()
|
|
.unwrap();
|
|
assert!(elem
|
|
.property::<crate::Array>("array")
|
|
.iter()
|
|
.map(|val| val.get::<&str>().unwrap())
|
|
.eq(["default0", "default1"]));
|
|
|
|
let elem = crate::ElementFactory::make("testelement")
|
|
.property_if_any::<crate::Array>("array", ["value0", "value1"])
|
|
.build()
|
|
.unwrap();
|
|
assert!(elem
|
|
.property::<crate::Array>("array")
|
|
.iter()
|
|
.map(|val| val.get::<&str>().unwrap())
|
|
.eq(["value0", "value1"]));
|
|
}
|
|
}
|