Can't cast Element to StreamVolume
I want to retrieve a GstElement from my pipeline that implements GstStreamVolume. I use the following code:
public StreamVolume getStreamVolume() {
Element sv = pipeline.getByInterface(StreamVolume.getType());
if (sv != null) {
return (StreamVolume) sv;
}
return null;
}
If I run my program, the cast fails with:
(java:67878): java-gi-WARNING **: 16:51:03.812: java.lang.ClassCastException: class org.freedesktop.gstreamer.gst.Element$Element$Impl cannot be cast to class org.freedesktop.gstreamer.audio.StreamVolume (org.freedesktop.gstreamer.gst.Element$Element$Impl and org.freedesktop.gstreamer.audio.StreamVolume are in unnamed module of loader 'app') in SourceFunc
According to the Gstreamer docs I should be able to use it like that. The element is also successfully retrieved.
Could this be the same issue as #295, that GstAudio hasn't been initialized?
In other words, did you try calling GstAudio.javagi$ensureInitialized(); before using the GstStreamVolume interface?
Thanks for the reply.
I don't think that this is the issue.
I added GstAudio.javagi$ensureInitialized(); to my main class and this
fixes #295. I can successfully retrieve the element.
As the log shows, this is a casting exception not a NullPointerException.
For testing I also added all the gestreamer intializer:
GstAudio.javagi$ensureInitialized();
Gst.javagi$ensureInitialized();
GstBase.javagi$ensureInitialized();
GstApp.javagi$ensureInitialized();GstAudio.javagi$ensureInitialized();
This doesn't help.
I found how to fix this.
I can't cast to the Interface so I have to create a new StreamVolume.StreamVolume$Impl object like so:
return new StreamVolume.StreamVolume$Impl(sv.handle());
Maybe I should write some documentation for this.
Yes that will work. But it’s a workaround and shouldn’t be necessary. I suspect that there’s a bug related to GstElement being an abstract class. I’ll have to investigate a bit further.
OK I found the cause.
The element that is returned by pipeline.getByInterface(StreamVolume.getType()) is of a type that isn't publicly documented in the GObject-Introspection data; basically a "private class". Java-GI tries to wrap that into a Java proxy class. Because the exact type isn't registered, I have to return a supertype: either a parent class or an implemented interface. Java's static typing doesn't allow instances of "unknown class that extends X and implements Y". It has to be a concrete Java class, but which one should it be? I haven't been able to come up with a foolproof solution yet.
Currently I implemented this:
- When the parent class isn't a fundamental type (like GObject), choose that one. (This happens in your case.)
- Otherwise, choose one of its implemented interfaces.
- If that also fails, fallback to the return type of the function.
I frankly don't know how to properly resolve this. If I prioritize the implemented interfaces instead of the superclass, then other functions could break instead.
One other thing that complicates this issue: The return type of getByInterface() is Element. But the StreamVolume interface can be applied to any GObject-derived class. So I cannot change getByInterface() to return a StreamVolume instance; it would not be an Element and it would throw a ClassCastException, just like you saw when you tried to cast it in your own code.
Of course, the actual object that getByInterface() returns, IS most probably an Element subclass that implements StreamVolume. It's just not a type that java-gi knows about, so there is no wrapper class available on the Java side.
I'm afraid I can't fix this issue cleanly. The only way to handle this, is like you already mentioned:
Element sv = pipeline.getByInterface(StreamVolume.getType());
if (sv != null) {
new StreamVolume.StreamVolume$Impl(sv.handle());
}