0

I'm new to Java and JavaFX -- I'm making a small player application and are finding it a challenge to get the duration timer to display on a label on my display.

My latest attempt was creating a TimeListener.java class which would change the duration values for each new song played and set them on the label in another class but that idea is flawed as I came across a non-static error.

TrackPlayer object class

    private MediaPlayer player;
    private Media track;
    private String filepath;

    private Duration duration;

    public TrackPlayer(String filepath) {
        this.filepath = filepath;
        track = new Media(filepath);
        player = new MediaPlayer(track);

        player.setOnReady(() -> {
            duration = track.getDuration();
            System.out.println("Duration: " + duration);
        });

        player.currentTimeProperty().addListener(new TimeListener());
    }

TimeListener class

public class TimeListener implements ChangeListener<Duration> {

    @Override
    public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
        TrackPlayerController.setTime(newValue.toString());
    }
}

FXML Controller class

    @FXML
    private Label runTime;

    ...

    public void setTime(String time) {
        //runTime.setText(time);
    }

How else could I approach this problem? I want a label which would display something like 00:00:00 (elapsed) / 00:00:00 (duration) but I'm sure if I just get the duration working I can also get the elapsed time working.

Example with the same problem but most if not all features removed

TrackPlayer class

package logic;

import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayer.Status;
import javafx.util.Duration;

public class TrackPlayer {

    private MediaPlayer player;
    private Media track;
    private String filepath;

    private Duration duration;

    public TrackPlayer(String filepath) {
        this.filepath = filepath;
        track = new Media(filepath);
        player = new MediaPlayer(track);

        player.setOnReady(() -> {
            duration = track.getDuration();
            System.out.println("Duration: " + duration);
        });
    }

    public void playSong() {
        System.out.println("Playing song");
        player.play();
    }

    public void pauseSong() {
        System.out.println("Pausing song");
        player.pause();
    }

    public void stopSong() {
        System.out.println("Stopping song");
        player.stop();
    }

    public Status getStatus() {
        return player.getStatus();
    }

    public Duration getDuration() {
        return duration;
    }

    public Duration getCurrentTime() {
        return player.getCurrentTime();
    }

    public Duration getStartTime() {
        return player.getStartTime();
    }

    public void setSeek(Duration duration) {
        player.seek(duration);
    }

    public Media getMedia() {
        return player.getMedia();
    }

    public ReadOnlyObjectProperty<Duration> currentTimeProperty() {
        return player.currentTimeProperty();
    }

    public Duration getTotalDuration() {
        return player.getTotalDuration();
    }
}

TrackPlayerController class

package gui;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import logic.TrackPlayer;
import logic.Track;

public class TrackPlayerController implements Initializable {

    @FXML
    private TableView<Track> playingTable;
    @FXML
    private TableColumn<Track, String> playingTitleCol;
    @FXML
    private TableColumn<Track, String> playingArtistCol;
    @FXML
    private TableColumn<Track, String> playingGenreCol;
    @FXML
    private TableColumn<Track, String> playingRunTimeCol;
    @FXML
    private Label runTime;

    private TrackPlayer player;

    @Override
    public void initialize(URL fxmlFileLocation, ResourceBundle resources) {
        playingTitleCol.setCellValueFactory(new PropertyValueFactory<>("TrackTitle"));
        playingArtistCol.setCellValueFactory(new PropertyValueFactory<>("TrackArtist"));
        playingGenreCol.setCellValueFactory(new PropertyValueFactory<>("TrackGenre"));
        playingRunTimeCol.setCellValueFactory(new PropertyValueFactory<>("RunTime"));

        player.currentTimeProperty().addListener(observable -> {
            setTime(player.getCurrentTime()
                    + " / "
                    + player.getTotalDuration());
        });

        playingTable.setRowFactory(tv -> { // Function for double-click to play (load)
            TableRow<Track> row = new TableRow<>();
            row.setOnMouseClicked(event -> {
                if (event.getClickCount() == 2 && (!row.isEmpty())) {
                    play();
                }
            });
            return row;
        });
    }

    @FXML
    private void play() {
    }

    @FXML
    private void reset(ActionEvent e) {
    }

    @FXML
    private void remove(ActionEvent e) {
    }

    @FXML
    private void removeAll(ActionEvent e) {
    }

    @FXML
    private void search(ActionEvent e) throws IOException {
    }

    public void setTime(String time) {
        runTime.setText(time);
    }
}

TrackPlayerMain class

package gui;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.BorderPane;

public class TrackPlayerMain extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        BorderPane root = new BorderPane();

        FXMLLoader trackPlayerLoader = new FXMLLoader(getClass().getResource("TrackPlayer.fxml"));
        root.setCenter(trackPlayerLoader.load());
        TrackPlayerController trackPlayerController = trackPlayerLoader.getController();

        Scene scene = new Scene(root, 600, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}

TrackPlayer FXML

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.paint.*?>
<?import javafx.scene.text.*?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="gui.TrackPlayerController">
   <children>
      <Slider fx:id="timeSlider" layoutX="9.0" layoutY="333.0" prefHeight="25.0" prefWidth="582.0" />
      <Label alignment="BOTTOM_LEFT" layoutX="23.0" layoutY="10.0" prefHeight="17.0" prefWidth="75.0" text="Now Playing" />
      <Button fx:id="play" layoutX="250.0" layoutY="361.0" mnemonicParsing="false" onAction="#play" prefHeight="25.0" prefWidth="100.0" text="Play" />
      <Button fx:id="ff" layoutX="356.0" layoutY="361.0" mnemonicParsing="false" text="&gt;&gt;" />
      <Button fx:id="rw" layoutX="211.0" layoutY="361.0" mnemonicParsing="false" text="&lt;&lt;" />
      <Button fx:id="reset" layoutX="22.0" layoutY="361.0" mnemonicParsing="false" onAction="#reset" prefWidth="59.0" text="Reset" />
      <Button fx:id="remove" layoutX="498.0" layoutY="305.0" mnemonicParsing="false" onAction="#remove" prefWidth="83.0" text="Remove" />
      <Label fx:id="runTime" alignment="TOP_CENTER" layoutX="516.0" layoutY="350.0" prefHeight="17.0" prefWidth="75.0" text="00:00 / 00:00" textFill="#00000065">
         <font>
            <Font size="11.0" />
         </font>
      </Label>
      <Button fx:id="removeAll" layoutX="401.0" layoutY="305.0" mnemonicParsing="false" onAction="#removeAll" prefHeight="25.0" prefWidth="83.0" text="Remove All" />
      <TableView fx:id="playingTable" layoutX="18.0" layoutY="32.0" prefHeight="263.0" prefWidth="563.0">
        <columns>
          <TableColumn fx:id="playingTitleCol" editable="false" prefWidth="140.75" resizable="false" text="Title" />
          <TableColumn fx:id="playingArtistCol" editable="false" prefWidth="140.75" resizable="false" text="Artist" />
            <TableColumn fx:id="playingGenreCol" editable="false" prefWidth="140.75" resizable="false" text="Genre" />
            <TableColumn fx:id="playingRunTimeCol" prefWidth="140.75" resizable="false" text="Run Time" />
        </columns>
         <columnResizePolicy>
            <TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
         </columnResizePolicy>
      </TableView>
      <Button fx:id="search" layoutX="303.0" layoutY="305.0" mnemonicParsing="false" onAction="#search" prefHeight="0.0" prefWidth="83.0" text="Search" />
   </children>
</AnchorPane>

From what I assume it is throwing NullPointerException because it's trying to initalize the listener with the current time and duration however the player object has not been created yet (as no song is played right from the start, only when selected and pressed play) -- if that is the case how can I add the listener?

Edit: Okay so I've tested what causes the NullPointerException and it's the player being null, as the program launches when I do this.

if (player != null) {
    player.currentTimeProperty().addListener(observable -> {
        runTime.setText(player.getCurrentTime()
                + " / "
                + player.getTotalDuration());
    });
}

However when I do this the listener doesn't get initialized as the runTime label does not change at all. This is my problem that I'm trying to solve. How can I go about fixing it?

1 Answers1

2

Non-FXML Based Sample

Here is some sample code, it doesn't use FXML, but the principles are the same whether you have FXML involved or not. You just add a listener to the relevant property and take action as it changes. I didn't format the duration as you have in your question, but that is trivial and different from the problem of responding to changes in the listener.

sample

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.scene.media.*;
import javafx.stage.Stage;

public class VideoPlayerExample extends Application {
    private static final String MEDIA_LOC =
            "http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";

    @Override
    public void start(final Stage stage) throws Exception {
        final MediaPlayer oracleVid = new MediaPlayer(
                new Media(MEDIA_LOC)
        );

        Label progress = new Label();
        oracleVid.currentTimeProperty().addListener(observable -> {
            progress.setText(
                    oracleVid.getCurrentTime()
                            + " / "
                            + oracleVid.getTotalDuration()
            );
        });

        VBox layout = new VBox(10, progress, new MediaView(oracleVid));
        stage.setScene(new Scene(layout, 540, 208));
        stage.show();

        oracleVid.play();
    }

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

where should I place the listener?

It's difficult to recommend without seeing full code, so I'll just make some assumptions and provide advice on that to get you started. So, let's assume that:

  1. You are defining and embedding your custom media player control in FXML similar to the mechanism outlined in:
  2. The duration tracking label is (for some reason) not part of the custom media control.
  3. Your encompassing FXML therefore includes two components, your custom media player and the duration label.

In this case the listener is situated in the controller for the encompassing FXML file that includes the media player and the duration label and is set up during the initialize() call for that controller.

FXML Based Sample

In general, for something like this for generic reuse you might create a custom control as outlined previously, but for brevity this sample will just use the in-built MediaView and Label controls rather than a custom control.

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.media.MediaView?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="plot.VideoPlayerController">
   <children>
      <Label fx:id="progress" text="Label" />
      <MediaView fx:id="mediaView" fitHeight="208.0" fitWidth="540.0" />
   </children>
</VBox>

App

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class VideoPlayerApp extends Application {
    @Override
    public void start(final Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("player.fxml"));
        Parent root = loader.load();

        stage.setScene(new Scene(root));
        stage.show();
    }

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

Controller

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;

public class VideoPlayerController {    
    private static final String MEDIA_LOC =
            "http://download.oracle.com/otndocs/products/javafx/oow2010-2.flv";

    @FXML
    MediaView mediaView;

    @FXML
    Label progress;

    public void initialize() {
        final MediaPlayer oracleVid = new MediaPlayer(
                new Media(MEDIA_LOC)
        );

        oracleVid.currentTimeProperty().addListener(observable -> {
            progress.setText(
                    oracleVid.getCurrentTime()
                            + " / "
                            + oracleVid.getTotalDuration()
            );
        });

        mediaView.setMediaPlayer(oracleVid);

        oracleVid.play();
    }    
}
Community
  • 1
  • 1
jewelsea
  • 130,119
  • 12
  • 333
  • 365
  • I just don't understand one thing, where should I place the listener? I have the TrackPlayer class with functions such as play, stop and the TrackPlayer object itself from the constructor when you pass a filepath, and controller class where I have functions for all of the buttons. I've been trying to find something about standard layout of such things but I haven't been successful. If I were to place it below my only other listener (in the constructor of TrackPlayer) I'd get an error saying setTime in the controller class cannot be referenced from a static context. – Gordon Freeman Turtle Mar 02 '16 at 00:41
  • 1
    I don't have time to review an off-site resource, hopefully you have enough information in the current answer as it stands to help you determine the correct approach for your application. For future questions, you might consider supplying an [mcve](http://stackoverflow.com/help/mcve) in the question, i.e. complete minimal compilable code that somebody could copy and paste to immediately replicate the issue. – jewelsea Mar 02 '16 at 01:08
  • I've added minimal code which produces the same issue. I believe I understand why the issue occurs, but I don't know how to go about fixing it. – Gordon Freeman Turtle Mar 02 '16 at 14:18
  • You get a NullPointerException because you have a member of your TrackController you that you try to use without initializing. You should write: `private TrackPlayer player = new TrackPlayer("some file name");`. There are probably other errors in your code which will prevent it from doing what you want, but at least if you initialize the member correctly, the NullPointerException will be avoided and the GUI will show. See: [What is a Null Pointer Exception, and how do I fix it?](http://stackoverflow.com/questions/218384/what-is-a-null-pointer-exception-and-how-do-i-fix-it) – jewelsea Mar 02 '16 at 18:52
  • Yes but if you look at my code I only want to load a TrackPlayer once the user chooses a song and decides to play it, not right at launch. – Gordon Freeman Turtle Mar 02 '16 at 19:04
  • Then you have to listen to the track player at that time (when it actually exists). You can't listen to something that is not there. – jewelsea Mar 02 '16 at 19:57