1// Take a look at the license at the top of the repository in the LICENSE file.
2
3#[cfg(not(feature = "v1_26"))]
4use {crate::value::GstValueExt, std::str::FromStr};
5
6use glib::{prelude::*, subclass::prelude::*, translate::*};
7
8use super::prelude::*;
9use crate::{
10 ffi, Bin, Buffer, BufferList, Element, Event, FlowError, FlowSuccess, Message, MiniObject,
11 Object, Pad, PadLinkError, PadLinkSuccess, QueryRef, StateChange, StateChangeError,
12 StateChangeSuccess, Tracer,
13};
14
15#[allow(unused_variables)]
16pub trait TracerImpl: TracerImplExt + GstObjectImpl + Send + Sync {
17 // rustdoc-stripper-ignore-next
18 /// Whether to use `gst::Structure` style "params" and automatically pass
19 /// them to the corresponding properties during instantiation.
20 const USE_STRUCTURE_PARAMS: bool = false;
21
22 fn bin_add_post(&self, ts: u64, bin: &Bin, element: &Element, success: bool) {}
23 fn bin_add_pre(&self, ts: u64, bin: &Bin, element: &Element) {}
24 fn bin_remove_post(&self, ts: u64, bin: &Bin, success: bool) {}
25 fn bin_remove_pre(&self, ts: u64, bin: &Bin, element: &Element) {}
26 fn element_new(&self, ts: u64, element: &Element) {}
27 fn element_add_pad(&self, ts: u64, element: &Element, pad: &Pad) {}
28 fn element_remove_pad(&self, ts: u64, element: &Element, pad: &Pad) {}
29 fn element_change_state_post(
30 &self,
31 ts: u64,
32 element: &Element,
33 change: StateChange,
34 result: Result<StateChangeSuccess, StateChangeError>,
35 ) {
36 }
37 fn element_change_state_pre(&self, ts: u64, element: &Element, change: StateChange) {}
38 fn element_post_message_post(&self, ts: u64, element: &Element, success: bool) {}
39 fn element_post_message_pre(&self, ts: u64, element: &Element, message: &Message) {}
40 fn element_query_post(&self, ts: u64, element: &Element, query: &QueryRef, success: bool) {}
41 fn element_query_pre(&self, ts: u64, element: &Element, query: &QueryRef) {}
42 // rustdoc-stripper-ignore-next
43 /// Hook to be called before the GstMiniObject has been fully initialized.
44 fn mini_object_created(&self, ts: u64, object: std::ptr::NonNull<ffi::GstMiniObject>) {}
45 // rustdoc-stripper-ignore-next
46 /// Hook to be called after the GstMiniObject has been finalized.
47 fn mini_object_destroyed(&self, ts: u64, object: std::ptr::NonNull<ffi::GstMiniObject>) {}
48 fn mini_object_reffed(&self, ts: u64, object: &MiniObject, new_refcount: i32) {}
49 fn mini_object_unreffed(&self, ts: u64, object: &MiniObject, new_refcount: i32) {}
50 fn object_created(&self, ts: u64, object: &Object) {}
51 // rustdoc-stripper-ignore-next
52 /// Hook to be called after the GstObject has been finalized.
53 fn object_destroyed(&self, ts: u64, object: std::ptr::NonNull<ffi::GstObject>) {}
54 fn object_reffed(&self, ts: u64, object: &Object, new_refcount: i32) {}
55 fn object_unreffed(&self, ts: u64, object: &Object, new_refcount: i32) {}
56 fn pad_link_post(
57 &self,
58 ts: u64,
59 src: &Pad,
60 sink: &Pad,
61 result: Result<PadLinkSuccess, PadLinkError>,
62 ) {
63 }
64 fn pad_link_pre(&self, ts: u64, src: &Pad, sink: &Pad) {}
65 fn pad_pull_range_post(&self, ts: u64, pad: &Pad, result: Result<&Buffer, FlowError>) {}
66 fn pad_pull_range_pre(&self, ts: u64, pad: &Pad, offset: u64, size: u32) {}
67 fn pad_push_event_post(&self, ts: u64, pad: &Pad, success: bool) {}
68 fn pad_push_event_pre(&self, ts: u64, pad: &Pad, event: &Event) {}
69 #[cfg(feature = "v1_22")]
70 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
71 fn pad_chain_list_post(&self, ts: u64, pad: &Pad, result: Result<FlowSuccess, FlowError>) {}
72 #[cfg(feature = "v1_22")]
73 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
74 fn pad_chain_list_pre(&self, ts: u64, pad: &Pad, buffer_list: &BufferList) {}
75 #[cfg(feature = "v1_22")]
76 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
77 fn pad_chain_post(&self, ts: u64, pad: &Pad, result: Result<FlowSuccess, FlowError>) {}
78 #[cfg(feature = "v1_22")]
79 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
80 fn pad_chain_pre(&self, ts: u64, pad: &Pad, buffer: &Buffer) {}
81 fn pad_push_list_post(&self, ts: u64, pad: &Pad, result: Result<FlowSuccess, FlowError>) {}
82 fn pad_push_list_pre(&self, ts: u64, pad: &Pad, buffer_list: &BufferList) {}
83 fn pad_push_post(&self, ts: u64, pad: &Pad, result: Result<FlowSuccess, FlowError>) {}
84 fn pad_push_pre(&self, ts: u64, pad: &Pad, buffer: &Buffer) {}
85 fn pad_query_post(&self, ts: u64, pad: &Pad, query: &QueryRef, success: bool) {}
86 fn pad_query_pre(&self, ts: u64, pad: &Pad, query: &QueryRef) {}
87 fn pad_unlink_post(&self, ts: u64, src: &Pad, sink: &Pad, success: bool) {}
88 fn pad_unlink_pre(&self, ts: u64, src: &Pad, sink: &Pad) {}
89 #[cfg(feature = "v1_20")]
90 #[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
91 fn plugin_feature_loaded(&self, ts: u64, feature: &crate::PluginFeature) {}
92}
93
94#[cfg(not(feature = "v1_26"))]
95fn format_available_properties(class: &glib::object::ObjectClass) -> String {
96 class
97 .list_properties()
98 .iter()
99 .filter(|p| {
100 p.flags().contains(glib::ParamFlags::WRITABLE)
101 && p.name() != "parent"
102 && p.name() != "params"
103 })
104 .map(|p| format!(" {}: {}", p.name(), p.blurb().map_or("", |b| b)))
105 .collect::<Vec<_>>()
106 .join(sep:"\n")
107}
108
109#[cfg(not(feature = "v1_26"))]
110fn emit_property_warning(obj: &glib::Object, msg: &str) {
111 let props: String = format_available_properties(obj.class());
112 glib::g_warning!("gsttracer", "{}\nAvailable properties:\n{}", msg, props);
113}
114
115unsafe impl<T: TracerImpl> IsSubclassable<T> for Tracer {
116 fn class_init(class: &mut glib::Class<Self>) {
117 Self::parent_class_init::<T>(class);
118
119 #[cfg(feature = "v1_26")]
120 {
121 let class = class.as_mut();
122 unsafe {
123 ffi::gst_tracer_class_set_use_structure_params(
124 class,
125 T::USE_STRUCTURE_PARAMS.into_glib(),
126 );
127 }
128 }
129 #[cfg(not(feature = "v1_26"))]
130 unsafe {
131 if T::USE_STRUCTURE_PARAMS {
132 use std::sync::OnceLock;
133 static TRACER_CONSTRUCTED_FUNC: OnceLock<
134 unsafe extern "C" fn(*mut glib::gobject_ffi::GObject),
135 > = OnceLock::new();
136
137 let class =
138 &mut *(class.as_mut() as *mut _ as *mut glib::gobject_ffi::GObjectClass);
139 TRACER_CONSTRUCTED_FUNC.get_or_init(|| class.constructed.unwrap());
140 unsafe extern "C" fn constructed(objptr: *mut glib::gobject_ffi::GObject) {
141 let obj = glib::Object::from_glib_borrow(objptr);
142 let params = obj.property::<Option<String>>("params");
143
144 let Some(params) = params else {
145 TRACER_CONSTRUCTED_FUNC.get().unwrap()(objptr);
146
147 return;
148 };
149
150 if params.is_empty() {
151 TRACER_CONSTRUCTED_FUNC.get().unwrap()(objptr);
152
153 return;
154 }
155
156 let s = match crate::Structure::from_str(&format!("tracer-settings,{}", params))
157 {
158 Ok(s) => s,
159 Err(err) => {
160 emit_property_warning(
161 &obj,
162 &format!(
163 "Can't setup tracer {err:?}: invalid parameters '{params}'"
164 ),
165 );
166 return;
167 }
168 };
169
170 let class = obj.class();
171
172 for (field, field_value) in s.iter() {
173 let pspec = match class.find_property(field.as_str()) {
174 Some(p) => p,
175 None => {
176 emit_property_warning(
177 &obj,
178 &format!(
179 "Can't setup tracer: property '{}' not found",
180 field.as_str()
181 ),
182 );
183 return;
184 }
185 };
186
187 let value = if field_value.type_() == pspec.value_type() {
188 field_value.to_value()
189 } else if field_value.type_() == glib::types::Type::STRING {
190 let str_val = field_value.get::<String>().unwrap();
191 #[cfg(feature = "v1_20")]
192 {
193 match glib::Value::deserialize_with_pspec(&str_val, &pspec) {
194 Ok(v) => v,
195 Err(_) => {
196 emit_property_warning(&obj, &format!("Can't instantiate tracer: invalid property '{}' value: '{}'", field.as_str(), str_val));
197 return;
198 }
199 }
200 }
201 #[cfg(not(feature = "v1_20"))]
202 {
203 match glib::Value::deserialize(&str_val, pspec.value_type()) {
204 Ok(v) => v,
205 Err(_) => {
206 emit_property_warning(&obj, &format!("Can't instantiate tracer: invalid property '{}' value: '{}'", field.as_str(), str_val));
207 return;
208 }
209 }
210 }
211 } else {
212 emit_property_warning(&obj, &format!(
213 "Can't setup tracer: property '{}' type mismatch, expected {}, got {}",
214 field.as_str(),
215 pspec.value_type().name(),
216 field_value.type_().name()
217 ));
218 return;
219 };
220
221 crate::debug!(crate::CAT_RUST, "Setting property {field:?}");
222 obj.set_property(field.as_str(), &value);
223 }
224
225 TRACER_CONSTRUCTED_FUNC.get().unwrap()(objptr);
226 }
227
228 class.constructed = Some(constructed);
229 }
230 }
231 }
232}
233
234pub trait TracerImplExt: ObjectSubclass {
235 // rustdoc-stripper-ignore-next
236 /// Register a corresponding hook to be called for this tracer when certain events occur.
237 ///
238 /// Upon an event a corresponding method in `TracerImpl` will be called.
239 fn register_hook(&self, hook: TracerHook);
240}
241
242macro_rules! define_tracer_hooks {
243 ($($(#[$attr:meta])* $name: ident($quark: literal) = |$this: ident, $ts: ident, $($cb_arg: ident: $cb_arg_ty: ty),*| $impl: block;)*) => {
244 pub enum TracerHook {
245 $($(#[$attr])* $name),*
246 }
247 impl<T: TracerImpl> TracerImplExt for T {
248 fn register_hook(&self, hook: TracerHook) {
249 use TracerHook::*;
250 let (hook_type, callback) = match hook {
251 $($(#[$attr])* $name => {
252 #[allow(non_snake_case)]
253 unsafe extern "C" fn callback<T: TracerImpl>(
254 $this: *mut ffi::GstTracer,
255 $ts: u64,
256 $($cb_arg: $cb_arg_ty),*
257 ) {
258 let $this = Tracer::from_glib_borrow($this);
259 let $this = T::from_obj($this.unsafe_cast_ref());
260 $impl
261 }
262 (
263 concat!($quark, "\0"),
264 callback::<T> as unsafe extern "C" fn(_, _, $($cb_arg_ty),*) as *const ()
265 )
266 },)*
267 };
268 unsafe {
269 let instance = self.obj();
270 ffi::gst_tracing_register_hook(
271 instance.to_glib_none().0 as *mut ffi::GstTracer,
272 hook_type.as_ptr() as *const _,
273 Some(std::mem::transmute::<*const (), extern "C" fn()>(callback)),
274 );
275 }
276 }
277 }
278 };
279}
280
281define_tracer_hooks! {
282 BinAddPost("bin-add-post") = |this, ts, b: *mut ffi::GstBin, e: *mut ffi::GstElement, r: glib::ffi::gboolean| {
283 let b = Bin::from_glib_borrow(b);
284 let e = Element::from_glib_borrow(e);
285 this.bin_add_post(ts, &b, &e, bool::from_glib(r))
286 };
287 BinAddPre("bin-add-pre") = |this, ts, b: *mut ffi::GstBin, e: *mut ffi::GstElement| {
288 let b = Bin::from_glib_borrow(b);
289 let e = Element::from_glib_borrow(e);
290 this.bin_add_pre(ts, &b, &e)
291 };
292 BinRemovePost("bin-remove-post") = |this, ts, b: *mut ffi::GstBin, r: glib::ffi::gboolean| {
293 let b = Bin::from_glib_borrow(b);
294 this.bin_remove_post(ts, &b, bool::from_glib(r))
295 };
296 BinRemovePre("bin-remove-pre") = |this, ts, b: *mut ffi::GstBin, e: *mut ffi::GstElement| {
297 let b = Bin::from_glib_borrow(b);
298 let e = Element::from_glib_borrow(e);
299 this.bin_remove_pre(ts, &b, &e)
300 };
301 ElementNew("element-new") = |this, ts, e: *mut ffi::GstElement| {
302 let e = Element::from_glib_borrow(e);
303 this.element_new(ts, &e)
304 };
305 ElementAddPad("element-add-pad") = |this, ts, e: *mut ffi::GstElement, p: *mut ffi::GstPad| {
306 let e = Element::from_glib_borrow(e);
307 let p = Pad::from_glib_borrow(p);
308 this.element_add_pad(ts, &e, &p)
309 };
310 ElementRemovePad("element-remove-pad") = |this, ts, e: *mut ffi::GstElement, p: *mut ffi::GstPad| {
311 let e = Element::from_glib_borrow(e);
312 let p = Pad::from_glib_borrow(p);
313 this.element_remove_pad(ts, &e, &p)
314 };
315 ElementChangeStatePost("element-change-state-post") = |this, ts, e: *mut ffi::GstElement, c: ffi::GstStateChange, r: ffi::GstStateChangeReturn| {
316 let e = Element::from_glib_borrow(e);
317 this.element_change_state_post(ts, &e, StateChange::from_glib(c), try_from_glib(r))
318 };
319 ElementChangeStatePre("element-change-state-pre") = |this, ts, e: *mut ffi::GstElement, c: ffi::GstStateChange| {
320 let e = Element::from_glib_borrow(e);
321 this.element_change_state_pre(ts, &e, StateChange::from_glib(c))
322 };
323 ElementPostMessagePost("element-post-message-post") = |this, ts, e: *mut ffi::GstElement, r: glib::ffi::gboolean| {
324 let e = Element::from_glib_borrow(e);
325 this.element_post_message_post(ts, &e, bool::from_glib(r))
326 };
327 ElementPostMessagePre("element-post-message-pre") = |this, ts, e: *mut ffi::GstElement, m: *mut ffi::GstMessage| {
328 let e = Element::from_glib_borrow(e);
329 let m = Message::from_glib_borrow(m);
330 this.element_post_message_pre(ts, &e, &m)
331 };
332 ElementQueryPost("element-query-post") = |this, ts, e: *mut ffi::GstElement, q: *mut ffi::GstQuery, r: glib::ffi::gboolean| {
333 let e = Element::from_glib_borrow(e);
334 let q = QueryRef::from_ptr(q);
335 this.element_query_post(ts, &e, q, bool::from_glib(r))
336 };
337 ElementQueryPre("element-query-pre") = |this, ts, e: *mut ffi::GstElement, q: *mut ffi::GstQuery| {
338 let e = Element::from_glib_borrow(e);
339 let q = QueryRef::from_ptr(q);
340 this.element_query_pre(ts, &e, q)
341 };
342 // TODO: unclear what to do here as the `GstMiniObject` here is not fully initialized yet…
343 MiniObjectCreated("mini-object-created") = |this, ts, o: *mut ffi::GstMiniObject| {
344 this.mini_object_created(ts, std::ptr::NonNull::new_unchecked(o))
345 };
346 // TODO: unclear what to do here as the `GstMiniObject` here is no longer valid…
347 MiniObjectDestroyed("mini-object-destroyed") = |this, ts, o: *mut ffi::GstMiniObject| {
348 this.mini_object_destroyed(ts, std::ptr::NonNull::new_unchecked(o))
349 };
350 MiniObjectReffed("mini-object-reffed") = |this, ts, o: *mut ffi::GstMiniObject, rc: libc::c_int| {
351 let o = MiniObject::from_glib_borrow(o);
352 this.mini_object_reffed(ts, &o, rc)
353 };
354 MiniObjectUnreffed("mini-object-unreffed") = |this, ts, o: *mut ffi::GstMiniObject, rc: libc::c_int| {
355 let o = MiniObject::from_glib_borrow(o);
356 this.mini_object_unreffed(ts, &o, rc)
357 };
358 ObjectCreated("object-created") = |this, ts, o: *mut ffi::GstObject| {
359 let o = Object::from_glib_borrow(o);
360 this.object_created(ts, &o)
361 };
362 // TODO: unclear what to do here as the `GstObject` here is no longer valid…
363 ObjectDestroyed("object-destroyed") = |this, ts, o: *mut ffi::GstObject| {
364 this.object_destroyed(ts, std::ptr::NonNull::new_unchecked(o))
365 };
366 ObjectReffed("object-reffed") = |this, ts, o: *mut ffi::GstObject, rc: libc::c_int| {
367 let o = Object::from_glib_borrow(o);
368 this.object_reffed(ts, &o, rc)
369 };
370 ObjectUnreffed("object-unreffed") = |this, ts, o: *mut ffi::GstObject, rc: libc::c_int| {
371 let o = Object::from_glib_borrow(o);
372 this.object_unreffed(ts, &o, rc)
373 };
374 PadLinkPost("pad-link-post") = |this, ts, src: *mut ffi::GstPad, sink: *mut ffi::GstPad, r: ffi::GstPadLinkReturn| {
375 let src = Pad::from_glib_borrow(src);
376 let sink = Pad::from_glib_borrow(sink);
377 this.pad_link_post(ts, &src, &sink, try_from_glib(r))
378 };
379 PadLinkPre("pad-link-pre") = |this, ts, src: *mut ffi::GstPad, sink: *mut ffi::GstPad| {
380 let src = Pad::from_glib_borrow(src);
381 let sink = Pad::from_glib_borrow(sink);
382 this.pad_link_pre(ts, &src, &sink)
383 };
384 PadPullRangePost("pad-pull-range-post") = |this, ts, p: *mut ffi::GstPad, b: *mut ffi::GstBuffer, r: ffi::GstFlowReturn| {
385 let p = Pad::from_glib_borrow(p);
386 let res: Result::<FlowSuccess, FlowError> = try_from_glib(r);
387 match res {
388 Ok(_) => {
389 this.pad_pull_range_post(ts, &p, Ok(&from_glib_borrow(b)))
390 }
391 Err(err) => {
392 this.pad_pull_range_post(ts, &p, Err(err))
393 }
394 }
395 };
396 PadPullRangePre("pad-pull-range-pre") = |this, ts, p: *mut ffi::GstPad, o: u64, s: libc::c_uint| {
397 let p = Pad::from_glib_borrow(p);
398 this.pad_pull_range_pre(ts, &p, o, s)
399 };
400 PadPushEventPost("pad-push-event-post") = |this, ts, p: *mut ffi::GstPad, r: glib::ffi::gboolean| {
401 let p = Pad::from_glib_borrow(p);
402 this.pad_push_event_post(ts, &p, bool::from_glib(r))
403 };
404 PadPushEventPre("pad-push-event-pre") = |this, ts, p: *mut ffi::GstPad, e: *mut ffi::GstEvent| {
405 let p = Pad::from_glib_borrow(p);
406 let e = Event::from_glib_borrow(e);
407 this.pad_push_event_pre(ts, &p, &e)
408 };
409 PadPushListPost("pad-push-list-post") = |this, ts, p: *mut ffi::GstPad, r: ffi::GstFlowReturn| {
410 let p = Pad::from_glib_borrow(p);
411 this.pad_push_list_post(ts, &p, try_from_glib(r))
412 };
413 PadPushListPre("pad-push-list-pre") = |this, ts, p: *mut ffi::GstPad, bl: *mut ffi::GstBufferList| {
414 let p = Pad::from_glib_borrow(p);
415 let bl = BufferList::from_glib_borrow(bl);
416 this.pad_push_list_pre(ts, &p, &bl)
417 };
418 PadPushPost("pad-push-post") = |this, ts, p: *mut ffi::GstPad, r: ffi::GstFlowReturn| {
419 let p = Pad::from_glib_borrow(p);
420 this.pad_push_post(ts, &p, try_from_glib(r))
421 };
422 PadPushPre("pad-push-pre") = |this, ts, p: *mut ffi::GstPad, b: *mut ffi::GstBuffer| {
423 let p = Pad::from_glib_borrow(p);
424 let b = Buffer::from_glib_borrow(b);
425 this.pad_push_pre(ts, &p, &b)
426 };
427 #[cfg(feature = "v1_22")]
428 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
429 PadChainListPost("pad-chain-list-post") = |this, ts, p: *mut ffi::GstPad, r: ffi::GstFlowReturn| {
430 let p = Pad::from_glib_borrow(p);
431 this.pad_chain_list_post(ts, &p, try_from_glib(r))
432 };
433 #[cfg(feature = "v1_22")]
434 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
435 PadChainListPre("pad-chain-list-pre") = |this, ts, p: *mut ffi::GstPad, bl: *mut ffi::GstBufferList| {
436 let p = Pad::from_glib_borrow(p);
437 let bl = BufferList::from_glib_borrow(bl);
438 this.pad_chain_list_pre(ts, &p, &bl)
439 };
440 #[cfg(feature = "v1_22")]
441 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
442 PadChainPost("pad-chain-post") = |this, ts, p: *mut ffi::GstPad, r: ffi::GstFlowReturn| {
443 let p = Pad::from_glib_borrow(p);
444 this.pad_chain_post(ts, &p, try_from_glib(r))
445 };
446 #[cfg(feature = "v1_22")]
447 #[cfg_attr(docsrs, doc(cfg(feature = "v1_22")))]
448 PadChainPre("pad-chain-pre") = |this, ts, p: *mut ffi::GstPad, b: *mut ffi::GstBuffer| {
449 let p = Pad::from_glib_borrow(p);
450 let b = Buffer::from_glib_borrow(b);
451 this.pad_chain_pre(ts, &p, &b)
452 };
453 PadQueryPost("pad-query-post") = |this, ts, p: *mut ffi::GstPad, q: *mut ffi::GstQuery, r: glib::ffi::gboolean| {
454 let p = Pad::from_glib_borrow(p);
455 let q = QueryRef::from_ptr(q);
456 this.pad_query_post(ts, &p, q, bool::from_glib(r))
457 };
458 PadQueryPre("pad-query-pre") = |this, ts, p: *mut ffi::GstPad, q: *mut ffi::GstQuery| {
459 let p = Pad::from_glib_borrow(p);
460 let q = QueryRef::from_ptr(q);
461 this.pad_query_pre(ts, &p, q)
462 };
463 PadUnlinkPost("pad-unlink-post") = |this, ts, src: *mut ffi::GstPad, sink: *mut ffi::GstPad, r: glib::ffi::gboolean| {
464 let src = Pad::from_glib_borrow(src);
465 let sink = Pad::from_glib_borrow(sink);
466 this.pad_unlink_post(ts, &src, &sink, bool::from_glib(r))
467 };
468 PadUnlinkPre("pad-unlink-pre") = |this, ts, src: *mut ffi::GstPad, sink: *mut ffi::GstPad| {
469 let src = Pad::from_glib_borrow(src);
470 let sink = Pad::from_glib_borrow(sink);
471 this.pad_unlink_pre(ts, &src, &sink)
472 };
473 #[cfg(feature = "v1_20")]
474 #[cfg_attr(docsrs, doc(cfg(feature = "v1_20")))]
475 PluginFeatureLoaded("plugin-feature-loaded") = |this, ts, feature: *mut ffi::GstPluginFeature| {
476 let feature = crate::PluginFeature::from_glib_borrow(feature);
477 this.plugin_feature_loaded(ts, &feature)
478 };
479}
480