Anton
Anton

Reputation: 39

JavaFX, MediaPlayer - volume trouble! Why the volume of mediaPlayer do not changing bit by bit?

I setVolume to 0.0, and then change the volume bit by bit in the while loop. Yet, the volume jumps from 0.0 to 1.0 ? How can I change the volume smoothly? I tried

public class EngineSound extends Application {

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        mp3File = new File(metronom);
        media = new Media(mp3File.toURI().toURL().toString());
        mediaPlayer = new MediaPlayer(media);
        mediaView = new MediaView(mediaPlayer);
        mediaPlayer.play();
        mediaPlayer.setVolume(0.0);
        slider = new Slider();

        slider.valueProperty().addListener(new ChangeListener<Number>() {
            public void changed(ObservableValue<? extends Number> ov, Number old_val, Number new_val) {
                EventQueue.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        mediaPlayer.setVolume(slider.getValue());

                    }
                });
            }
        });

        double count = 1;
        while (count != 101) {
            for (int i = 0; i < 100000000; i++) {

            }
            slider.setValue(count / 100);
            count++;
            System.out.println(mediaPlayer.getVolume());
        }
    }
}

Upvotes: 3

Views: 6977

Answers (1)

jewelsea
jewelsea

Reputation: 159576

There are a few things wrong with your code.

  • JavaFX code should be executed on the JavaFX Application Thread, not the Swing event dispatch thread.

Instead of using EventQueue.invokeLater to execute on the Swing thread, use Platform.runLater to execute on the JavaFX thread.

  • There is no reason to use the Swing event dispatch thread at all.

Your program only makes use of JavaFX controls, so don't run anything on the Swing thread.

  • You usually don't need any thread switching calls in a ChangeListener.

Even though using EventQueue.invokeLater is wrong, in this case you don't even need to Platform.runLater either as only the JavaFX application thread should be modifying the Slider value anyway. There is a rule you can see in the JavaFX Node documentation:

An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.

  • Don't do busy waiting on the JavaFX Application Thread.

The loop where you count to one hundred million will just block the application thread resulting in a frozen UI as control will never be returned to the framework to update the UI.

  • Don't change a slider value in a loop.

Once you set a value on a UI control, you must return control back to the JavaFX framework to allow the value change to be reflected in the control and to the user.

Try the following code which addresses all of the above issues through the use of Timeline and Binding.

import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.event.*;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.media.*;
import javafx.stage.Stage;
import javafx.util.Duration;

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

  private static final Duration FADE_DURATION = Duration.seconds(2.0);

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

  @Override public void start(Stage stage) throws Exception {
    final MediaPlayer mediaPlayer = new MediaPlayer(
      new Media(
        MEDIA_URL
      )
    );
    final MediaView mediaView = new MediaView(mediaPlayer);

    HBox layout = new HBox(5);
    layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 10;");
    layout.getChildren().addAll(
      createVolumeControls(mediaPlayer), 
      mediaView
    );
    stage.setScene(new Scene(layout, 650, 230));
    stage.show();           

    mediaPlayer.play();
  }

  public Region createVolumeControls(final MediaPlayer mediaPlayer) {
    final Slider volumeSlider = new Slider(0, 1, 0);
    volumeSlider.setOrientation(Orientation.VERTICAL);

    mediaPlayer.volumeProperty().bindBidirectional(volumeSlider.valueProperty());

    final Timeline fadeInTimeline = new Timeline(
      new KeyFrame(
        FADE_DURATION,
        new KeyValue(mediaPlayer.volumeProperty(), 1.0)
      )
    );

    final Timeline fadeOutTimeline = new Timeline(
      new KeyFrame(
        FADE_DURATION,
        new KeyValue(mediaPlayer.volumeProperty(), 0.0)
      )
    );

    Button fadeIn = new Button("Fade In");
    fadeIn.setOnAction(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent t) {
        fadeInTimeline.play();
      }
    });
    fadeIn.setMaxWidth(Double.MAX_VALUE);

    Button fadeOut = new Button("Fade Out");
    fadeOut.setOnAction(new EventHandler<ActionEvent>() {
      @Override public void handle(ActionEvent t) {
        fadeOutTimeline.play();
      }
    });
    fadeOut.setMaxWidth(Double.MAX_VALUE);

    VBox controls = new VBox(5);
    controls.getChildren().setAll(
      volumeSlider,
      fadeIn,
      fadeOut
    );
    controls.setAlignment(Pos.CENTER);
    VBox.setVgrow(volumeSlider, Priority.ALWAYS);

    controls.disableProperty().bind(
      Bindings.or(
        Bindings.equal(Timeline.Status.RUNNING, fadeInTimeline.statusProperty()),
        Bindings.equal(Timeline.Status.RUNNING, fadeOutTimeline.statusProperty())
      )
    );

    return controls;
  }
}

The code controls a Video, but making it do audio only is just a matter of setting the Media URL to an audio only format such as mp3 or aac.

volumecontrolsample

Upvotes: 3

Related Questions