Adding other video codecs / DVD support to JavaFX 2.2
Asked Answered
K

5

16

Update:

Since the media side of JFX has been open sourced, I've looked into this myself and it is indeed possible, but requires changing and rebuilding the JFX source (both Java and C parts.) The process is described here for anyone that wants to have a go - I add MKV support in that example, but it should be very similar for other plugins.

The remainder of the question is thus mainly historical, but I'll leave it here for reference.

Background

I've been using VLCJ thus far for playing video in my application. It works, but if possible I'd like to see if I can achieve a similar level of support for common codecs by migrating to JavaFX and saving myself a lot of hassle with multiple VMs and suchlike that VLCJ needs to play multiple videos reliably. I won't go into it here but see my answer to this question if you're interested in the details. There's also the issue of cross-platform compatibility, it works on Mac and Linux ok but I haven't worked out how to get it to show on Mac yet (I believe there's some security in place to prevent one process gaining access to another's native components, but again that's beyond the scope of this question.)

It boils down to the fact that while it works, it's a lot of maintenance and hassle working with multiple VMs and bridging them stably if there's another solution that would be easier. VLC does have a pretty legendary level of support for playing pretty much anything which is why I've gone with it thus far, and I'd be interested to see if I can get a similar result in JavaFX - or at least if it can provide the means for doing so in a cross platform manner.

Research

JavaFX 2.0 supports video - great! But at the moment the official line is it supports "FLV containing VP6 video and MP3 audio". Is there a way to extend this to add in support for more codecs? There's no hard codec that I'd like to support, it's more a case of as many as I can so I'm looking for an extensible method to go about the above.

I wondered if it would play video for codecs installed natively on the machine and that it just doesn't advertise itself as such (because that functionality obviously is machine dependant and not cross-platform.) But no dice, I've tried a number of common formats and it really does refuse to play anything other than what it states.

From looking at JavaFX 1.3 it also supports other platform dependant codecs depending on where it's installed. Is there a way to get this behaviour with JavaFX 2? Or is it planned at all for a subsequent release? I haven't been able to find any information on it on the roadmap or any comment from Oracle about it.

Only thing I could find from searching extensively is here which implies that it may be possible but no-one seems to know how. I'd also be interested to know if it's based on GStreamer why all the formats supported by GStreamer aren't included by default either?

In terms of playing DVDs with JavaFX I've got absolutely nowhere, so I'm assuming that's just a no-go at the moment. If anyone does have any ideas or information though, I'm all ears.

Other approaches

One approach which I was half wondering may be possible is crowbarring the JMC jar out of the old JavaFX as described here and trying to get that working alongside JavaFX 2. I don't suppose anyone has had any luck with that approach or something similar?

All things failing, if anyone has any information or links on if / when support for additional codecs will be supported out of the box, then I'd be interested to hear that also. Or if anyone has any contact details for someone at Oracle I could ask that would also be appreciated! I've been longing for decent video support in Java for some time, and I guess what this boils down to is trying to figure out if JavaFX is the answer to this, or just another half hearted attempt that will never play more than what it does at the moment! I'm hoping it's not the latter, but I've yet to see much to show that's the case.

Kampala answered 16/11, 2011 at 14:27 Comment(1)
Related question: Playing h265 HEVC in a JavaFX clientNelsonnema
K
9

I've now managed to compile MKV support into JavaFX successfully, and it does take some, but not a great deal of effort on the native layer also. See here for the discussion surrounding it, and here for the result submitted as a patch / JIRA ticket.

I've written a much more comprehensive guide on the process here which may be of interest to anyone else looking to go down this route.

What follows is my brief investigation before I actually seriously looked at compiling other media support in, though I'll leave it here for reference.

Now that JFX8 has been released and is completely open source, I've spent a bit of time looking at how this could be done, and whether it could be done without patching the JFX source. Unfortunately the answer to that latter point is an almost definite no, at least not without horrible bytecode manipulation hacks. I may look into this more practically at a later date, but I'll document what I've worked out so far from the source available.

The magic starts from the Media constructor, which is ultimately where the MediaException pops out from (with the MEDIA_UNSUPPORTED flag if you try to play an unsupported format.) From there it creates the Locator, whose constructor ensures that the URL is one that's supported. It's init() method is then called in a separate thread, which performs some sanity checking on the URL string, reads the file, then proceeds to try to work out what the format is.

The relevant code for this part of the method is thus:

if (scheme.equals("file") || scheme.equals("jar")) {
    InputStream stream = getInputStream(uri);
    stream.close();
    isConnected = true;
    contentType = MediaUtils.filenameToContentType(uriString); // We need to provide at least something
}

if (isConnected) {
    // Check whether content may be played.
    // For WAV use file signature, since it can detect audio format
    // and we can fail sooner, then doing it at runtime.
    // This is important for AudioClip.
    if (MediaUtils.CONTENT_TYPE_WAV.equals(contentType)) {
        contentType = getContentTypeFromFileSignature(uri);
        if (!MediaManager.canPlayContentType(contentType)) {
            isMediaSupported = false;
        }
    } else {
        if (contentType == null || !MediaManager.canPlayContentType(contentType)) {
            // Try content based on file name.
            contentType = MediaUtils.filenameToContentType(uriString);

            if (Locator.DEFAULT_CONTENT_TYPE.equals(contentType)) {
                // Try content based on file signature.
                contentType = getContentTypeFromFileSignature(uri);
            }

            if (!MediaManager.canPlayContentType(contentType)) {
                isMediaSupported = false;
            }
        }
    }

    // Break as connection has been made and media type checked.
    break;
}

From this we can see a first "dumb" attempt is made to grab the file content based on its name (this is what MediaUtils.filenameToContentType() does.) There's then some special cases for checking for different types of wav file, but if that fails then we fall back on a cleverer check which looks at the actual file signature. Both these checks are in MediaUtils. This latter check is much more extensive, and looks at the first few bytes of the file to see if it can work out a format that way. If it can't, then it bails out and throws the exception that then pops out as our dreaded MEDIA_UNSUPPORTED flag.

If the type is identified correctly though, there's still another hurdle to go through - it has to be supported by the current platform. Some platforms are loaded dynamically depending on the environment, however the GSTPlatform always exists, thus we would need to put any additional (universal) formats here. This is relatively simple, a CONTENT_TYPES array exists which just holds the array of supported formats.

Unfortunately cloning the JavaFX repo seems to be failing for me at the moment, otherwise I'd attempt to put some of this in practice. But in lieu of the above, what actually needs to happen to add support for further formats? It actually doesn't seem hugely difficult.

  1. In MediaUtils, support needs to be added to the filenameToContentType() method to handle the new file extension. This is trivial.

  2. In the same class, support needs to be added to the fileSignatureToContentType() method to work out the file type based on its signature. This is a tad more complex, but still not too bad. This may even be optional, since the current code only seems to use this as a fallback if the format isn't identified correctly (or at all) from the file extension. A comprehensive list of file signatures for different formats can be found here which should help with this task.

  3. In GSTPlatform, the new content type needs to be added to the list of supported content types.

On the Java side of things, this appears to be all that's necessary to get it to accept the content type and at least attempt to pass it down to the native Gstreamer layer.

However, I'm no expert in GStreamer, so while I'm aware there's many more formats that it can handle and play that JavaFX currently refuses, I'm unsure as to how exactly they've removed this capacity. They've definitely done it in the Java layer above, but they may have also done it on the native GStreamer level - at this point I'm unsure.

I assume they've made some changes to GStreamer for JFX8 - but at the present time they're not listed on the relevant project page, so it's quite hard to work out exactly what they've changed for this version.

The next step would be to grab the JFX8 source, build with the above proposed changes for a new content type, and then see what errors (if any) occur on the native level, then take it from there.

Kampala answered 21/3, 2014 at 16:14 Comment(2)
Interesting concept Berry.Perplex
It's been a few years, but here's a gitHub link to something similar github.com/gstreamer-javaHamforrd
P
9

Believe me, I feel and know your frustration. I have pondered this for a while, but I had to use un-straight means of solving my issues.

There are many ways around this, each with limitations but depends on what works for you:

  1. Docs say WebView works with HTML5, which plays videos supported on the platform (Though sadly not flash). If using a webview to play video works for you, you can try this out. You can even draw over it with other nodes.

  2. Portable VLC Player! If maybe you're developing some sort of projector/director app and you want fullscreen video, you can have portable VLC player play the video in fullscreen in one screen with it's controls in the other. Used this solution and it works quite well for mac and windows. :) Only thing is you can't draw nodes on the video as it's an external app, with just the illusion of fullscreen video of your app.

  3. If you ever need to utilize the power of flash within your javafx 2.0 application, then use a swt-based browser(or something Like the DJ Project if you're a Swinger) as they support all features of your native browser.

Perplex answered 27/11, 2011 at 0:6 Comment(2)
One more thing =) 4. Mencoder can be used to convert any video file into on2 VP6. I incorporated it into my app as well and it works just fine. You can have some sort of "import video" feature in your app.Perplex
By the way, this is the app: bufflogic.com/…Perplex
K
9

I've now managed to compile MKV support into JavaFX successfully, and it does take some, but not a great deal of effort on the native layer also. See here for the discussion surrounding it, and here for the result submitted as a patch / JIRA ticket.

I've written a much more comprehensive guide on the process here which may be of interest to anyone else looking to go down this route.

What follows is my brief investigation before I actually seriously looked at compiling other media support in, though I'll leave it here for reference.

Now that JFX8 has been released and is completely open source, I've spent a bit of time looking at how this could be done, and whether it could be done without patching the JFX source. Unfortunately the answer to that latter point is an almost definite no, at least not without horrible bytecode manipulation hacks. I may look into this more practically at a later date, but I'll document what I've worked out so far from the source available.

The magic starts from the Media constructor, which is ultimately where the MediaException pops out from (with the MEDIA_UNSUPPORTED flag if you try to play an unsupported format.) From there it creates the Locator, whose constructor ensures that the URL is one that's supported. It's init() method is then called in a separate thread, which performs some sanity checking on the URL string, reads the file, then proceeds to try to work out what the format is.

The relevant code for this part of the method is thus:

if (scheme.equals("file") || scheme.equals("jar")) {
    InputStream stream = getInputStream(uri);
    stream.close();
    isConnected = true;
    contentType = MediaUtils.filenameToContentType(uriString); // We need to provide at least something
}

if (isConnected) {
    // Check whether content may be played.
    // For WAV use file signature, since it can detect audio format
    // and we can fail sooner, then doing it at runtime.
    // This is important for AudioClip.
    if (MediaUtils.CONTENT_TYPE_WAV.equals(contentType)) {
        contentType = getContentTypeFromFileSignature(uri);
        if (!MediaManager.canPlayContentType(contentType)) {
            isMediaSupported = false;
        }
    } else {
        if (contentType == null || !MediaManager.canPlayContentType(contentType)) {
            // Try content based on file name.
            contentType = MediaUtils.filenameToContentType(uriString);

            if (Locator.DEFAULT_CONTENT_TYPE.equals(contentType)) {
                // Try content based on file signature.
                contentType = getContentTypeFromFileSignature(uri);
            }

            if (!MediaManager.canPlayContentType(contentType)) {
                isMediaSupported = false;
            }
        }
    }

    // Break as connection has been made and media type checked.
    break;
}

From this we can see a first "dumb" attempt is made to grab the file content based on its name (this is what MediaUtils.filenameToContentType() does.) There's then some special cases for checking for different types of wav file, but if that fails then we fall back on a cleverer check which looks at the actual file signature. Both these checks are in MediaUtils. This latter check is much more extensive, and looks at the first few bytes of the file to see if it can work out a format that way. If it can't, then it bails out and throws the exception that then pops out as our dreaded MEDIA_UNSUPPORTED flag.

If the type is identified correctly though, there's still another hurdle to go through - it has to be supported by the current platform. Some platforms are loaded dynamically depending on the environment, however the GSTPlatform always exists, thus we would need to put any additional (universal) formats here. This is relatively simple, a CONTENT_TYPES array exists which just holds the array of supported formats.

Unfortunately cloning the JavaFX repo seems to be failing for me at the moment, otherwise I'd attempt to put some of this in practice. But in lieu of the above, what actually needs to happen to add support for further formats? It actually doesn't seem hugely difficult.

  1. In MediaUtils, support needs to be added to the filenameToContentType() method to handle the new file extension. This is trivial.

  2. In the same class, support needs to be added to the fileSignatureToContentType() method to work out the file type based on its signature. This is a tad more complex, but still not too bad. This may even be optional, since the current code only seems to use this as a fallback if the format isn't identified correctly (or at all) from the file extension. A comprehensive list of file signatures for different formats can be found here which should help with this task.

  3. In GSTPlatform, the new content type needs to be added to the list of supported content types.

On the Java side of things, this appears to be all that's necessary to get it to accept the content type and at least attempt to pass it down to the native Gstreamer layer.

However, I'm no expert in GStreamer, so while I'm aware there's many more formats that it can handle and play that JavaFX currently refuses, I'm unsure as to how exactly they've removed this capacity. They've definitely done it in the Java layer above, but they may have also done it on the native GStreamer level - at this point I'm unsure.

I assume they've made some changes to GStreamer for JFX8 - but at the present time they're not listed on the relevant project page, so it's quite hard to work out exactly what they've changed for this version.

The next step would be to grab the JFX8 source, build with the above proposed changes for a new content type, and then see what errors (if any) occur on the native level, then take it from there.

Kampala answered 21/3, 2014 at 16:14 Comment(2)
Interesting concept Berry.Perplex
It's been a few years, but here's a gitHub link to something similar github.com/gstreamer-javaHamforrd
E
5

The API design does not appear to have support for rolling your own codecs. Pretty much all of the classes are final (e.g. VideoTrack, Media, MediaPlayer etc). I assume that the actual video decoding is done with internal classes at present, meaning there is no way to override them.

There is a plan to Open Source JavaFX 2.0, I suspect as we approach the release of JDK8. Hopefully when they do this we can see how they resolve their codecs from the Media(String source) constructor and see if we can hook into this somehow.

Enidenigma answered 25/11, 2011 at 6:12 Comment(0)
P
5

And now, Javafx2.1 finally supports mp4 H.264 so you should now be good to go without the above posted stunts. :)

Perplex answered 27/3, 2012 at 23:19 Comment(4)
but does it support .mkvs? x264 encoded with AVC (H.264) as the video stream and AC-3/DTS as audio, i.e.?Claudicant
I don't think so. Just on2 flv and normal h264 for video, and then just mp3 for audio stream.Perplex
It's an improvement but it's a long way from supporting the majority of commonly used formats, which is ideally what I want!Kampala
You and me both. But you'll have to rely on workarounds for now till Oracle does something about it, though I think they may soon be making the entire JFX stack opensource so it might be left to the community to figure out. You could incorporate an embedded ffmpeg for now to do auto conversion within your app prior to loading the video if it fails to open.Perplex
N
2

Current open feature requests for this in the JavaFX bug tracking system:

Read the linked feature requests and the associated comments on them to understand their current status (or lack thereof ;-) for the JavaFX distribution version that you are using.

Note, for the InputStream based Media API, one of the later comments by a JavaFX developer is "I propose we consider this for JDK 10", so I guess it may be a possibility in the future...

Also note, if you are not sure if JavaFX currently has in-built support for a given encoding type or not, a comprehensive overview of supported media encodings and media container types is provided in the javadoc for the javafx.media package (just ensure that you review the version of the javadoc which matches your version of JavaFX).

Those who may be interested in other solutions to at least get a video to play from JavaFX, even if it is a media type not natively supported by JavaFX and you don't want to hack the native JavaFX media support just to get your video to play, can also see my answer to the related question:

Nelsonnema answered 30/1, 2018 at 0:11 Comment(1)
Thanks! This is still an ongoing area of interest for me - though it looks like really, very little has changed in the 7 or so years since I posted the question. InputStream support would be a plus, though not a game changer since it doesn't really change what you can play, just makes playing from some sources more efficient. I never did really find a JFX-only solution to this; I'm still using VLCJ rendered to a canvas - but clearly that isn't a solution that will fit everyone's needs!Kampala

© 2022 - 2024 — McMap. All rights reserved.