Search code examples
javaaudiojavafxjavasound

JavaFX MediaPlayer not playing M4A files


Given the following code:

import java.io.File;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        StackPane stackPane = new StackPane();
        stackPane.setOnMouseClicked((event) -> {
            String path = "audio.ext";
            Media media = new Media(new File(path).toURI().toString());
            MediaPlayer mp = new MediaPlayer(media);
            mp.setAutoPlay(true);
        });
        stage.setScene(new Scene(stackPane));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

The audio file (audio.ext) is supposed to play when I click on the screen. I can get MP3 files and WAV files to play audio. However when I try the same code using an M4A file, the audio does not play.

I noticed some interesting cases when modifying the code slightly to fix the problem.

Case 1: Making the MediaPlayer an instance variable instead of a local variable.

If I make the MediaPlayer object (mp) an instance variable and initialize it in the setOnMouseClicked block, the audio plays as it should and I have no issues.

Case 2: Adding the following code to the end of the setOnMouseClicked block:

MediaView mv = new MediaView(mp);
stackPane.getChildren().add(mv);

If I do this, then the audio plays as it should, and the screen does not visually change (i.e., adding the MediaView object to the StackPane does not visually alter it).


My question is: Why does this happen, and is there any way to get the audio to play without having to use these workarounds?

One suspicion I have is that an external reference to the object is necessary for the MediaPlayer to work. In Case 1, the instance variable served as the external reference, and in Case 2, the StackPane held a reference to the MediaView which in turn had a reference to the MediaPlayer. However, this does not explain why this only occurs with M4A files and not MP3 or WAV files. Perhaps MediaPlayer treats M4A files as video files instead of audio files for some reason. However, this is all speculation and I do not know for sure why this is occurring.


Solution

  • When you don't store a reference to an object, the Java garbage collector can clean it up when the reference goes out of scope.

    From How Garbage Collection Works:

    garbage collector

    When you add the code:

    MediaPlayer mp = new MediaPlayer(media);
    MediaView mv = new MediaView(mp);
    stackPane.getChildren().add(mv);
    

    A reference to the media player is retained in the MediaView and the StackPane, so the media player is not garbage collected. However, when you don't have the code that retains the reference, then the MediaPlayer can be garbage collected at any time.

    Also note the MediaPlayer javadoc:

    The operation of a MediaPlayer is inherently asynchronous. A player is not prepared to respond to commands quasi-immediately until its status has transitioned to MediaPlayer.Status.READY, which in effect generally occurs when media pre-roll completes.

    Because the operation is asynchronous, if you don't store a reference to the MediaPlayer, then the code can be subject to a race condition, where the garbage collector cleans up the MediaPlayer before the MediaPlayer completes playing your media. By the nature of race conditions, the behavior of code that is subject to them is unpredictable, which is usually an undesirable feature.

    As to why MP3 and WAV files are playing without you retaining a reference to the MediaPlayer, that could be just luck. The garbage collection can occur at any time that you no longer have a reference to the MediaPlayer. So playing Media in a MediaPlayer that you don't retain a reference to isn't a behavior I would rely on. Instead, it would be best to retain a reference to the MediaPlayer at least until it has completed playing its associated media.