Issue
I have created a music player using JavaFX. However, I am not able to understand how I can know the duration of a song I am currently playing.
Edit: I have added the following lines to my code and now the song duration is visible. However, when I play the next song, the song duration is not visible. Here are the lines that I added to the code:
mediaPlayer.currentTimeProperty().addListener(new TimeListener());
TimeListener = new ChangeListener<Duration>()
{
@Override public void changed(ObservableValue<? extends Duration> o, Duration oldVal, Duration newVal)
{
//now newVal is of Duration class
}
};
}
public class TimeListener implements ChangeListener {
public void changed(ObservableValue o, Object oldVal, Object newVal) {
//Update time display with MediaPlayer's current time:
playTime.setText(newVal.toString());
}
}
Here is my code to create the music player:
public class MusicController implements Initializable
{
@FXML
private ImageView trackLogo;
@FXML
private Button next, previous, pause, play, reset;
@FXML
private Slider VolumeSlider;
@FXML
private AnchorPane Panel;
@FXML
private Label playTime, appTitle, songTitle;
@FXML
private ProgressBar songProgressBar;
@FXML
private Media media;
private MediaPlayer mediaPlayer;
private File directory;
private File [] files;
private ArrayList<File> songs;
private int songNumber;
private Timer timer;
private TimerTask task;
private boolean running;
private ChangeListener<Duration> TimeListener;
@Override
public void initialize(URL url, ResourceBundle rb)
{
songs = new ArrayList<File>();
directory = new File("music");
files = directory.listFiles();
if(files!=null)
{
for(File file : files)
{
songs.add(file);
System.out.println(file);
}
}
media = new Media(songs.get(songNumber).toURI().toString());
mediaPlayer = new MediaPlayer(media);
mediaPlayer.currentTimeProperty().addListener(new TimeListener());
songTitle.setText(songs.get(songNumber).getName());
VolumeSlider.valueProperty().addListener(new ChangeListener<Number>()
{
public void changed(ObservableValue<? extends Number> arg0, Number arg1, Number arg2)
{
mediaPlayer.setVolume(VolumeSlider.getValue() * 0.01);
}
});
TimeListener = new ChangeListener<Duration>()
{
@Override public void changed(ObservableValue<? extends Duration> o, Duration oldVal, Duration newVal)
{
//now newVal is of Duration class
}
};
}
public class TimeListener implements ChangeListener {
public void changed(ObservableValue o, Object oldVal, Object newVal) {
//Update time display with MediaPlayer's current time:
playTime.setText(newVal.toString());
}
}
public void playMedia()
{
beginTimer();
mediaPlayer.setVolume(VolumeSlider.getValue() * 0.01);
mediaPlayer.play();
}
public void pauseMedia()
{
cancelTimer();
mediaPlayer.pause();
}
public void resetMedia()
{
songProgressBar.setProgress(0);
mediaPlayer.seek(Duration.seconds(0));
}
public void previousMedia()
{
if (songNumber > 0)
{
songNumber--;
mediaPlayer.stop();
if(running)
{
cancelTimer();
}
media = new Media(songs.get(songNumber).toURI().toString());
mediaPlayer = new MediaPlayer(media);
songTitle.setText(songs.get(songNumber).getName());
playMedia();
}
else
{
songNumber = songs.size() - 1;
mediaPlayer.stop();
if(running)
{
cancelTimer();
}
media = new Media(songs.get(songNumber).toURI().toString());
mediaPlayer = new MediaPlayer(media);
songTitle.setText(songs.get(songNumber).getName());
playMedia();
}
}
public void nextMedia()
{
if (songNumber < songs.size() - 1)
{
songNumber++;
mediaPlayer.stop();
if(running)
{
cancelTimer();
}
media = new Media(songs.get(songNumber).toURI().toString());
mediaPlayer = new MediaPlayer(media);
songTitle.setText(songs.get(songNumber).getName());
playMedia();
}
else
{
songNumber = 0;
mediaPlayer.stop();
if(running)
{
cancelTimer();
}
media = new Media(songs.get(songNumber).toURI().toString());
mediaPlayer = new MediaPlayer(media);
songTitle.setText(songs.get(songNumber).getName());
playMedia();
}
}
public void beginTimer()
{
timer = new Timer();
task = new TimerTask()
{
public void run()
{
running = true;
double current = mediaPlayer.getCurrentTime().toSeconds();
double end = media.getDuration().toSeconds();
System.out.println(current/end);
songProgressBar.setProgress(current/end);
if (current/end ==1)
{
cancelTimer();
}
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
}
public void cancelTimer()
{
running = false;
timer.cancel();
}
}
I have tried reading the Oracle document (https://docs.oracle.com/javase/8/javafx/media-tutorial/playercontrol.htm) and this youtube video (https://www.youtube.com/watch?v=-D2OIekCKes) to create a music player.
And I would be grateful for any help. Thanks in advance!
Solution
You should not use Timer
, or threads in general, for something like this. The properties of MediaPlayer
are observable, and so you should either add listeners to them, or bind the UI properties to them. Here's an example of binding the ProgressBar
's progress property to the player:
progressBar.progressProperty().bind(
Bindings.createDoubleBinding(() -> {
Duration current = mediaPlayer.getCurrentTime();
Duration total = mediaPlayer.getCycleDuration();
return current.toMilli() / total.toMillis();
},
mediaPlayer.currentTimeProperty(),
mediaPlayer.cycleDurationProperty())
);
The above makes use of Bindings#createDoubleBinding(Callable,Observable...) and lambda expressions.
You'd need to do the above every time a new MediaPlayer
is created, but otherwise that will handle keeping the progress bar up-to-date. And you can do something similar for labels displaying, for instance, the current time and total time in a human readable format.
Also, instead of adding a listener to your volumeSlider
you can do:
mediaPlayer.volumeProperty().bind(volumeSlider.valueProperty());
See Using JavaFX Properties and Binding for more information.
As an aside, not only should you not use Timer
here, but the way you're using it is incorrect. In JavaFX, there is a special thread named the JavaFX Application Thread. Only that thread should interact with the UI, directly or indirectly. Using any other thread to interact with the UI is broken code and can lead to undefined behavior—JavaFX is not thread safe.
You can schedule actions on the FX thread using Platform#runLater(Runnable)
.
Answered By - Slaw