How to create custom binding types in JavaFx
java, javafx
Solution
Sample solution using an `ObjectBinding<TimeElapsed>`.
Key method
/* @return an ObjectBinding of immutable TimeElapsed objects for the player */
private ObjectBinding<TimeElapsed> createElapsedBindingByBindingsAPI(
final MediaPlayer player
) {
return Bindings.createObjectBinding(
new Callable<TimeElapsed>() {
@Override
public TimeElapsed call() throws Exception {
return new TimeElapsed(player.getCurrentTime());
}
},
player.currentTimeProperty()
);
}
Complete executable example
Change the `MEDIA_PATH` in the sample to match your required media.
import javafx.application.Application;
import javafx.beans.binding.*;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.media.*;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.File;
import java.net.MalformedURLException;
import java.util.concurrent.Callable;
/** Displays progress (time elapsed in seconds) of playing a media file. */
public class TimeElapsedBinding extends Application {
private static final String MEDIA_PATH =
"C:\\Users\\Public\\Music\\Sample Music\\Dillon - Thirteen Thirtyfive.mp3";
public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws Exception {
final MediaView mediaView = createMediaView();
final MediaPlayer player = mediaView.getMediaPlayer();
final Label elapsedLabel = new Label();
ObjectBinding<TimeElapsed> elapsedBinding =
createElapsedBindingByBindingsAPI(player);
StringBinding elapsedStringBinding =
createStringBindingByBindingsAPI(elapsedBinding);
elapsedLabel.textProperty().bind(
elapsedStringBinding
);
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20px;");
layout.getChildren().setAll(
mediaView,
elapsedLabel
);
stage.setScene(new Scene(layout));
stage.show();
}
/* @return an ObjectBinding of immutable TimeElapsed objects for the player */
private ObjectBinding<TimeElapsed> createElapsedBindingByBindingsAPI(
final MediaPlayer player
) {
return Bindings.createObjectBinding(
new Callable<TimeElapsed>() {
@Override
public TimeElapsed call() throws Exception {
return new TimeElapsed(player.getCurrentTime());
}
},
player.currentTimeProperty()
);
}
/* @return a string binding to an ObjectBinding of immutable TimeElapsed objects */
private StringBinding createStringBindingByBindingsAPI(
final ObjectBinding<TimeElapsed> elapsedBinding
) {
return Bindings.createStringBinding(
new Callable<String>() {
@Override
public String call() throws Exception {
return String.format(
"%.0f",
elapsedBinding.getValue().getElapsed()
);
}
},
elapsedBinding
);
}
/* @Return a new MediaView from a predefined MEDIA_PATH string */
private MediaView createMediaView() throws MalformedURLException {
String mediaURI = new File(MEDIA_PATH).toURI().toURL().toExternalForm();
Media media = new Media(mediaURI);
MediaPlayer player = new MediaPlayer(media);
MediaView mediaView = new MediaView(player);
player.play();
return mediaView;
}
/** immutable TimeElapsed class. */
class TimeElapsed {
private final double elapsed;
TimeElapsed(Duration duration) {
elapsed = duration.toSeconds();
}
public double getElapsed() {
return elapsed;
}
}
}
The above is provided only as a sample to fit into the question framework of using an immutable object in an ObjectBinding, rather than as the most efficient way to track the progress of playing media.
Alternate Implementation
Without the requirement of using an immutable object in an ObjectBinding, I would monitor the progress property of a MediaPlayer directly, similar to the code below:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.layout.StackPane;
import javafx.scene.media.*;
import javafx.stage.Stage;
import java.io.File;
import java.net.MalformedURLException;
public class MediaProgressMonitoring extends Application {
private static final String MEDIA_PATH = "C:\\Users\\Public\\Music\\Sample Music\\Dillon - Thirteen Thirtyfive.mp3";
public static void main(String[] args) { launch(args); }
@Override public void start(Stage stage) throws Exception {
final MediaView mediaView = createMediaView();
final MediaPlayer player = mediaView.getMediaPlayer();
final ProgressBar progress = new ProgressBar(0);
progress.setPrefWidth(800);
player.currentTimeProperty().addListener((observable) ->
progress.setProgress(
player.getCurrentTime().toMillis() /
player.getTotalDuration().toMillis()
)
);
StackPane layout = new StackPane();
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20px;");
layout.getChildren().setAll(
mediaView,
progress
);
stage.setScene(new Scene(layout));
stage.show();
}
private MediaView createMediaView() throws MalformedURLException {
String mediaURI = new File(MEDIA_PATH).toURI().toURL().toExternalForm();
Media media = new Media(mediaURI);
MediaPlayer player = new MediaPlayer(media);
MediaView mediaView = new MediaView(player);
player.play();
return mediaView;
}
}
Problem
I have a custom class called `TimeElapsed` (which is immutable). `TimeElapsed` has a constructor that takes in a `Duration` (which is the type used in JavaFx `MediaPlayer` to keep track of time). The constructor then converts the `Duration` to a `TimeElapsed`. The issue is that I have a function that needs to return a `TimeElapsed` `ObservableValue`. What I need is to be able to do something like this: ``` new Binding<TimeElapsed>() { { super.bind(player.duration()) } @Override protected TimeElapsed computeValue() { return new TimeElapsed(player.duration()); } } ``` But for some reason there is no `Binding` generic, and you can only do this with `DoubleBinding` and things like that, where you compute the value but can't select the type. So, what should I do?