1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{borrow::Cow, future::Future, sync::atomic}; |
4 | |
5 | use glib::{subclass::prelude::*, translate::*}; |
6 | |
7 | use super::prelude::*; |
8 | use crate::{ |
9 | ffi, prelude::*, Element, Event, PadTemplate, QueryRef, StateChange, StateChangeError, |
10 | StateChangeReturn, StateChangeSuccess, |
11 | }; |
12 | |
13 | #[derive (Debug, Clone)] |
14 | pub struct ElementMetadata { |
15 | long_name: Cow<'static, str>, |
16 | classification: Cow<'static, str>, |
17 | description: Cow<'static, str>, |
18 | author: Cow<'static, str>, |
19 | additional: Cow<'static, [(Cow<'static, str>, Cow<'static, str>)]>, |
20 | } |
21 | |
22 | impl ElementMetadata { |
23 | pub fn new(long_name: &str, classification: &str, description: &str, author: &str) -> Self { |
24 | Self { |
25 | long_name: Cow::Owned(long_name.into()), |
26 | classification: Cow::Owned(classification.into()), |
27 | description: Cow::Owned(description.into()), |
28 | author: Cow::Owned(author.into()), |
29 | additional: Cow::Borrowed(&[]), |
30 | } |
31 | } |
32 | |
33 | pub fn with_additional( |
34 | long_name: &str, |
35 | classification: &str, |
36 | description: &str, |
37 | author: &str, |
38 | additional: &[(&str, &str)], |
39 | ) -> Self { |
40 | Self { |
41 | long_name: Cow::Owned(long_name.into()), |
42 | classification: Cow::Owned(classification.into()), |
43 | description: Cow::Owned(description.into()), |
44 | author: Cow::Owned(author.into()), |
45 | additional: additional |
46 | .iter() |
47 | .copied() |
48 | .map(|(key, value)| (Cow::Owned(key.into()), Cow::Owned(value.into()))) |
49 | .collect(), |
50 | } |
51 | } |
52 | |
53 | pub const fn with_cow( |
54 | long_name: Cow<'static, str>, |
55 | classification: Cow<'static, str>, |
56 | description: Cow<'static, str>, |
57 | author: Cow<'static, str>, |
58 | additional: Cow<'static, [(Cow<'static, str>, Cow<'static, str>)]>, |
59 | ) -> Self { |
60 | Self { |
61 | long_name, |
62 | classification, |
63 | description, |
64 | author, |
65 | additional, |
66 | } |
67 | } |
68 | } |
69 | |
70 | pub trait ElementImpl: ElementImplExt + GstObjectImpl + Send + Sync { |
71 | fn metadata() -> Option<&'static ElementMetadata> { |
72 | None |
73 | } |
74 | |
75 | fn pad_templates() -> &'static [PadTemplate] { |
76 | &[] |
77 | } |
78 | |
79 | fn change_state( |
80 | &self, |
81 | transition: StateChange, |
82 | ) -> Result<StateChangeSuccess, StateChangeError> { |
83 | self.parent_change_state(transition) |
84 | } |
85 | |
86 | fn request_new_pad( |
87 | &self, |
88 | templ: &crate::PadTemplate, |
89 | name: Option<&str>, |
90 | caps: Option<&crate::Caps>, |
91 | ) -> Option<crate::Pad> { |
92 | self.parent_request_new_pad(templ, name, caps) |
93 | } |
94 | |
95 | fn release_pad(&self, pad: &crate::Pad) { |
96 | self.parent_release_pad(pad) |
97 | } |
98 | |
99 | fn send_event(&self, event: Event) -> bool { |
100 | self.parent_send_event(event) |
101 | } |
102 | |
103 | fn query(&self, query: &mut QueryRef) -> bool { |
104 | self.parent_query(query) |
105 | } |
106 | |
107 | fn set_context(&self, context: &crate::Context) { |
108 | self.parent_set_context(context) |
109 | } |
110 | |
111 | fn set_clock(&self, clock: Option<&crate::Clock>) -> bool { |
112 | self.parent_set_clock(clock) |
113 | } |
114 | |
115 | fn provide_clock(&self) -> Option<crate::Clock> { |
116 | self.parent_provide_clock() |
117 | } |
118 | |
119 | fn post_message(&self, msg: crate::Message) -> bool { |
120 | self.parent_post_message(msg) |
121 | } |
122 | } |
123 | |
124 | mod sealed { |
125 | pub trait Sealed {} |
126 | impl<T: super::ElementImplExt> Sealed for T {} |
127 | } |
128 | |
129 | pub trait ElementImplExt: sealed::Sealed + ObjectSubclass { |
130 | fn parent_change_state( |
131 | &self, |
132 | transition: StateChange, |
133 | ) -> Result<StateChangeSuccess, StateChangeError> { |
134 | unsafe { |
135 | let data = Self::type_data(); |
136 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
137 | |
138 | let f = (*parent_class) |
139 | .change_state |
140 | .expect("Missing parent function `change_state`" ); |
141 | try_from_glib(f( |
142 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
143 | transition.into_glib(), |
144 | )) |
145 | } |
146 | } |
147 | |
148 | fn parent_request_new_pad( |
149 | &self, |
150 | templ: &crate::PadTemplate, |
151 | name: Option<&str>, |
152 | caps: Option<&crate::Caps>, |
153 | ) -> Option<crate::Pad> { |
154 | unsafe { |
155 | let data = Self::type_data(); |
156 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
157 | |
158 | (*parent_class) |
159 | .request_new_pad |
160 | .map(|f| { |
161 | from_glib_none(f( |
162 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
163 | templ.to_glib_none().0, |
164 | name.to_glib_full(), |
165 | caps.to_glib_none().0, |
166 | )) |
167 | }) |
168 | .unwrap_or(None) |
169 | } |
170 | } |
171 | |
172 | fn parent_release_pad(&self, pad: &crate::Pad) { |
173 | unsafe { |
174 | let data = Self::type_data(); |
175 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
176 | |
177 | (*parent_class) |
178 | .release_pad |
179 | .map(|f| { |
180 | f( |
181 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
182 | pad.to_glib_none().0, |
183 | ) |
184 | }) |
185 | .unwrap_or(()) |
186 | } |
187 | } |
188 | |
189 | fn parent_send_event(&self, event: Event) -> bool { |
190 | unsafe { |
191 | let data = Self::type_data(); |
192 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
193 | |
194 | (*parent_class) |
195 | .send_event |
196 | .map(|f| { |
197 | from_glib(f( |
198 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
199 | event.into_glib_ptr(), |
200 | )) |
201 | }) |
202 | .unwrap_or(false) |
203 | } |
204 | } |
205 | |
206 | fn parent_query(&self, query: &mut QueryRef) -> bool { |
207 | unsafe { |
208 | let data = Self::type_data(); |
209 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
210 | |
211 | (*parent_class) |
212 | .query |
213 | .map(|f| { |
214 | from_glib(f( |
215 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
216 | query.as_mut_ptr(), |
217 | )) |
218 | }) |
219 | .unwrap_or(false) |
220 | } |
221 | } |
222 | |
223 | fn parent_set_context(&self, context: &crate::Context) { |
224 | unsafe { |
225 | let data = Self::type_data(); |
226 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
227 | |
228 | (*parent_class) |
229 | .set_context |
230 | .map(|f| { |
231 | f( |
232 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
233 | context.to_glib_none().0, |
234 | ) |
235 | }) |
236 | .unwrap_or(()) |
237 | } |
238 | } |
239 | |
240 | fn parent_set_clock(&self, clock: Option<&crate::Clock>) -> bool { |
241 | unsafe { |
242 | let data = Self::type_data(); |
243 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
244 | |
245 | (*parent_class) |
246 | .set_clock |
247 | .map(|f| { |
248 | from_glib(f( |
249 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
250 | clock.to_glib_none().0, |
251 | )) |
252 | }) |
253 | .unwrap_or(false) |
254 | } |
255 | } |
256 | |
257 | fn parent_provide_clock(&self) -> Option<crate::Clock> { |
258 | unsafe { |
259 | let data = Self::type_data(); |
260 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
261 | |
262 | (*parent_class) |
263 | .provide_clock |
264 | .map(|f| { |
265 | from_glib_none(f(self.obj().unsafe_cast_ref::<Element>().to_glib_none().0)) |
266 | }) |
267 | .unwrap_or(None) |
268 | } |
269 | } |
270 | |
271 | fn parent_post_message(&self, msg: crate::Message) -> bool { |
272 | unsafe { |
273 | let data = Self::type_data(); |
274 | let parent_class = data.as_ref().parent_class() as *mut ffi::GstElementClass; |
275 | |
276 | if let Some(f) = (*parent_class).post_message { |
277 | from_glib(f( |
278 | self.obj().unsafe_cast_ref::<Element>().to_glib_none().0, |
279 | msg.into_glib_ptr(), |
280 | )) |
281 | } else { |
282 | false |
283 | } |
284 | } |
285 | } |
286 | |
287 | #[inline (never)] |
288 | fn panicked(&self) -> &atomic::AtomicBool { |
289 | #[cfg (panic = "abort" )] |
290 | { |
291 | static DUMMY: atomic::AtomicBool = atomic::AtomicBool::new(false); |
292 | &DUMMY |
293 | } |
294 | #[cfg (not(panic = "abort" ))] |
295 | { |
296 | self.instance_data::<atomic::AtomicBool>(crate::Element::static_type()) |
297 | .expect("instance not initialized correctly" ) |
298 | } |
299 | } |
300 | |
301 | fn catch_panic<R, F: FnOnce(&Self) -> R, G: FnOnce() -> R>(&self, fallback: G, f: F) -> R { |
302 | panic_to_error!(self, fallback(), { f(self) }) |
303 | } |
304 | |
305 | fn catch_panic_future<R, F: FnOnce() -> R, G: Future<Output = R>>( |
306 | &self, |
307 | fallback: F, |
308 | fut: G, |
309 | ) -> CatchPanic<Self, F, G> { |
310 | CatchPanic { |
311 | self_: self.ref_counted().downgrade(), |
312 | fallback: Some(fallback), |
313 | fut, |
314 | } |
315 | } |
316 | |
317 | fn catch_panic_pad_function<R, F: FnOnce(&Self) -> R, G: FnOnce() -> R>( |
318 | parent: Option<&crate::Object>, |
319 | fallback: G, |
320 | f: F, |
321 | ) -> R { |
322 | let element = parent.unwrap().dynamic_cast_ref::<Self::Type>().unwrap(); |
323 | let imp = element.imp(); |
324 | |
325 | panic_to_error!(imp, fallback(), { f(imp) }) |
326 | } |
327 | |
328 | fn post_error_message(&self, msg: crate::ErrorMessage) { |
329 | unsafe { |
330 | self.obj() |
331 | .unsafe_cast_ref::<Element>() |
332 | .post_error_message(msg) |
333 | } |
334 | } |
335 | } |
336 | |
337 | impl<T: ElementImpl> ElementImplExt for T {} |
338 | |
339 | pin_project_lite::pin_project! { |
340 | #[must_use = "futures do nothing unless you `.await` or poll them" ] |
341 | pub struct CatchPanic<T: glib::subclass::types::ObjectSubclass, F, G> { |
342 | self_: glib::subclass::ObjectImplWeakRef<T>, |
343 | fallback: Option<F>, |
344 | #[pin] |
345 | fut: G, |
346 | } |
347 | } |
348 | |
349 | impl<R, T: ElementImpl, F: FnOnce() -> R, G: Future<Output = R>> Future for CatchPanic<T, F, G> { |
350 | type Output = R; |
351 | |
352 | fn poll( |
353 | self: std::pin::Pin<&mut Self>, |
354 | cx: &mut std::task::Context<'_>, |
355 | ) -> std::task::Poll<Self::Output> { |
356 | let this = self.project(); |
357 | |
358 | let Some(self_) = this.self_.upgrade() else { |
359 | return std::task::Poll::Ready((this |
360 | .fallback |
361 | .take() |
362 | .expect("Future polled after resolving" ))( |
363 | )); |
364 | }; |
365 | |
366 | panic_to_error!( |
367 | &*self_, |
368 | std::task::Poll::Ready(this.fallback.take().expect("Future polled after resolving" )()), |
369 | { |
370 | let fut = this.fut; |
371 | fut.poll(cx) |
372 | } |
373 | ) |
374 | } |
375 | } |
376 | |
377 | unsafe impl<T: ElementImpl> IsSubclassable<T> for Element { |
378 | fn class_init(klass: &mut glib::Class<Self>) { |
379 | Self::parent_class_init::<T>(klass); |
380 | let klass = klass.as_mut(); |
381 | klass.change_state = Some(element_change_state::<T>); |
382 | klass.request_new_pad = Some(element_request_new_pad::<T>); |
383 | klass.release_pad = Some(element_release_pad::<T>); |
384 | klass.send_event = Some(element_send_event::<T>); |
385 | klass.query = Some(element_query::<T>); |
386 | klass.set_context = Some(element_set_context::<T>); |
387 | klass.set_clock = Some(element_set_clock::<T>); |
388 | klass.provide_clock = Some(element_provide_clock::<T>); |
389 | klass.post_message = Some(element_post_message::<T>); |
390 | |
391 | unsafe { |
392 | for pad_template in T::pad_templates() { |
393 | ffi::gst_element_class_add_pad_template(klass, pad_template.to_glib_none().0); |
394 | } |
395 | |
396 | if let Some(metadata) = T::metadata() { |
397 | ffi::gst_element_class_set_metadata( |
398 | klass, |
399 | metadata.long_name.to_glib_none().0, |
400 | metadata.classification.to_glib_none().0, |
401 | metadata.description.to_glib_none().0, |
402 | metadata.author.to_glib_none().0, |
403 | ); |
404 | |
405 | for (key, value) in &metadata.additional[..] { |
406 | ffi::gst_element_class_add_metadata( |
407 | klass, |
408 | key.to_glib_none().0, |
409 | value.to_glib_none().0, |
410 | ); |
411 | } |
412 | } |
413 | } |
414 | } |
415 | |
416 | fn instance_init(instance: &mut glib::subclass::InitializingObject<T>) { |
417 | Self::parent_instance_init::<T>(instance); |
418 | |
419 | #[cfg (not(panic = "abort" ))] |
420 | instance.set_instance_data(Self::static_type(), atomic::AtomicBool::new(false)); |
421 | } |
422 | } |
423 | |
424 | unsafe extern "C" fn element_change_state<T: ElementImpl>( |
425 | ptr: *mut ffi::GstElement, |
426 | transition: ffi::GstStateChange, |
427 | ) -> ffi::GstStateChangeReturn { |
428 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
429 | let imp: &T = instance.imp(); |
430 | |
431 | // *Never* fail downwards state changes, this causes bugs in GStreamer |
432 | // and leads to crashes and deadlocks. |
433 | let transition: StateChange = from_glib(val:transition); |
434 | let fallback: StateChangeReturn = match transition { |
435 | StateChange::PlayingToPaused | StateChange::PausedToReady | StateChange::ReadyToNull => { |
436 | StateChangeReturn::Success |
437 | } |
438 | _ => StateChangeReturn::Failure, |
439 | }; |
440 | |
441 | panic_to_errorStateChangeReturn!(imp, fallback, { |
442 | StateChangeReturn::from(imp.change_state(transition)) |
443 | }) |
444 | .into_glib() |
445 | } |
446 | |
447 | unsafe extern "C" fn element_request_new_pad<T: ElementImpl>( |
448 | ptr: *mut ffi::GstElement, |
449 | templ: *mut ffi::GstPadTemplate, |
450 | name: *const libc::c_char, |
451 | caps: *const ffi::GstCaps, |
452 | ) -> *mut ffi::GstPad { |
453 | let instance = &*(ptr as *mut T::Instance); |
454 | let imp = instance.imp(); |
455 | |
456 | let caps = Option::<crate::Caps>::from_glib_borrow(caps); |
457 | let name = Option::<String>::from_glib_none(name); |
458 | |
459 | // XXX: This is effectively unsafe but the best we can do |
460 | // See https://bugzilla.gnome.org/show_bug.cgi?id=791193 |
461 | let pad = panic_to_error!(imp, None, { |
462 | imp.request_new_pad( |
463 | &from_glib_borrow(templ), |
464 | name.as_deref(), |
465 | caps.as_ref().as_ref(), |
466 | ) |
467 | }); |
468 | |
469 | // Ensure that the pad is owned by the element now, if a pad was returned |
470 | if let Some(ref pad) = pad { |
471 | assert_eq!( |
472 | pad.parent().as_ref(), |
473 | Some(&*crate::Object::from_glib_borrow( |
474 | ptr as *mut ffi::GstObject |
475 | )) |
476 | ); |
477 | } |
478 | |
479 | pad.to_glib_none().0 |
480 | } |
481 | |
482 | unsafe extern "C" fn element_release_pad<T: ElementImpl>( |
483 | ptr: *mut ffi::GstElement, |
484 | pad: *mut ffi::GstPad, |
485 | ) { |
486 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
487 | let imp: &T = instance.imp(); |
488 | |
489 | // If we get a floating reference passed simply return here. It can't be stored inside this |
490 | // element, and if we continued to use it we would take ownership of this floating reference. |
491 | if glib::gobject_ffi::g_object_is_floating(object:pad as *mut glib::gobject_ffi::GObject) |
492 | != glib::ffi::GFALSE |
493 | { |
494 | return; |
495 | } |
496 | |
497 | panic_to_error!(imp, (), { imp.release_pad(&from_glib_none(pad)) }) |
498 | } |
499 | |
500 | unsafe extern "C" fn element_send_event<T: ElementImpl>( |
501 | ptr: *mut ffi::GstElement, |
502 | event: *mut ffi::GstEvent, |
503 | ) -> glib::ffi::gboolean { |
504 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
505 | let imp: &T = instance.imp(); |
506 | |
507 | panic_to_error!(imp, false, { imp.send_event(from_glib_full(event)) }).into_glib() |
508 | } |
509 | |
510 | unsafe extern "C" fn element_query<T: ElementImpl>( |
511 | ptr: *mut ffi::GstElement, |
512 | query: *mut ffi::GstQuery, |
513 | ) -> glib::ffi::gboolean { |
514 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
515 | let imp: &T = instance.imp(); |
516 | let query: &mut QueryRef = QueryRef::from_mut_ptr(query); |
517 | |
518 | panic_to_error!(imp, false, { imp.query(query) }).into_glib() |
519 | } |
520 | |
521 | unsafe extern "C" fn element_set_context<T: ElementImpl>( |
522 | ptr: *mut ffi::GstElement, |
523 | context: *mut ffi::GstContext, |
524 | ) { |
525 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
526 | let imp: &T = instance.imp(); |
527 | |
528 | panic_to_error!(imp, (), { imp.set_context(&from_glib_borrow(context)) }) |
529 | } |
530 | |
531 | unsafe extern "C" fn element_set_clock<T: ElementImpl>( |
532 | ptr: *mut ffi::GstElement, |
533 | clock: *mut ffi::GstClock, |
534 | ) -> glib::ffi::gboolean { |
535 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
536 | let imp: &T = instance.imp(); |
537 | |
538 | let clock: Borrowed = Option::<crate::Clock>::from_glib_borrow(_ptr:clock); |
539 | |
540 | panic_to_error!(imp, false, { imp.set_clock(clock.as_ref().as_ref()) }).into_glib() |
541 | } |
542 | |
543 | unsafe extern "C" fn element_provide_clock<T: ElementImpl>( |
544 | ptr: *mut ffi::GstElement, |
545 | ) -> *mut ffi::GstClock { |
546 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
547 | let imp: &T = instance.imp(); |
548 | |
549 | panic_to_error!(imp, None, { imp.provide_clock() }).into_glib_ptr() |
550 | } |
551 | |
552 | unsafe extern "C" fn element_post_message<T: ElementImpl>( |
553 | ptr: *mut ffi::GstElement, |
554 | msg: *mut ffi::GstMessage, |
555 | ) -> glib::ffi::gboolean { |
556 | let instance: &::Instance = &*(ptr as *mut T::Instance); |
557 | let imp: &T = instance.imp(); |
558 | |
559 | // Can't catch panics here as posting the error message would cause |
560 | // this code to be called again recursively forever. |
561 | imp.post_message(msg:from_glib_full(ptr:msg)).into_glib() |
562 | } |
563 | |
564 | #[cfg (test)] |
565 | mod tests { |
566 | use std::sync::{atomic, Arc, Mutex, OnceLock}; |
567 | |
568 | use super::*; |
569 | use crate::ElementFactory; |
570 | |
571 | pub mod imp { |
572 | use super::*; |
573 | |
574 | pub struct TestElement { |
575 | pub(super) srcpad: crate::Pad, |
576 | pub(super) sinkpad: crate::Pad, |
577 | pub(super) n_buffers: atomic::AtomicU32, |
578 | pub(super) reached_playing: atomic::AtomicBool, |
579 | pub(super) array: Arc<Mutex<Vec<String>>>, |
580 | } |
581 | |
582 | impl TestElement { |
583 | fn sink_chain( |
584 | &self, |
585 | _pad: &crate::Pad, |
586 | buffer: crate::Buffer, |
587 | ) -> Result<crate::FlowSuccess, crate::FlowError> { |
588 | self.n_buffers.fetch_add(1, atomic::Ordering::SeqCst); |
589 | self.srcpad.push(buffer) |
590 | } |
591 | |
592 | fn sink_event(&self, _pad: &crate::Pad, event: crate::Event) -> bool { |
593 | self.srcpad.push_event(event) |
594 | } |
595 | |
596 | fn sink_query(&self, _pad: &crate::Pad, query: &mut crate::QueryRef) -> bool { |
597 | self.srcpad.peer_query(query) |
598 | } |
599 | |
600 | fn src_event(&self, _pad: &crate::Pad, event: crate::Event) -> bool { |
601 | self.sinkpad.push_event(event) |
602 | } |
603 | |
604 | fn src_query(&self, _pad: &crate::Pad, query: &mut crate::QueryRef) -> bool { |
605 | self.sinkpad.peer_query(query) |
606 | } |
607 | } |
608 | |
609 | #[glib::object_subclass ] |
610 | impl ObjectSubclass for TestElement { |
611 | const NAME: &'static str = "TestElement" ; |
612 | type Type = super::TestElement; |
613 | type ParentType = Element; |
614 | |
615 | fn with_class(klass: &Self::Class) -> Self { |
616 | let templ = klass.pad_template("sink" ).unwrap(); |
617 | let sinkpad = crate::Pad::builder_from_template(&templ) |
618 | .chain_function(|pad, parent, buffer| { |
619 | TestElement::catch_panic_pad_function( |
620 | parent, |
621 | || Err(crate::FlowError::Error), |
622 | |identity| identity.sink_chain(pad, buffer), |
623 | ) |
624 | }) |
625 | .event_function(|pad, parent, event| { |
626 | TestElement::catch_panic_pad_function( |
627 | parent, |
628 | || false, |
629 | |identity| identity.sink_event(pad, event), |
630 | ) |
631 | }) |
632 | .query_function(|pad, parent, query| { |
633 | TestElement::catch_panic_pad_function( |
634 | parent, |
635 | || false, |
636 | |identity| identity.sink_query(pad, query), |
637 | ) |
638 | }) |
639 | .build(); |
640 | |
641 | let templ = klass.pad_template("src" ).unwrap(); |
642 | let srcpad = crate::Pad::builder_from_template(&templ) |
643 | .event_function(|pad, parent, event| { |
644 | TestElement::catch_panic_pad_function( |
645 | parent, |
646 | || false, |
647 | |identity| identity.src_event(pad, event), |
648 | ) |
649 | }) |
650 | .query_function(|pad, parent, query| { |
651 | TestElement::catch_panic_pad_function( |
652 | parent, |
653 | || false, |
654 | |identity| identity.src_query(pad, query), |
655 | ) |
656 | }) |
657 | .build(); |
658 | |
659 | Self { |
660 | n_buffers: atomic::AtomicU32::new(0), |
661 | reached_playing: atomic::AtomicBool::new(false), |
662 | array: Arc::new(Mutex::new(vec![ |
663 | "default0" .to_string(), |
664 | "default1" .to_string(), |
665 | ])), |
666 | srcpad, |
667 | sinkpad, |
668 | } |
669 | } |
670 | } |
671 | |
672 | impl ObjectImpl for TestElement { |
673 | fn constructed(&self) { |
674 | self.parent_constructed(); |
675 | |
676 | let element = self.obj(); |
677 | element.add_pad(&self.sinkpad).unwrap(); |
678 | element.add_pad(&self.srcpad).unwrap(); |
679 | } |
680 | |
681 | fn properties() -> &'static [glib::ParamSpec] { |
682 | static PROPERTIES: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new(); |
683 | PROPERTIES.get_or_init(|| vec![crate::ParamSpecArray::builder("array" ).build()]) |
684 | } |
685 | |
686 | fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { |
687 | match pspec.name() { |
688 | "array" => { |
689 | let value = value.get::<crate::Array>().unwrap(); |
690 | let mut array = self.array.lock().unwrap(); |
691 | array.clear(); |
692 | array.extend(value.iter().map(|v| v.get().unwrap())); |
693 | } |
694 | _ => unimplemented!(), |
695 | } |
696 | } |
697 | |
698 | fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
699 | match pspec.name() { |
700 | "array" => crate::Array::new(&*self.array.lock().unwrap()).to_value(), |
701 | _ => unimplemented!(), |
702 | } |
703 | } |
704 | } |
705 | |
706 | impl GstObjectImpl for TestElement {} |
707 | |
708 | impl ElementImpl for TestElement { |
709 | fn metadata() -> Option<&'static ElementMetadata> { |
710 | static ELEMENT_METADATA: std::sync::OnceLock<ElementMetadata> = |
711 | std::sync::OnceLock::new(); |
712 | |
713 | Some(ELEMENT_METADATA.get_or_init(|| { |
714 | ElementMetadata::new( |
715 | "Test Element" , |
716 | "Generic" , |
717 | "Does nothing" , |
718 | "Sebastian Dröge <sebastian@centricular.com>" , |
719 | ) |
720 | })) |
721 | } |
722 | |
723 | fn pad_templates() -> &'static [PadTemplate] { |
724 | static PAD_TEMPLATES: std::sync::OnceLock<Vec<PadTemplate>> = |
725 | std::sync::OnceLock::new(); |
726 | |
727 | PAD_TEMPLATES.get_or_init(|| { |
728 | let caps = crate::Caps::new_any(); |
729 | vec![ |
730 | PadTemplate::new( |
731 | "src" , |
732 | crate::PadDirection::Src, |
733 | crate::PadPresence::Always, |
734 | &caps, |
735 | ) |
736 | .unwrap(), |
737 | PadTemplate::new( |
738 | "sink" , |
739 | crate::PadDirection::Sink, |
740 | crate::PadPresence::Always, |
741 | &caps, |
742 | ) |
743 | .unwrap(), |
744 | ] |
745 | }) |
746 | } |
747 | |
748 | fn change_state( |
749 | &self, |
750 | transition: crate::StateChange, |
751 | ) -> Result<crate::StateChangeSuccess, crate::StateChangeError> { |
752 | let res = self.parent_change_state(transition)?; |
753 | |
754 | if transition == crate::StateChange::PausedToPlaying { |
755 | self.reached_playing.store(true, atomic::Ordering::SeqCst); |
756 | } |
757 | |
758 | Ok(res) |
759 | } |
760 | } |
761 | } |
762 | |
763 | glib::wrapper! { |
764 | pub struct TestElement(ObjectSubclass<imp::TestElement>) @extends Element, crate::Object; |
765 | } |
766 | |
767 | impl TestElement { |
768 | pub fn new(name: Option<&str>) -> Self { |
769 | glib::Object::builder().property("name" , name).build() |
770 | } |
771 | } |
772 | |
773 | fn plugin_init(plugin: &crate::Plugin) -> Result<(), glib::BoolError> { |
774 | crate::Element::register( |
775 | Some(plugin), |
776 | "testelement" , |
777 | crate::Rank::MARGINAL, |
778 | TestElement::static_type(), |
779 | ) |
780 | } |
781 | |
782 | crate::plugin_define!( |
783 | rssubclasstestelem, |
784 | env!("CARGO_PKG_DESCRIPTION" ), |
785 | plugin_init, |
786 | env!("CARGO_PKG_VERSION" ), |
787 | "MPL-2.0" , |
788 | env!("CARGO_PKG_NAME" ), |
789 | env!("CARGO_PKG_NAME" ), |
790 | env!("CARGO_PKG_REPOSITORY" ), |
791 | "1970-01-01" |
792 | ); |
793 | |
794 | fn init() { |
795 | use std::sync::Once; |
796 | static INIT: Once = Once::new(); |
797 | |
798 | INIT.call_once(|| { |
799 | crate::init().unwrap(); |
800 | plugin_register_static().expect("gstreamer subclass element test" ); |
801 | }); |
802 | } |
803 | |
804 | #[test ] |
805 | fn test_element_subclass() { |
806 | init(); |
807 | |
808 | let element = TestElement::new(Some("test" )); |
809 | |
810 | assert_eq!(element.name(), "test" ); |
811 | |
812 | assert_eq!( |
813 | element.metadata(crate::ELEMENT_METADATA_LONGNAME), |
814 | Some("Test Element" ) |
815 | ); |
816 | |
817 | let pipeline = crate::Pipeline::new(); |
818 | let src = ElementFactory::make("fakesrc" ) |
819 | .property("num-buffers" , 100i32) |
820 | .build() |
821 | .unwrap(); |
822 | let sink = ElementFactory::make("fakesink" ).build().unwrap(); |
823 | |
824 | pipeline |
825 | .add_many([&src, element.upcast_ref(), &sink]) |
826 | .unwrap(); |
827 | Element::link_many([&src, element.upcast_ref(), &sink]).unwrap(); |
828 | |
829 | pipeline.set_state(crate::State::Playing).unwrap(); |
830 | let bus = pipeline.bus().unwrap(); |
831 | |
832 | let eos = bus.timed_pop_filtered(crate::ClockTime::NONE, &[crate::MessageType::Eos]); |
833 | assert!(eos.is_some()); |
834 | |
835 | pipeline.set_state(crate::State::Null).unwrap(); |
836 | |
837 | let imp = element.imp(); |
838 | assert_eq!(imp.n_buffers.load(atomic::Ordering::SeqCst), 100); |
839 | assert!(imp.reached_playing.load(atomic::Ordering::SeqCst)); |
840 | } |
841 | |
842 | #[test ] |
843 | fn property_from_iter_if_not_empty() { |
844 | init(); |
845 | |
846 | let elem = crate::ElementFactory::make("testelement" ).build().unwrap(); |
847 | assert!(elem |
848 | .property::<crate::Array>("array" ) |
849 | .iter() |
850 | .map(|val| val.get::<&str>().unwrap()) |
851 | .eq(["default0" , "default1" ])); |
852 | |
853 | let elem = crate::ElementFactory::make("testelement" ) |
854 | .property_from_iter::<crate::Array>("array" , ["value0" , "value1" ]) |
855 | .build() |
856 | .unwrap(); |
857 | assert!(elem |
858 | .property::<crate::Array>("array" ) |
859 | .iter() |
860 | .map(|val| val.get::<&str>().unwrap()) |
861 | .eq(["value0" , "value1" ])); |
862 | |
863 | let array = Vec::<String>::new(); |
864 | let elem = crate::ElementFactory::make("testelement" ) |
865 | .property_if_not_empty::<crate::Array>("array" , &array) |
866 | .build() |
867 | .unwrap(); |
868 | assert!(elem |
869 | .property::<crate::Array>("array" ) |
870 | .iter() |
871 | .map(|val| val.get::<&str>().unwrap()) |
872 | .eq(["default0" , "default1" ])); |
873 | |
874 | let elem = crate::ElementFactory::make("testelement" ) |
875 | .property_if_not_empty::<crate::Array>("array" , ["value0" , "value1" ]) |
876 | .build() |
877 | .unwrap(); |
878 | assert!(elem |
879 | .property::<crate::Array>("array" ) |
880 | .iter() |
881 | .map(|val| val.get::<&str>().unwrap()) |
882 | .eq(["value0" , "value1" ])); |
883 | } |
884 | } |
885 | |