Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playing audio using JavaFX MediaPlayer in a normal Java application?

I need to be able to play Audio files (MP3 / Wav) in a normal Java project. I'd prefer using the new JavaFX MediaPlayer rather than JMF. I wrote some code to test this:

public void play()
{
    URL thing = getClass().getResource("mysound.wav");
    Media audioFile = new Media( thing.toString() );     
    try
    {                                       
        MediaPlayer player = new MediaPlayer(audioFile);
        player.play();
    }
    catch (Exception e)
    {
        System.out.println( e.getMessage() );
        System.exit(0);
    }        
}

When I run this, I get the exception: Toolkit not initialized

I get that this has something to do with the JavaFX thread. My question is, how can I solve this? Do I need to create a JavaFX Panel just to play some audio files in the background of my normal app, or is there any other way?

Edit: Stacktrace:

java.lang.IllegalStateException: Toolkit not initialized
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:121)
    at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:116)
    at javafx.application.Platform.runLater(Platform.java:52)
    at javafx.scene.media.MediaPlayer.init(MediaPlayer.java:445)
    at javafx.scene.media.MediaPlayer.<init>(MediaPlayer.java:360)
    at javaapplication6.JavaApplication6.play(JavaApplication6.java:23)
    at javaapplication6.JavaApplication6.main(JavaApplication6.java:14)
like image 751
Ali Avatar asked Sep 22 '12 23:09

Ali


1 Answers

For a solution with integrates a JavaFX MediaPlayer in Swing

Use a JFXPanel and be careful to only use JavaFX objects on the JavaFX thread and after the JavaFX system has been properly initialized.

JavaFX is normal Java which makes the question a bit confusing, but I guess you mean Swing.

Here's a sample audio player which is launched from Swing. The sample assumes that there are a bunch of mp3 files in the default public sample music folder for Windows 7 (C:\Users\Public\Music\Sample Music) and plays each file in turn.

app screenshot

JavaFXMediaPlayerLaunchedFromSwing.java

This code is responsible for creating a Swing application which, in turn, initializes the JavaFX toolkit and creates a JavaFX scene on the JavaFX application thread.

import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;

import javax.swing.*;

/**
 * Example of playing all mp3 audio files in a given directory
 * using a JavaFX MediaView launched from Swing
 */
public class JavaFXMediaPlayerLaunchedFromSwing {
    private static void initAndShowGUI() {
        // This method is invoked on Swing thread
        JFrame frame = new JFrame("FX");
        final JFXPanel fxPanel = new JFXPanel();
        frame.add(fxPanel);
        frame.setBounds(200, 100, 800, 250);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);

        Platform.runLater(() -> initFX(fxPanel));
    }

    private static void initFX(JFXPanel fxPanel) {
        // This method is invoked on JavaFX thread
        Scene scene = new MediaSceneGenerator().createScene();
        fxPanel.setScene(scene);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(
            JavaFXMediaPlayerLaunchedFromSwing::initAndShowGUI
        );
    }
}

MediaSceneGenerator.java

Creates a JavaFX media player which sequentially plays all of the .mp3 media files in a given folder. It provides some controls for the media (play, pause, skip track, current track play progress indicator).

import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.layout.VBox;
import javafx.scene.media.*;
import javafx.util.Duration;

import java.io.File;
import java.util.*;

public class MediaSceneGenerator {
    private static final String MUSIC_FOLDER = "C:\\Users\\Public\\Music\\Sample Music";
    private static final String MUSIC_FILE_EXTENSION = ".mp3";

    private final Label currentlyPlaying = new Label();
    private final ProgressBar progress = new ProgressBar();
    private ChangeListener<Duration> progressChangeListener;

    public Scene createScene() {
        final StackPane layout = new StackPane();

        // determine the source directory for the playlist
        final File dir = new File(MUSIC_FOLDER);
        if (!dir.exists() || !dir.isDirectory()) {
            System.out.println("Cannot find media source directory: " + dir);
            Platform.exit();
            return null;
        }

        // create some media players.
        final List<MediaPlayer> players = new ArrayList<>();
        for (String file : Objects.requireNonNull(dir.list((dir1, name) -> name.endsWith(MUSIC_FILE_EXTENSION))))
            players.add(
                    createPlayer(
                            normalizeFileURL(dir, file)
                    )
            );
        if (players.isEmpty()) {
            System.out.println("No audio found in " + dir);
            Platform.exit();
            return null;
        }

        // create a view to show the mediaplayers.
        final MediaView mediaView = new MediaView(players.get(0));
        final Button skip = new Button("Skip");
        final Button play = new Button("Pause");

        // play each audio file in turn.
        for (int i = 0; i < players.size(); i++) {
            MediaPlayer player = players.get(i);
            MediaPlayer nextPlayer = players.get((i + 1) % players.size());
            player.setOnEndOfMedia(() -> {
                final MediaPlayer curPlayer = mediaView.getMediaPlayer();
                nextPlayer.seek(Duration.ZERO);
                if (nextPlayer != curPlayer) {
                    curPlayer.currentTimeProperty().removeListener(progressChangeListener);
                }
                mediaView.setMediaPlayer(nextPlayer);
                nextPlayer.play();
            });
        }

        // allow the user to skip a track.
        skip.setOnAction(actionEvent -> {
            final MediaPlayer curPlayer = mediaView.getMediaPlayer();
            MediaPlayer nextPlayer = players.get((players.indexOf(curPlayer) + 1) % players.size());
            nextPlayer.seek(Duration.ZERO);
            mediaView.setMediaPlayer(nextPlayer);
            if (nextPlayer != curPlayer) {
                curPlayer.currentTimeProperty().removeListener(progressChangeListener);
            }
            nextPlayer.play();
        });

        // allow the user to play or pause a track.
        play.setOnAction(actionEvent -> {
            if ("Pause".equals(play.getText())) {
                mediaView.getMediaPlayer().pause();
                play.setText("Play");
            } else {
                mediaView.getMediaPlayer().play();
                play.setText("Pause");
            }
        });

        // display the name of the currently playing track.
        mediaView.mediaPlayerProperty().addListener(
                (observableValue, oldPlayer, newPlayer) -> setCurrentlyPlaying(newPlayer)
        );

        // start playing the first track.
        mediaView.setMediaPlayer(players.get(0));
        mediaView.getMediaPlayer().play();
        setCurrentlyPlaying(mediaView.getMediaPlayer());

        // silly invisible button used as a template to get the actual preferred size of the Pause button.
        Button invisiblePause = new Button("Pause");
        invisiblePause.setVisible(false);
        play.prefHeightProperty().bind(invisiblePause.heightProperty());
        play.prefWidthProperty().bind(invisiblePause.widthProperty());

        // layout the scene.
        HBox controls = new HBox(10, skip, play, progress);
        controls.setAlignment(Pos.CENTER);
        VBox mediaPanel = new VBox(10, currentlyPlaying, mediaView, controls);

        layout.setStyle("-fx-background-color: cornsilk; -fx-font-size: 20; -fx-padding: 20; -fx-alignment: center;");
        layout.getChildren().addAll(
                invisiblePause,
                mediaPanel
        );
        progress.setMaxWidth(Double.MAX_VALUE);
        HBox.setHgrow(progress, Priority.ALWAYS);

        return new Scene(layout);
    }

    /**
     * sets the currently playing label to the label of the new media player and updates the progress monitor.
     */
    private void setCurrentlyPlaying(final MediaPlayer newPlayer) {
        progress.setProgress(0);
        progressChangeListener = (observableValue, oldValue, newValue) ->
                progress.setProgress(
                        1.0 * newPlayer.getCurrentTime().toMillis() / newPlayer.getTotalDuration().toMillis()
                );
        newPlayer.currentTimeProperty().addListener(progressChangeListener);

        String source = getUserFriendlyMediaName(newPlayer);
        currentlyPlaying.setText("Now Playing: " + source);
    }

    /**
     * @return a MediaPlayer for the given source which will report any errors it encounters
     */
    private MediaPlayer createPlayer(String aMediaSrc) {
        System.out.println("Creating player for: " + aMediaSrc);
        final MediaPlayer player = new MediaPlayer(new Media(aMediaSrc));
        player.setOnError(() -> System.out.println("Media error occurred: " + player.getError()));
        return player;
    }

    private String normalizeFileURL(File dir, String file) {
        return "file:///" + (dir + "\\" + file).replace("\\", "/").replaceAll(" ", "%20");
    }

    private String getUserFriendlyMediaName(MediaPlayer newPlayer) {
        String source = newPlayer.getMedia().getSource();

        source = source.substring(0, source.length() - MUSIC_FILE_EXTENSION.length());
        source = source.substring(source.lastIndexOf("/") + 1).replaceAll("%20", " ");

        return source;
    }
}

If you just want a native JavaFX application with a MediaPlayer and no Swing

The solution above which uses Swing answers the question asked. However, I have noted that sometimes people have picked up this answer and used it to create Java based media players even when they don't have an existing Swing application that they are embedding their application into.

If you don't have an existing Swing application, then eliminate the Swing code completely from your application and write a native JavaFX application instead. To do this, use the JavaFXMediaPlayer class below instead of the class JavaFXMediaPlayerLaunchedFromSwing from the sample above.

JavaFXMediaPlayer

import javafx.application.Application;
import javafx.stage.Stage;

public class JavaFXMediaPlayer extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new MediaSceneGenerator().createScene());
        stage.show();
    }
}

Answers to follow-up questions

Will my .JAR file built via swing automatically have JavaFX added to it once I add it to the libraries in netbeans?

Note: info in this follow-up answer on packaging is likely dated now and other preferred packaging options exist at this time (e.g https://github.com/openjfx/javafx-maven-plugin).

Technically, Swing doesn't build Jar files but the jar of javafx packaging commands do.

If your app contains JavaFX, then, it's best to use the JavaFX packaging tools. Without them, you may have some deployment issues issues as the Java runtime jar (jfxrt.jar) is not automatically on the java boot classpath for jdk7u7. Users can manually add it to their runtime classpath, but it could be a bit of a pain. In future jdk versions (perhaps jdk7u10 or jdk8), jfxrt.jar will be on the classpath. Even then, use of the JavaFX packaging tools would still be recommended as it will be the best way to ensure that your deployment package will work in the most compatible way.

The SwingInterop NetBeans project is a sample NetBeans project which utilizes JavaFX deployment tools for a Swing project embedding JavaFX components. Source for SwingInterop is part of the JDK 7 and JavaFX Demos and Samples download.

like image 183
jewelsea Avatar answered Oct 22 '22 07:10

jewelsea