How to play clean audio loops and one-shot sounds in parallel in JavaFX 2.0? - javafx-2

I'm trying to play background audio in a loop in a JavaFX 2.0 application using JavaFX SDK 2.0.1. I decided to use a MediaPlayer created by the following piece of code:
MediaPlayerBuilder
.create().media(BACKGROUND_MEDIA)
.cycleCount(MediaPlayer.INDEFINITE);
This basically works, but when a new cycle starts there is a tiny (latency?) gap between the end and the start of the audio. So it's not a working option for me since it's not playing a clean loop.
I decided to build a new MediaPlayer object and start playback everytime Media ends. This works fine so far. Additionally, I use a button playing a short AudioClip when clicked.
I discoverd that frequent and fast clicking this button leads to interrupts in the background audio. I created an example to reproduce this behaviour by inifinitely playing an AudioClip with volume 0 when the button is clicked once. The example is not self contained, since the required audio files are missing. It requires to place 2 audio files in the project's source directory:
click.wav (a really short click sound ~300ms)
background.wav (~5 seconds of audio)
How do I achieve playing a clean audio loop in background without these interrupts when other one-shot audio sounds are played? Is it just a performance issue?
Example:
package mediatest;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayerBuilder;
import javafx.stage.Stage;
public class MediaTest extends Application {
private static final AudioClip CLICK_AUDIOCLIP = new AudioClip(MediaTest.class.getResource("/click.wav").toString());
private static final Media BACKGROUND_MEDIA = new Media(MediaTest.class.getResource("/background.wav").toString());
private MediaPlayerBuilder builder;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root, 300, 250);
this.builder = MediaPlayerBuilder
.create()
.media(BACKGROUND_MEDIA)
.onEndOfMedia(new Runnable() {
public void run() {
MediaPlayer player = MediaTest.this.builder.build();
player.play();
}
});
MediaPlayer player = this.builder.build();
player.play();
Button btn = new Button();
btn.setText("Repeat playing short audio clip");
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
//Simulation of many button clicks
MediaTest.CLICK_AUDIOCLIP.setCycleCount(AudioClip.INDEFINITE);
MediaTest.CLICK_AUDIOCLIP.play(0);
}
});
root.getChildren().add(btn);
primaryStage.setScene(scene);
primaryStage.show();
}
}

have you looked into ExecutorService? You would then have a number of predefined threads like so:
ExecutorService service = Executors.newFixedThreadPool(4);
where 4 is the number of threads it makes.
It will improve performance because it uses already made threads rather than making a new one each time you want to run something.
You would create a Runnable and execute it with the service like so:
Runnable r = new Runnable() {
#Override
public void run() {
playSound();
}
};
service.execute(r);
Not only would this improve performance but it automatically assigns the job to a not-currently-busy thread in its thread pool.
Also look at this: Playing sound loops using javafx which I believe solves your small latency problem.
EDIT: damn sorry, I didn't know this post was that old. It was a top result in google.

Using another thread at
public void handle(ActionEvent event) {
Platform.runLater(new Runnable()
{
#Override
public void run()
{
//Simulation of many button clicks
MediaTest.CLICK_AUDIOCLIP.setCycleCount(AudioClip.INDEFINITE);
MediaTest.CLICK_AUDIOCLIP.play(0);
}
});
}
});
May solve the problem of the interruption

Related

setText() not reflecting changes when called from another thread

I am a beginner in javaFX and am stuck in this one area. Any help will be appreciated a lot.
This is sample app I have made for clear understanding using scene builder. There is a text area and a button.I want to set data into the text area on the button click. The setting happens in another thread.
The code is as follows:
import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.stage.Stage;
public class TpController{
#FXML
private ScrollPane scrollPane;
#FXML
private Button button;
#FXML
public TextArea txtArea ;
private Stage stage;
public void setTextArea(TextArea txt)
{
this.txtArea = txt ;
}
public TextArea getTextArea()
{
return txtArea;
}
public void setStage(Stage stage)
{
this.stage = stage;
}
public Stage getStage()
{
return stage;
}
public void setTopText(String text) {
// set text from another class
txtArea.setText(text);
}
public void buttonHandler()
{
tpThread t = new tpThread();
t.start();
}
The tpThread class is as follows:
import java.io.IOException;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;
public class tpThread extends Thread {
#Override
public void run() {
// TODO Auto-generated method stub
FXMLLoader loader = new FXMLLoader(getClass().getResource("Justtp.fxml"));
try {
Parent root = (Parent) loader.load();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
TpController myController = loader.getController();
TextArea t = myController.getTextArea();
String data = "hi\nhello\nhow are you\nnice to meet you\nhahaha";
//System.out.println(t.setData("hi"));
myController.setTopText(data);
}
Instead of using setTopText, i have also directly used
t.setText(data);
But no use. My final output does nothing on the button click.
There are many issues with your code.
Modifications to the active scene graph off of the JavaFX application thread must be performed via Platform.runLater().
You don't need another thread to accomplish something on a button click.
You can just define an action handler for the button using setOnAction().
Event handler methods on controllers can also be cross-referenced in FXML via onAction="#handleButtonAction" where handleButtonAction is defined as a public void handleButtonAction(ActionEvent event) method in your controller.
All event handlers are invoked by the JavaFX runtime on the JavaFX application thread, so you don't need to worry about multi-threading when writing event handlers - the JavaFX event handling programming model is single threaded.
Loading an FXML as you do in your code and not attaching the resultant node to a scene is pointless as the user will never see anything that is not attached to a scene.
There may be other issues with your code which cause it not to work as you expect.
In general, for assistance debugging an issue, provide an mcve. Note it should be both minimal and complete so that somebody could copy and paste the code to replicate the issue (and pretty much nothing else).

How to redirect MouseEvent.MOUSE_PRESSED to a different Node?

I have a Region called 'R', and a Node called 'N'. N is the only child on R. Consider this behaviour:
the user presses the left-mouse button on some part of R
N (which is somewhere else on R) moves so that it is centered on the spot where the user pressed
the user releases the left-mouse button, then presses it again without moving the mouse
with the left-mouse button still pressed, the user drags the mouse, and N now follows the mouse cursor around as it is dragged.
I have no problems implementing the behaviour I've just described. I put a MOUSE_PRESSED handler on R that implements step 2. And I put a MOUSE_DRAGGED handler on N that implements step 4. JavaFX automatically directs the MouseEvents to these handlers on R and N for the presses in step 1 and 3 respectively.
The Problem:
I need to do this WITHOUT step 3. That is, the user should not have to press-release-press-drag, but rather should simply press-drag, and N should "jump" to the mouse location on the "press", and then start receiving MOUSE_DRAGGED events immediately.
Unfortunately, this doesn't happen. The release-click that I'm trying to omit seems to be necessary, otherwise the drag events all happen on R instead of N.
I'm thinking the solution will involve redispatching the initial MOUSE_PRESSED, or something along those lines. Does anyone know a way to do this (or a better way to solve my problem?)
Node has api to mark it as the target of drag gestures:
public void startFullDrag()
Starts a full press-drag-release gesture with this node as gesture
source. This method can be called only from a DRAG_DETECTED mouse
event handler. More detail about dragging gestures can be found in the
overview of MouseEvent and MouseDragEvent.
Assuming circle being your currently active node in a pane (borrowing code/names from James's answer), the collaborators are handlers on
mousePressed on pane that snaps the position of circle to the the current location
dragDetected on pane that calls startsFullDrag on circle
dragAny on circle that does the actual moving
In code:
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
pane.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.startFullDrag();
}
});
circle.addEventHandler(MouseDragEvent.ANY, new EventHandler<MouseDragEvent>() {
#Override
public void handle(MouseDragEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
I chose the simplest node to work with for this, but I think this would work in general:
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ClickAndDragTest extends Application {
#Override
public void start(Stage primaryStage) {
final Pane pane = new Pane();
final Circle circle = new Circle(100, 100, 50, Color.CORNFLOWERBLUE);
pane.getChildren().add(circle);
pane.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
pane.setOnMouseDragged(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
circle.setCenterX(event.getX());
circle.setCenterY(event.getY());
}
});
final Scene scene = new Scene(pane, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

JavaFX: How to bind two values?

I'm new guy here :)
I have a small problem which concerns binding in JavaFX. I have created Task which is working as a clock and returns value which has to be set in a special label (label_Time). This label presents how many seconds left for player's answer in quiz.
The problem is how to automatically change value in label using the timer task? I tried to link value from timer Task (seconds) to label_Time value in such a way...
label_Time.textProperty().bind(timer.getSeconds());
...but it doesn't work. Is it any way to do this thing?
Thanks in advance for your answer! :)
Initialize method in Controller class:
public void initialize(URL url, ResourceBundle rb) {
Timer2 timer = new Timer2();
label_Time.textProperty().bind(timer.getSeconds());
new Thread(timer).start();
}
Task class "Timer2":
public class Timer2 extends Task{
private static final int SLEEP_TIME = 1000;
private static int sec;
private StringProperty seconds;
public Timer2(){
Timer2.sec = 180;
this.seconds = new SimpleStringProperty("180");
}
#Override protected StringProperty call() throws Exception {
int iterations;
for (iterations = 0; iterations < 1000; iterations++) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
System.out.println("TIK! " + sec);
seconds.setValue(String.valueOf(sec));
System.out.println("TAK! " + seconds.getValue());
// From the counter we subtract one second
sec--;
//Block the thread for a short time, but be sure
//to check the InterruptedException for cancellation
try {
Thread.sleep(10);
} catch (InterruptedException interrupted) {
if (isCancelled()) {
updateMessage("Cancelled");
break;
}
}
}
return seconds;
}
public StringProperty getSeconds(){
return this.seconds;
}
}
Why your app does not work
What is happening is that you run the task on it's own thread, set the seconds property in the task, then the binding triggers an immediate update of the label text while still on the task thread.
This violates a rule for JavaFX thread processing:
An application must attach nodes to a Scene, and modify nodes that are already attached to a Scene, on the JavaFX Application Thread.
This is the reason that your originally posted program does not work.
How to fix it
To modify your original program so that it will work, wrap the modification of the property in the task inside a Platform.runLater construct:
Platform.runLater(new Runnable() {
#Override public void run() {
System.out.println("TIK! " + sec);
seconds.setValue(String.valueOf(sec));
System.out.println("TAK! " + seconds.getValue());
}
});
This ensures that when you write out to the property, you are already on the JavaFX application thread, so that when the subsequent change fires for the bound label text, that change will also occur on the JavaFX application thread.
On Property Naming Conventions
It is true that the program does not correspond to JavaFX bean conventions as Matthew points out. Conforming to those conventions is both useful in making the program more readily understandable and also for making use of things like the PropertyValueFactory which reflect on property method names to allow table and list cells to automatically update their values as the underlying property is updated. However, for your example, not following JavaFX bean conventions does not explain why the program does not work.
Alternate Solution
Here is an alternate solution to your countdown binding problem which uses the JavaFX animation framework rather than the concurrency framework. I prefer this because it keeps everything on the JavaFX application thread and you don't need to worry about concurrency issues which are difficult to understand and debug.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.event.*;
import javafx.geometry.Pos;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CountdownTimer extends Application {
#Override public void start(final Stage stage) throws Exception {
final CountDown countdown = new CountDown(10);
final CountDownLabel countdownLabel = new CountDownLabel(countdown);
final Button countdownButton = new Button(" Start ");
countdownButton.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent t) {
countdownButton.setText("Restart");
countdown.start();
}
});
VBox layout = new VBox(10);
layout.getChildren().addAll(countdownLabel, countdownButton);
layout.setAlignment(Pos.BASELINE_RIGHT);
layout.setStyle("-fx-background-color: cornsilk; -fx-padding: 20; -fx-font-size: 20;");
stage.setScene(new Scene(layout));
stage.show();
}
public static void main(String[] args) throws Exception {
launch(args);
}
}
class CountDownLabel extends Label {
public CountDownLabel(final CountDown countdown) {
textProperty().bind(Bindings.format("%3d", countdown.timeLeftProperty()));
}
}
class CountDown {
private final ReadOnlyIntegerWrapper timeLeft;
private final ReadOnlyDoubleWrapper timeLeftDouble;
private final Timeline timeline;
public ReadOnlyIntegerProperty timeLeftProperty() {
return timeLeft.getReadOnlyProperty();
}
public CountDown(final int time) {
timeLeft = new ReadOnlyIntegerWrapper(time);
timeLeftDouble = new ReadOnlyDoubleWrapper(time);
timeline = new Timeline(
new KeyFrame(
Duration.ZERO,
new KeyValue(timeLeftDouble, time)
),
new KeyFrame(
Duration.seconds(time),
new KeyValue(timeLeftDouble, 0)
)
);
timeLeftDouble.addListener(new InvalidationListener() {
#Override public void invalidated(Observable o) {
timeLeft.set((int) Math.ceil(timeLeftDouble.get()));
}
});
}
public void start() {
timeline.playFromStart();
}
}
Update for additional questions on Task execution strategy
Is it possible to run more than one Task which includes a Platform.runLater(new Runnable()) method ?
Yes, you can use multiple tasks. Each task can be of the same type or a different type.
You can create a single thread and run each task on the thread sequentially, or you can create multiple threads and run the tasks in parallel.
For managing multiple tasks, you can create an overseer Task. Sometimes it is appropriate to use a Service for managing the multiple tasks and the Executors framework for managing multiple threads.
There is an example of a Task, Service, Executors co-ordination approach: Creating multiple parallel tasks by a single service In each task.
In each task you can place no runlater call, a single runlater call or multiple runlater calls.
So there is a great deal of flexibility available.
Or maybe I should create one general task which will be only take data from other Tasks and updating a UI?
Yes you can use a co-ordinating task approach like this if complexity warrants it. There is an example of such an approach in in Render 300 charts off screen and save them to files.
Your "Timer2" class doesn't conform to the JavaFX bean conventions:
public String getSeconds();
public void setSeconds(String seconds);
public StringProperty secondsProperty();

Create new Tooltip on not JavaFX Application Thread

is there a way how to create javafx.scene.control.Tooltip in thread which is not "JavaFX Application Thread"?
Because of performance I prepare some visual elements in one thread and keep them in memory. And when I need them I use Application Thread for showing them. But new Tooltip() seems to require Application Thread only.
is there a way how to create javafx.scene.control.Tooltip in thread which is not "JavaFX Application Thread"?
For JavaFX 2.2 - No.
See this JavaFX issue tracker tiecket: Tooltip of Tab (and other controls) cannot be set of FXApplicationThread.
There is a suggested workaround in the ticket to create the Tooltip in a Platform.runLater call.
package fxthread_25127_wrongthread;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.control.Tooltip;
import javafx.stage.Stage;
/**
*
* #author mickf
*/
public class Main extends Application {
Tooltip tooltip;
public static void main(String[] args) {
Application.launch(args);
}
#Override public void init() throws Exception {
Platform.runLater(new Runnable() {
#Override public void run() {
tooltip = new Tooltip("Top tip : use runLater()");
}
});
/*
* this will cause an ISE
*/
//tooltip = new Tooltip("flip");
}
#Override public void start(Stage s) {
System.exit(0);
}
}
A description of how Platform.runLater works from it's javadoc:
Run the specified Runnable on the JavaFX Application Thread at some unspecified time in the future. This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller. The Runnables are executed in the order they are posted. A runnable passed into the runLater method will be executed before any Runnable passed into a subsequent call to runLater.

How to remove JavaFX stage buttons (minimize, maximize, close)

How to remove JavaFX stage buttons (minimize, maximize, close)? Can't find any according Stage methods, so should I use style for the stage? It's necessary for implementing Dialog windows like Error, Warning, Info.
If you want to disable only the maximize button then use :
stage.resizableProperty().setValue(Boolean.FALSE);
or if u want to disable maximize and minimize except close use
stage.initStyle(StageStyle.UTILITY);
or if you want to remove all three then use
stage.initStyle(StageStyle.UNDECORATED);
You just have to set a stage's style. Try this example:
package undecorated;
import javafx.application.Application;
import javafx.stage.StageStyle;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class UndecoratedApp extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
primaryStage.initStyle(StageStyle.UNDECORATED);
Group root = new Group();
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
}
When learning JavaFX 2.0 these examples are very helpful.
primaryStage.setResizable(false);
primaryStage.initStyle(StageStyle.UTILITY);
I´m having the same issue, seems like an undecorated but draggable/titled window (for aesthetic sake) is not possible in javafx at this moment. The closest approach is to consume the close event.
stage.setOnCloseRequest(new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent event) {
event.consume();
}
});
If you like lambdas
stage.setOnCloseRequest(e->e.consume());
stage.initModality(Modality.APPLICATION_MODAL);
stage.setResizable(false);
I found this answer here -->
http://javafxportal.blogspot.ie/2012/03/to-remove-javafx-stage-buttons-minimize.html
We can do it:
enter code here
#Override
public void start(Stage primaryStage) {
primaryStage.initStyle(StageStyle.UNDECORATED);
Group root = new Group();
Scene scene = new Scene(root, 100, 100);
primaryStage.setScene(scene);
primaryStage.show();
}
stage.initStyle(StageStyle.DECORATED);
stage.setResizable(false);
You can achieve this, you call the following methods on your stage object
stage.initModality(Modality.APPLICATION_MODAL); // makes stage act as a modal
stage.setMinWidth(250); // sets stage width
stage.setMinHeight(250); // sets stage height
stage.setResizable(false); // prevents resize and removes minimize and maximize buttons
stage.showAndWait(); // blocks execution until the stage is closed

Resources