0

Is there a thread safe way to read properties of the JavaFX MediaPlayer (such as CurrentTime)?

To play/change a media I typically use Platform.runLater, but I need the CurrentTime immediately returned.

Is it safe to do myplayer.getCurrentTime()? Or will I get into trouble if the player is disposed off by another thread?

EXAMPLE:

private MediaPlayer player;

public void playMe(){
    Platform.runLater(
        new Runnable() {
        @Override
        public void run() {
            player = new MediaPlayer(media);
            player.play();
    }});
}
public void deleteMe(){
    Platform.runLater(
        new Runnable() {
        @Override
        public void run() {
            if (player != null) player.dispose();
            player = null;
    }});
}

public Double getCurrentTime(){
    if (player != null) return player.getCurrentTime().toSeconds(); // thread issues???
    else return null;
}
Guillaume F.
  • 1,010
  • 7
  • 21
  • See: [How to create a Minimal, Reproducible Example.](https://stackoverflow.com/help/minimal-reproducible-example) – SedJ601 Sep 09 '19 at 13:45
  • Your code is not complete. Please read link above. – SedJ601 Sep 09 '19 at 15:31
  • Your link is not relevant - I'm not trying to reproduce a problem. – Guillaume F. Sep 09 '19 at 17:02
  • You code should be able to be copied. It should also reproduce the problem you are currently experiencing using the least amount of code. – SedJ601 Sep 09 '19 at 17:04
  • Your comment is not relevant - I'm not trying to reproduce a problem. – Guillaume F. Sep 09 '19 at 17:19
  • 1
    Make sure you really need to do this. Do everything on the JavaFX application thread unless you have a very good reason not do so. That way you don't have to worry about concurrency issues, which you may find difficult to deal with. – jewelsea Sep 09 '19 at 17:59
  • @GuillaumeF. But you _are_ asking for help, and seeing a minimal example of what you're trying to do can influence the contents of a potential answer. For instance, it may be completely unnecessary to use background threads in the first place. Regardless, fabian's answer and the linked duplicate should help you. – Slaw Sep 09 '19 at 21:35

1 Answers1

4

JavaFX properties are not thread-safe. Memory consistency is not guaranteed: Java is allowed to create a copy of the memory containing the property object for the background thread at any time. When this happens, any changes to the property happening later won't be visible to the background thread.

It's not too hard to make sure the value is accessible from the thread though. Depending on the frequency of the access and the delay you're willing accept for the information retrieval, the following approaches could work for you:

Updating a AtomicReference from a listener

This way you simply make sure the updates become visible to the background thread by assigning the value to a AtomicReference on the application thread:

final AtomicReference<Duration> time = new AtomicReference<>(player.getCurrentTime());
player.currentTimeProperty().addListener((o, oldValue, newValue) -> time.set(newValue));

Thread t = new Thread(() -> {
    while (true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
        }
        System.out.println(time.get());
    }
});
t.setDaemon(true);
t.start();

The drawback is that the updates to the reference happen more often than necessary. (A volatile field may do the trick too btw.)

Querying the property using Platform.runLater

As alternative you could simply schedule a runnable reading the variable using Platform.runLater. This approach does not require a reference to be continuously updated:

Thread t = new Thread(() -> {
    while (true) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
        }
        CompletableFuture<Duration> future = new CompletableFuture<>();
        
        Platform.runLater(() -> future.complete(player.getCurrentTime()));
        try {
            Duration time = future.get(); // get value as soon as evaluated on application thread
            System.out.println(time);
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
});
t.setDaemon(true);
t.start();

Note: for both approaches you need to deal with the fact that you may set the player field to null. Note that any test is subject to the same memory consistency issues as described at the start of the answer. The first approach would require you to make the field volatile to make sure the change is visible to the background thread too, for the second approach you could either cancel the future or throw cause an exception to notify the caller: future.completeExceptionally and future.cancel(true) result in future.get() yielding an ExecutionException or a CancelationException respecively.

Community
  • 1
  • 1
fabian
  • 80,457
  • 12
  • 86
  • 114