Using new AudioClip(s) with s an external UI String will download the clip from a remote server. This can take several seconds.
If this is done on the JavaFX Application thread, the application will likely become unresponsive.
What is the preferred solution to prevent this long download from being on the JavaFX App thread?
Example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.media.AudioClip;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
long time = System.currentTimeMillis();
AudioClip sound = new AudioClip("https://dl.dropboxusercontent.com/s/qq8vvfqx2l8sljb/Dice%201.wav?dl=0");
System.out.println("Time taken (ms): " + (System.currentTimeMillis() - time));
primaryStage.setScene(new Scene(new StackPane(), 300, 250));
primaryStage.show();
}
}
Creating the AudioClip this way freezes the JavaFX thread for around 2-3 seconds on my computer.
any operation that is expected to take more than some milliseconds needs to be moved out of the JavaFX Application Thread, because otherwise, it will freeze your UI until it's done, if I were to load an audio file from a remote URL I would do it in a new Thread as follows :
import java.util.concurrent.ExecutionException;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.StackPane;
import javafx.scene.media.AudioClip;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
StackPane root = new StackPane(new ProgressIndicator());
Button play = new Button("play");
Task<AudioClip> loadAudio = new Task<AudioClip>() {
#Override
protected AudioClip call() throws Exception {
return new AudioClip("https://dl.dropboxusercontent.com/s/qq8vvfqx2l8sljb/Dice%201.wav?dl=0");
}
};
loadAudio.setOnSucceeded(successEvent -> {
play.setOnAction(actionEvent -> {
try {
loadAudio.get().play();
} catch (InterruptedException | ExecutionException x) {
//Handle Exceptions
x.printStackTrace();
}
});
root.getChildren().setAll(play);
});
new Thread(loadAudio).start();
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
}
}
Note that in some cases, your app will need to indicate to the users that it is doing something in the background and they will need to wait until the operation is done, in this example I used a simple progress indicator while the audio is being downloaded from the remote URL.
note: I edited the solution using Task (the previous version works fine as well as this version as I have tested)
Related
my apologies if this is an easy thing for you, but my mind boggles. After several years of not programming at all, I am working on a pet project (2d tile based game engine) where I would like to use Java FX headless in order to make use of the graphics capabilities.
I have understood from here and here
that you need to a Java FX Application in order to have the graphics system initialized.
So I basically took the ImageViewer example and implemented Runnable:
package net.ck.game.test;
import java.io.BufferedReader;
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Logger;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class ImageTest extends Application implements Runnable {
protected static final Logger logger = (Logger) LogManager.getLogger(ImageTest.class);
BufferedReader x;
#Override public void start(#SuppressWarnings("exports") Stage stage) {
logger.error(Thread.currentThread().getName() + ", executing run() method!");
Image standardImage = new Image("file:graphics/image1.png");
logger.error("image height image1: "+ standardImage.getHeight());
logger.error("image width image1:" + standardImage.getWidth());
Image movingImage = new Image("file:graphics/image2.png");
ArrayList<Image> images = new ArrayList<Image>();
images.add(movingImage);
images.add(standardImage);
ImageView iv1 = new ImageView();
iv1.setImage(standardImage);
ImageView iv2 = new ImageView();
iv2.setImage(movingImage);
Group root = new Group();
Scene scene = new Scene(root);
scene.setFill(Color.BLACK);
HBox box = new HBox();
box.getChildren().add(iv1);
box.getChildren().add(iv2);
root.getChildren().add(box);
stage.setTitle("ImageView");
stage.setWidth(415);
stage.setHeight(200);
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void run()
{
Application.launch(ImageTest.class);
}
}
When I run this as its own application, this works fine and displays the two images I want it to display.
When I run it like this in the "game" constructor:
public class Game {
private boolean animated;
public boolean isAnimated() {
return animated;
}
public void setAnimated(boolean animated) {
this.animated = animated;
}
public Game() {
setAnimated(true);
if (isAnimated() == true)
{
ImageTest imageTest = new ImageTest();
new Thread(imageTest).start();
}
}
There are no errors, ImageTest runs in its own thread, the application window opens, but it is empty.
I do not understand this at all, why is that?
Can someone pleaese shed some light on this?
UPDATE:
I had different working contexts by accident. Fixing this fixed the problem.
UPDATE: I had different working contexts by accident. Fixing this fixed the problem.
This question already has answers here:
JavaFX periodic background task
(6 answers)
Closed 2 years ago.
Following is the code
package sample;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Change me");
primaryStage.setScene(scene);
primaryStage.show();
for(long i=0;i<3;i++){
primaryStage.setTitle("Change title to "+i);
try{
Thread.sleep(1000);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
The intention is to start the window and then change the title, but what happens is that first the loop executes and then window is shown.
Apart from this, Using normal thread for computations and JavaFX thread(i.e., Platform.runlater()) for UI updation is another solution.
Using this tutorial, I was able to complete the task. The reason is directly quoted from the tutorial -
It is repeated to emphasize that all UI event handlers in JavaFX run on a single thread, which is the JavaFX Application Thread.
New code is
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
Scene scene = new Scene(root);
primaryStage.setTitle("Change me");
primaryStage.setScene(scene);
primaryStage.show();
System.out.println("shown");
Thread thread = new Thread(){
public void run(){
for (long i = 0; i < 6; i++) {
long finalI = i;
Platform.runLater(()-> {
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
Date date = new Date();
System.out.println(dateFormat.format(date));
primaryStage.setTitle("Change title to " + finalI);
});
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
thread.start();
}
}
the program is a system that shows media: images, videos and keeps alternating between them. the problem is the use of increasing memory: after the programming running for 30 minutes, it consumes 1.2gb of ram
I do not have much idea of what I can do, I believe that the reason for the increasing memory consumption would be recursion (the function calls itself) or the fact that every time it gives a picture it creates a thread, and when it video it uses the technically 'correct' which is a runnable (.setOnEndOfMedia ())
Remembering that I can not use timer / timeline, because I have videos with different durations, this way would work with image
package testevideo2;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TesteVideo2 extends Application{
StackPane stack = new StackPane();
int xImagem = 0;
int xVideo = 0;
public void start(Stage primaryStage) throws Exception {
//primaryStage.setScene(videoCena);
primaryStage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
primaryStage.setFullScreen(true);
primaryStage.setTitle("Titulo bonito");
primaryStage.show();
proximo(primaryStage);
/*player.play();
player.setOnEndOfMedia(new Runnable() { //Classe Anônima
#Override
public void run() {
primaryStage.setScene(imagemCena);
//primaryStage.centerOnScreen();
}
});*/
}
private void proximo(Stage primaryStage){
//valores serao pego da api...
boolean[] eVideo = {false, false, true, false, true};
String[] nomeImagens = {"doido.jfif", "eu.jpg", "resultado.jpg", "37Teste.jpg"};
String[] nomeVideos = {"xx.mp4", "carinha.mp4"};
final String diretorioBase = "file:/C:/Users/Thiago/Desktop/arquivos_projetoandre/";
if(xImagem + xVideo < eVideo.length){
//look if the next file is a video or an image
if(eVideo[xImagem + xVideo]){
//criador de video
Media media = new Media(diretorioBase + nomeVideos[xVideo]);
MediaPlayer player = new MediaPlayer(media);
Scene videoCena = new Scene(new Group(new MediaView(player)), 1366, 720);
videoCena.setCursor(Cursor.NONE);
player.play();
player.setOnEndOfMedia(new Runnable() { //Classe Anônima
#Override
public void run() {
proximo(primaryStage);
//primaryStage.centerOnScreen();
}
});
primaryStage.setScene(videoCena);
xVideo++;
} else {
//criador de imagem
Pane pane = new HBox();
Image img = new Image(diretorioBase + nomeImagens[xImagem]);
pane.getChildren().add(new ImageView(img));
Scene imagemCena = new Scene(pane, 1366, 720);
//PROBABLY PROBLEM HERE --- CREATE A NEW THREAD ONLY TO WAIT 4 SECONDS
Thread a = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(4000);
//force to the application run on 'javaFx thread'
Platform.runLater(new Runnable(){
#Override
public void run() {
proximo(primaryStage);
}
});
} catch (InterruptedException ex) {
Logger.getLogger(TesteVideo2.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
a.start();
primaryStage.setScene(imagemCena);
xImagem++;
//Thread.sleep(4000);
//proximo(primaryStage);
}
} else {
xVideo = 0;
xImagem = 0;
proximo(primaryStage);
}
}
public static void main(String [] args) {
Application.launch();
}
}
I hope it does the same function as it is now, except in a way that the use of processing is increasing over time, because this application will run for hours ...
You need to call dispose() on your MediaPlayer object if you stop using it to free all its resources.
Also make sure your Java version is 8 or higher (there is a memory leak in older versions).
I'm writing a code for a bouncing ball program and everything has worked so far but I don't know how to set a background image for my stage. I'm beginner in java (well , obviously) and tried to use ImageView class to set a background for my stage. but no luck so far.
Thank you so much .
package particlesystemexample;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
public class ParticleSystemExample extends Application {
#Override
public void start(Stage primaryStage) {
ParticleAnimationPane paPane = new ParticleAnimationPane();
// mouse actions to pause and resume animation
//paPane.setOnMousePressed(e -> paPane.pause()); // Java 8.0
paPane.setOnMousePressed(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
paPane.init(); }
});
// paPane.setOnMouseReleased(e -> paPane.play()); // Java 8.0
paPane.setOnMouseReleased(new EventHandler<MouseEvent>() {
public void handle(MouseEvent me) {
paPane.play(); }
});
// buttons to increase and decrease animation speed
paPane.setOnKeyPressed(e -> {
if(e.getCode() == KeyCode.UP){
paPane.increaseSpeed();
}
else if (e.getCode() == KeyCode.DOWN){
paPane.decreaseSpeed();
}
});
// Create a scene and place it on stage
Scene scene = new Scene(paPane, 450, 350);
primaryStage.setTitle("Particle System Example");
primaryStage.setScene(scene);
primaryStage.show();
paPane.init(); // initialize the particles
paPane.requestFocus();
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
Edit :
I tried using this code to display the image but its not working ...
StackPane sp = new StackPane();
Image img = new Image("http://mikecann.co.uk/wp-content/uploads/2009/12/javafx_logo_color_1.jpg");
ImageView imgView = new ImageView(img);
sp.getChildren().add(imgView);
Personally, I use the BufferedImage and Image classes to deal with all of my image needs.
You can use the Image class for this one.
Here is an example:
try {
Graphics.drawImage(ImageIO.read(new File("Picture.png")), fX, fY, fW, fH, sX, sY, sW, sH,null);
} catch (IOException e) {
e.printStackTrace();
}
The things starting with F are the bounds for the rectangle that you will be painting it on.
The things starting with s are the bounds for what part of the picture you are painting.
I'm not exactly sure what the null at the end corresponds to. I just ignore it and everything works out fine.
I've recently started playing around with JavaFX :) I'm working on a little pet project and one of the issues I'm currently having is animating the resize of a VBox when a child is added or removed to/from the VBox.
I've got "the kids" fading out and then being removed from the VBox already. Once that animation completes I need the VBox height to resize preferably as an animation and not like the instant change it currently does.
The other threads that I've found are similar but I think they aren't quite exactly what I'm looking for.
Animation upon layout changes
Adding Node's animated to a VBox
Main Class:
package application;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCombination;
import javafx.scene.paint.Color;
import application.notifier.*;
public class Main extends Application
{
#Override
public void start(Stage stage)
{
try
{
stage.setTitle("Test");
Group root = new Group();
Scene scene = new Scene(root, (Screen.getPrimary().getBounds().getWidth()-100), (Screen.getPrimary().getBounds().getHeight()-100), Color.WHITE);
Notifier notice = new Notifier();
Notifier.addNotice("Testing Add Notice");
Notifier.addNotice("Testing Add Notice again!");
root.getChildren().add(Notifier.container);
stage.setFullScreen(true);
stage.setScene(scene);
stage.setFullScreenExitHint("");
//stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
stage.show();
Button test = new Button("Remove");
test.setLayoutX(500.0);
test.setLayoutY(500.0);
test.setOnAction(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.removeNotice();
}
});
root.getChildren().add(test);
}
catch(Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
launch(args);
}
}
Notifier Class:
import java.util.ArrayList;
import javafx.animation.FadeTransition;
import javafx.animation.Timeline;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Screen;
import javafx.util.Duration;
public class Notifier
{
private static int maxVisibleNotices = 5;
private static int currentVisible = 0;
private static ArrayList<String> msgOverflow;
public static VBox container;
public Notifier()
{
BackgroundFill bkgrndFill = new BackgroundFill(Color.rgb(0, 0, 0, .65), new CornerRadii(10.0), new Insets(0));
Background bkgrnd = new Background(bkgrndFill);
Notifier.container = new VBox();
Notifier.container.backgroundProperty().set(bkgrnd);
Notifier.container.setAlignment(Pos.TOP_CENTER);
Notifier.container.setMinWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setMaxWidth(Screen.getPrimary().getBounds().getWidth() - 50);
Notifier.container.setLayoutX((Screen.getPrimary().getBounds().getWidth() - (Screen.getPrimary().getBounds().getWidth() - 50))/2);
Notifier.container.setLayoutY(5.0);
Notifier.container.setSpacing(5.0);
Notifier.container.setPadding(new Insets(5.0));
Notifier.msgOverflow = new ArrayList<String>();
}
public static void addNotice(String msg)
{
if(Notifier.currentVisible < Notifier.maxVisibleNotices)
{
Text txt = new Text(msg);
txt.setFill(Color.rgb(255,255,255));
Notifier.container.getChildren().add(txt);
Notifier.currentVisible++;
}
else
{
Notifier.msgOverflow.add(msg);
}
}
public static void removeNotice()
{
if(Notifier.currentVisible > 0)
{
FadeTransition ft = new FadeTransition(Duration.millis(1000), Notifier.container.getChildren().get(0));
ft.setFromValue(1.0);
ft.setToValue(0.0);
ft.setCycleCount(0);
ft.setAutoReverse(false);
ft.play();
ft.setOnFinished(new EventHandler<ActionEvent>() {
#Override public void handle(ActionEvent e) {
Notifier.container.getChildren().remove(0);
Notifier.currentVisible--;
}
});
}
}
}
I hope this is clear enough.
And thanks in advance for help or suggestions.
Probably not very helpful any more. I am currently working on the same thing.
You just need:
1. Add the same animation(if they all supposed to be same) to each of the remaining children in the VBox.
2. Use ParallelAnimation to make them run at the same time.
3. Use again a SequentialAnimation for you "kids" fading out animation and the parallel animation. This ensures they will not happen concurrently since multiple threads could run simultaneously.
4. To reach still the same view as what the instant update does, use animation/timeline.setonFinished(EventHandler()) to updates the screen