Sunday, August 10, 2008

A simple MP3 Player in JavaFX

This is pretty much my first attempt at JavaFX and after looking around I wasn't able to find much about using the media player capabilities of JavaFX so I decided to create a simple MP3 player to basically get up and running with JavaFX.



So, for a start what does JavaFX give us in terms of media APIs.

Well the JavaFX SDK contains the javafx.scene.media package which contains the classes for using media within your application.

This package consists of the following classes:

  • Media : This class wraps a media source, i.e: in this case the location of the MP3 file.

  • MediaPlayer : This class provides functionality to drive the playback of a Media instance.

  • MediaError: This class represents an error that occurs during media playback.

  • MediaTimer: This class allows you to execute actions at an interval during the playback of media.

  • MediaView: This class is a visual component for displaying your media in the case of video.


  • For the purposes of this blog I'm only going to focus on the Media class, MediaPlayer class and MediaError class since these are enough to create a very basic MP3 player.

    Media Playback using the MediaPlayer class.
    So the first thing to do is create a most basic MediaPlayer instance:

    var player =
    MediaPlayer {
    repeatCount:MediaPlayer.REPEAT_FOREVER
    onError: function(e:MediaError) {
    display = e.message;
    }
    };


    You can see here that I've set two attributes: repeatCount and onError.

    The repeatCount attribute indicates if the media should loop or play once and then stop. The values for repeatCount are defined as constants on the MediaPlayer class, I've selected MediaPlayer.REPEAT_FOREVER in order to loop playback.

    Note: if you leave out repeatCount, playback will not occur (and no error is thrown either if you don't set it, which led to a rather annoying few hours for me).

    The onError attribute sets a closure (anonymous function) which gets executed when an error occurs. The closure is passed an instance of MediaError to indicate what happened. In my example I simply set a variable called display, which I bound to a Text node in the UI.

    Controlling the MediaPlayer class.
    The next thing we need to do define a method to control your player. MediaPlayer has two methods on it which allow you to control it: play() which starts or resumes playback and pause() which allows you to stop (but not reset) the current media playback in the player. For my example I wrote a very simple Button which stops and starts my MediaPlayer instance based on the button's state and the player's state:

    Button {
    enabled : bind enabled
    text: bind text
    action: function() {
    if (text == "Stop") {
    player.pause();
    text = "Play"
    } else if (player.media != null and text == "Play") {
    player.play();
    text = "Stop";
    }
    }
    }

    Nothing very fancy here, when the Button is clicked in Playing state I call player.pause(), if it's in Stopped state I check firstly that there is media available for the player and then that the button is in a state for me to start playing.

    Creating a Media instance for the MediaPlayer class
    The last thing really left to do is select some media for playback. Now the JavaFX SDK doesn't wrap all of the Swing API, only a select set of components which you can find in the javafx.ext.swing package. Most notably it's doesn't wrap any of the standard Swing dialogs including javax.swing.JFileChooser. The good news is that's it's easy to call JFileChooser from within JavaFX code. Once again I used a javafx.ext.swing.Button to call my code to select media and set it in my media player instance:

    var fileBtn: Button
    component:
    fileBtn = Button {
    text: "File"
    action: function() {
    var fc = new JFileChooser();
    var mp3Filter = new ExtensionFileFilter();
    mp3Filter.addExtension("mp3", "MP3");
    fc.addChoosableFileFilter(mp3Filter);
    var result = fc.showOpenDialog(fileBtn.getJButton());
    if (result == JFileChooser.APPROVE_OPTION) {
    var fFile = fc.getSelectedFile();
    display = fFile.getName();
    var file = fFile.toURL().toExternalForm();
    file = file.replaceAll(" ","%20");
    if (file != player.media.source) {
    player.pause();
    player.media = Media {source:file};
    text = "Play";
    enabled = true;
    }
    }
    }

    }



    This code basically calls a JFileChooser which selects .mp3 files from the file system using a simple javax.swing.filechooser.FileFilter instance FileExtensionFilter - which I wrote for my purposes - to get all the .mp3 files in the file system.

    Note that when you call showOpenDialog() on the FileChooser you have to pass in a java.awt.Component instance. The Button class does not implement this even though it's wraps a Swing JButton component. However Button does give you a method getJButton() which returns the underlying JButton instance, which you can use to pass to the FileChooser.

    Finally once you have the select .mp3 file, the last thing you need to do is create Media object and pass it to the MediaPlayer instance. The Media class has an attribute: source which is a string URL pointing to the location of the media. the Media object will also only accept escaped URLs, which java.io.File.toURL() does not do. This means you have manually escape the URL string before you pass it to the Media object.

    And that's it really, the full source code for my player is available here.

    4 comments:

    jNayden said...

    When i start this and choose some mp3 file i get :
    run:
    FX Media Object caught Exception com.sun.media.jmc.MediaUnsupportedException: Unsupported media: file:/D:/mp3/-10-%20Lacuna%20Coil%20-%20Entwined.mp3
    source ='file:/D:/mp3/-10-%20Lacuna%20Coil%20-%20Entwined.mp3'



    any ideas ?

    julian_za said...

    @Joke

    I seems like the mp3 codec support is not being picked up for some obscure reason.

    Are you using the SDK preview?

    julian_za said...

    Hi @Joke

    A bit of an update: I ran the player with the Netbeans profiler attached to the VM and I got the same exception!

    julian_za said...

    @Joke

    Even more info I read this can occur on some configurations of your sound card.

    You can check the known bugs in the preview here:

    http://java.sun.com/javafx/reference/releasenotes/javafx-sdk-release-notes.html#3