| 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 | |