java-gi icon indicating copy to clipboard operation
java-gi copied to clipboard

Can't cast Element to StreamVolume

Open Nyeksenn opened this issue 1 month ago • 5 comments

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.

Nyeksenn avatar Nov 13 '25 15:11 Nyeksenn

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?

jwharm avatar Nov 13 '25 21:11 jwharm

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.

Nyeksenn avatar Nov 14 '25 09:11 Nyeksenn

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.

Nyeksenn avatar Nov 14 '25 09:11 Nyeksenn

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.

jwharm avatar Nov 14 '25 17:11 jwharm

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.

jwharm avatar Nov 15 '25 10:11 jwharm

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());
}

jwharm avatar Nov 23 '25 20:11 jwharm