JavaFX Media Player - Binding Progress bar with Media Player (Mac m1 Silicon) - macos

I want to update Progress Bar with Media Player Playing. But, after start playing my progressBar fill 100% within one second while the media is 15 seconds - 5 minutes long. I can't figure out the cause.
My codes are as follows:
public static ProgressBar progress = new ProgressBar();
ObjectBinding<TimeElapsed> elapsedBinding =createElapsedBindingByBindingsAPI(player);
DoubleBinding elapsedDoubleBinding =createDoubleBindingByBindingsAPI(elapsedBinding);
progress.progressProperty().bind(elapsedDoubleBinding);
And The methods are :
public static #NotNull ObjectBinding<TimeElapsed> createElapsedBindingByBindingsAPI(
final #NotNull MediaPlayer player
) {
return Bindings.createObjectBinding(
new Callable<TimeElapsed>() {
#Override
public TimeElapsed call() throws Exception {
return new TimeElapsed(player.getCurrentTime());
}
},
player.currentTimeProperty()
);
}
public static #NotNull DoubleBinding createDoubleBindingByBindingsAPI(
final ObjectBinding<TimeElapsed> elapsedBinding
) {
return Bindings.createDoubleBinding(
new Callable<Double>() {
#Override
public Double call() throws Exception {
return elapsedBinding.getValue().getElapsed();
}
},
elapsedBinding
);
}
And the TimeElapsed class :
static class TimeElapsed {
private final double elapsed;
TimeElapsed(#NotNull Duration duration) {
elapsed = duration.toSeconds();
}
public double getElapsed() {
return elapsed;
}
}
So, what's the code changes that 1) update the progressBar with Playing, and 2) seek the song with progress bar clicked or dragged?

The progress of a ProgressBar should be, when determinate, between the values of 0.0 and 1.0 (inclusive). This means you should be dividing the current time by the total duration to get the progress and bind the progress property of the bar to that value. Note that the duration of a Media is observable and is pretty much guaranteed to be set some time after it was instantiated.
As for being able to seek when the progress bar is clicked or dragged, the simplest way—which is what I show in the example below—is to add a MOUSE_CLICKED and a MOUSE_DRAGGED handler to the progress bar, determine the ratio between the mouse's x position and the bar's width, and then seek the calculated time. Unfortunately, this setup may not exactly match up with the visuals of the progress bar because the actual "bar" is smaller than the entire space taken up by the node (at least with default styling). You would probably have to create your own control if you want "pixel perfect" behavior.
Here is a minimal example demonstrating what's discussed above:
import java.util.Optional;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ProgressBar;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaView;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
#Override
public void start(Stage primaryStage) {
var mediaView = new MediaView();
var progressBar = new ProgressBar();
progressBar.setMaxWidth(Double.MAX_VALUE);
StackPane.setAlignment(progressBar, Pos.BOTTOM_CENTER);
StackPane.setMargin(progressBar, new Insets(10));
var root = new StackPane(mediaView, progressBar);
primaryStage.setScene(new Scene(root, 1000, 650));
primaryStage.setTitle("Video Progress Demo");
primaryStage.show();
chooseMediaFile(primaryStage)
.ifPresentOrElse(
uri -> {
var media = new Media(uri);
var mediaPlayer = new MediaPlayer(media);
mediaPlayer.setAutoPlay(true);
mediaView.setMediaPlayer(mediaPlayer);
bindProgress(mediaPlayer, progressBar);
addSeekBehavior(mediaPlayer, progressBar);
},
Platform::exit);
}
private void bindProgress(MediaPlayer player, ProgressBar bar) {
var binding =
Bindings.createDoubleBinding(
() -> {
var currentTime = player.getCurrentTime();
var duration = player.getMedia().getDuration();
if (isValidDuration(currentTime) && isValidDuration(duration)) {
return currentTime.toMillis() / duration.toMillis();
}
return ProgressBar.INDETERMINATE_PROGRESS;
},
player.currentTimeProperty(),
player.getMedia().durationProperty());
bar.progressProperty().bind(binding);
}
private void addSeekBehavior(MediaPlayer player, ProgressBar bar) {
EventHandler<MouseEvent> onClickAndOnDragHandler =
e -> {
var duration = player.getMedia().getDuration();
if (isValidDuration(duration)) {
var seekTime = duration.multiply(e.getX() / bar.getWidth());
player.seek(seekTime);
e.consume();
}
};
bar.addEventHandler(MouseEvent.MOUSE_CLICKED, onClickAndOnDragHandler);
bar.addEventHandler(MouseEvent.MOUSE_DRAGGED, onClickAndOnDragHandler);
}
private boolean isValidDuration(Duration d) {
return d != null && !d.isIndefinite() && !d.isUnknown();
}
private Optional<String> chooseMediaFile(Stage owner) {
var chooser = new FileChooser();
chooser
.getExtensionFilters()
.add(new FileChooser.ExtensionFilter("Media Files", "*.mp4", "*.mp3", "*.wav"));
var file = chooser.showOpenDialog(owner);
return Optional.ofNullable(file).map(f -> f.toPath().toUri().toString());
}
}

Related

How to animate several nodes with pause between each one?

I am trying to animate a series of nodes one after the other in a loop. The goal is to have the first node begin its animation, followed by a short pause before the next node begins to animate.
However, when running this within a loop, it executes too fast and all nodes appear to be animating at the same time.
For simplicity, I am using the AnimateFX library to handle the animations, but I assume the functionality needed here would apply in other situations?
How would I add a pause between each of the HBox animations?
import animatefx.animation.Bounce;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class AnimationTest extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
final VBox root = new VBox(10);
root.setAlignment(Pos.CENTER);
final HBox tiles = new HBox(5);
tiles.setAlignment(Pos.CENTER);
// Create 5 tiles
for (int i = 0; i < 5; i++) {
HBox tile = new HBox();
tile.setPrefSize(50, 50);
tile.setStyle("-fx-border-color: black; -fx-background-color: lightblue");
tiles.getChildren().add(tile);
}
Button button = new Button("Animate");
button.setOnAction(event -> {
// Animate each tile, one at a time
for (Node child : tiles.getChildren()) {
Bounce animation = new Bounce(child);
animation.play();
}
});
root.getChildren().add(tiles);
root.getChildren().add(button);
primaryStage.setWidth(500);
primaryStage.setHeight(200);
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
}
I don't know AnimateFX, but using the standard libraries you can add animations to a SequentialTransition.
For example, to animate each node but starting at a later time, add PauseTransitions of increasing duration and the desired animation to SequentialTransitions, and play the SequentialTransitions.
As I said, I'm not familiar with the library you're using, but I think it would look like this:
Button button = new Button("Animate");
button.setOnAction(event -> {
Duration offset = Duration.millis(500);
Duration start = new Duration();
// Animate each tile, one at a time
for (Node child : tiles.getChildren()) {
Bounce bounce = new Bounce(child);
PauseTransition delay = new PauseTransition(start);
SequentialTransition animation = new SequentialTransition(delay, bounce.getTimeline());
animation.play();
start = start.add(offset);
}
});

Why Image() moved on screen, jump to default position , every click on screen , and start moving from default position?

I have simple demo project. Image moved along screen follow mouse point.
LibGDX Image instance jump to default x coordinate along x axis, determined inside MainMenuScreen.kt show method, every time i click on screen, and start from default position moving. But i expect Image will continue/start new moving from last position before click on screen. How fix it, and what problem?
Code is simple and short, and i can't understand what can be wrong.
pastebin link to full project code:
https://pastebin.com/4UQDjSWa
github link to project:
https://github.com/3dformortals/demo-libgdx/tree/master/DemoMovingImageOnScreen
full project code:
//-------
//KDA.kt
//-------
package com.kda
import com.badlogic.gdx.Game
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.GL20
import gui.AnimationSkin as AniSkin
class KDA : Game() {
internal var screenWidth:Float = 0.0f
internal var screenHeight:Float = 0.0f
internal val aniskin:AniSkin = AniSkin() //incuded human.atlas TextureAtlas for animation
override fun create() {
screenWidth = Gdx.graphics.width.toFloat()
screenHeight = Gdx.graphics.height.toFloat()
aniskin.prepare() //call preparing method for connect human.atlas for later using for animation
}
override fun render() {
Gdx.gl.glClearColor(1f, 0f, 0f, 1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
if (Gdx.input.justTouched()){
setScreen(MainMenuScreen(this))
}
super.render()
}
}
//-------------------
//AnimationSkin.kt
//-------------------
package gui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.scenes.scene2d.ui.Skin
class AnimationSkin : Skin() {
fun prepare(){
addRegions(TextureAtlas(Gdx.files.internal("animation/human.atlas")))
}
}
//----------------------
//MainMenuScreen.kt
//----------------------
package com.kda
import animated.ImageMoving
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.ScreenAdapter
import com.badlogic.gdx.graphics.GL20
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.utils.viewport.FitViewport
class MainMenuScreen(private val game: KDA) : ScreenAdapter() {
private val stage: Stage = Stage(FitViewport(game.screenWidth, game.screenHeight))
private val player = ImageMoving(game)
private val sprite = player.viewBox()
override fun show() {
Gdx.input.inputProcessor = stage
stage.isDebugAll = true //turn on frames around objects
sprite.x = 500f
//------------------------------------------------------------------------------------
//later, every mouse click on screen sprite jump to x500 position, and i can't fix it
//if i don't execute `sprite.x = 500f` , then sprite jump to x0 position, every click on screen
//--------------------------------------------------------------------------------------------
stage.addActor(sprite)
}
override fun resize(width: Int, height: Int) {
stage.viewport.update(width, height, true)
}
override fun render(delta: Float) {
super.render(delta)
Gdx.gl.glClearColor(0f, 0.5f, 0.5f, 1f)
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT)
if(Gdx.input.justTouched()) println("before calculateAction box.x= "+sprite.x.toString()) //500 always
player.calculateAction(delta) //call player method for calculation moving on screen
println(sprite.x) //print normal as expected
stage.act(delta)
stage.draw()
}
}
//-----------------
//ImageMoving.kt
//-----------------
package animated
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.kda.KDA
class ImageMoving(game: KDA) {
fun viewBox() = img
private val img = Image(game.aniskin.getDrawable("move-skin-male-back-R-0"))
fun calculateAction(delta:Float){
if (img.x > Gdx.input.x) img.x-=(100*delta).toInt().toFloat()
else if (img.x < Gdx.input.x) img.x+=(100*delta).toInt().toFloat()
}
}
//----------------------
//DesktopLauncher.kt
//---------------------
package com.kda.desktop
import com.badlogic.gdx.backends.lwjgl.LwjglApplication
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration
import com.kda.KDA
object DesktopLauncher {
#JvmStatic
fun main(arg: Array<String>) {
val config = LwjglApplicationConfiguration()
config.height = 720
config.width = 1280
LwjglApplication(KDA(), config)
}
}
gif animation demo of jumping image to default position x=500 after clicking on screen

How can I set another value in property listener?

I want to speed-up my ScrollPane scrolling. I need something like:
scrollPane.vvalueProperty().addListener((observable, oldValue, newValue) -> {
scrollPane.setVvalue(oldValue.doubleValue() + (newValue.doubleValue() - oldValue.doubleValue()) * 2);
});
but without stackowerflow exections and working..
May be there is a way to consume this like an event?
P.S. BTW, why does setOnScroll() fire only when scrolling reaches max (top) or min (bot) position?
I don't really recommend modifying a property while it is already changing, but if you want to do it you need to set a flag to suppress recursive calls. Here's an example:
import java.util.Random;
import javafx.application.Application;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Region;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
public class ModifyScrollSpeed extends Application {
#Override
public void start(Stage primaryStage) {
ScrollPane scrollPane = new ScrollPane(createContent());
DoublingListener.register(scrollPane.vvalueProperty());
Scene scene = new Scene(scrollPane, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
private static class DoublingListener implements ChangeListener<Number> {
private boolean doubling ;
private Property<Number> target ;
private DoublingListener(Property<Number> target) {
this.target = target ;
}
public static void register(Property<Number> target) {
target.addListener(new DoublingListener(target));
}
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (! doubling) {
doubling = true ;
target.setValue(oldValue.doubleValue() + 2 * (newValue.doubleValue() - oldValue.doubleValue()));
}
doubling = false ;
}
}
private Node createContent() {
TilePane tilePane = new TilePane();
Random rng = new Random();
for (int i = 0; i < 200; i++) {
Region region = new Region();
region.setMinSize(40, 40);
region.setPrefSize(40, 40);
region.setMaxSize(40, 40);
region.setStyle(String.format("-fx-background-color: #%02x%02x%02x;",
rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)));
tilePane.getChildren().add(region);
}
return tilePane ;
}
public static void main(String[] args) {
launch(args);
}
}
I don't actually see any other way to change the increment amounts for a scroll pane. Note that ScrollBar has API for changing the increment amounts via its unitIncrement and blockIncrement properties, however ScrollPane does not have equivalent properties.
There is a comment in the source code for ScrollPane which says
/*
* TODO The unit increment and block increment variables have been
* removed from the public API. These are intended to be mapped to
* the corresponding variables of the scrollbars. However, the problem
* is that they are specified in terms of the logical corrdinate space
* of the ScrollPane (that is, [hmin..hmax] by [vmin..vmax]. This is
* incorrect. Scrolling is a user action and should properly be based
* on how much of the content is visible, not on some abstract
* coordinate space. At some later date we may add a finer-grained
* API to allow applications to control this. Meanwhile, the skin should
* set unit and block increments for the scroll bars to do something
* reasonable based on the viewport size, e.g. the block increment
* should scroll 90% of the pixel size of the viewport, and the unit
* increment should scroll 10% of the pixel size of the viewport.
*/
The current skin for the scroll pane hard codes the unit and block increments for its scroll bars (in the updateHorizontalSB and updateVerticalSB methods) in the manner described in this comment (i.e. 10% and 90% of the visible amount), so I see no real way to get at these. In Java 9 the skin class will become a public class, and so at a minimum you could subclass it and modify this behavior.
You can try something like this -
private boolean scrolllChanging = false;
private void myScroll(ObservableDoubleValue observable, Double oldValue, Double newValue) {
if (!scrollChanging) {
try {
scrollChanging = true;
// Insert logic here. Any subsequent changes won't reach here until `scrollChanging` is set to false again.
} finally {
scrollChanging = false;
}
}
}
scrollPane.vvalueProperty().addListener(this::myScroll);
Forgive any minor type errors, I have not compiled this.

Can't paint an image after choosing it from JFileChooser

Good evening. I have read a lot of topics here on stackoverflow or even internet but I can't find the solution to my problem.
I have an interface like this:
When I click on "Load Image A", I can choose the image that I want. Next I want to paint this image under the JLabel "Image A". But it doesn't want to show up.
Here is the code I wrote:
package projet;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class MonPanelImage extends JPanel{
private static final long serialVersionUID = -8267224342030244581L;
private BufferedImage image;
public MonPanelImage(File adresse)
{
try{
image = ImageIO.read(adresse);
}catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponents(g);
System.out.println("paint");
if(image != null){
g.drawImage(image, 20, 20, this);
}
}
}
and here is where I call it:
//panel image. This is my second panel which will be for the images
final JPanel second = new JPanel(new BorderLayout());
//panel button. This is the third panel for the buttons
rows = 0;
cols = 3;
hgap = 5;
vgap = 0;
JPanel third = new JPanel(new GridLayout(rows,cols,hgap,vgap));
//buttons
JButton boutonLoad1 = new JButton("Load image A");
boutonLoad1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int retour = fc.showDialog(frame, "Charger l'image");
if(retour == JFileChooser.APPROVE_OPTION){
String pathImage1 = fc.getSelectedFile().getAbsolutePath();
path1 = pathImage1;
File file = fc.getSelectedFile();
MonPanelImage panelImage1 = new MonPanelImage(file);
second.add(panelImage1, BorderLayout.WEST);
second.revalidate();
second.repaint();
}
}
});
At the very end, i add the 3 panels to my frame and set the frame to visible.
But I can't paint an image. Maybe I'm not doing it properly. Can someone help me please?
Thanks
super.paintComponents(g);
First of all it should be super.paintComponent(g), without the "s".
second.add(panelImage1, BorderLayout.WEST);
You are adding your image to a component using a BorderLayout. The BorderLayout will respect the width of your component, which is 0, so there is nothing to paint.
Whenever, you do custom painting you need to override the getPreferredSize() method to return the size of your component so the layout manager can do its job.
However, an easier solution is to just use a JLabel with an Icon. There is no need to do custom painting when you are painting the image at its real size.

How to get position of an item in ListView in JavaFX?

If I create a ListView in JavaFX like this:
ObservableList<String> elements = FXCollections.observableArrayList("John", "Doe");
ListView<String> lView = new ListView<String>(elements);
What I want to do is draw a line starting from the end of a row in the ListView, say from "John"
To do this, I need the location(x,y) of the row "John". Is it possible to get the location?
Update
This is a sample interface that I got using Swing and Piccolo2D. However, using that library is painful. I am wondering if I can do the same in JavaFX
It is possible, but it may not be as straight forward as you hoped. In order to determine the layout coordinates for a particular Cell within a ListView (or TableView/TreeView) you need to have access to that particular Cell object. The best way (and maybe only way in JavaFX 2.2) is to provide the container with a custom Cell and CellFactory that exposes each Cell. How you expose the Cell depends on what your triggers are for drawing the line.
Bases on your illustration, you'll need access to each cell once the ListViews are populated. You can do this with a List<ListCell<String>> field in the CellFactory. I'll mention one caveat here about ListCells. The ListViewSkin will reuse Cells whenever possible. That means that if you are going to try to populate and connect a list that ends up scrolling, then keeping your lines in the right place will be much more difficult. I'd recommend trying to ensure that all your list items fit on screen.
Below is an example with some notes in the comments. Take note that getting the correct coordinates for drawing your Line will probably require calculating the offset of your SceneGraph which I didn't do in this example.
package listviewcellposition;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Callback;
public class ListViewCellPosition extends Application {
// CustomCellFactory for creating CustomCells
public class CustomCellFactory implements
Callback<ListView<String>, ListCell<String>> {
List<ListCell<String>> allCells = new ArrayList<>();
#Override
public ListCell<String> call(final ListView<String> p) {
final CustomCell cell = new CustomCell();
allCells.add(cell);
return cell;
}
public List<ListCell<String>> getAllCells() {
return allCells;
}
}
// CustomCell is where the exposure occurs. Here, it's based on the
// Cell being selected in the ListView. You could choose a different
// trigger here but you'll need to explore.
public class CustomCell extends ListCell<String> {
// General display stuff
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else {
setText(item == null ? "" : item);
setGraphic(null);
}
}
}
#Override
public void start(Stage primaryStage) {
// This pane will contain the lines after they are created.
// I set it into an AnchorPane to avoid having to deal with
// resizing.
Pane linePane = new Pane();
AnchorPane pane = new AnchorPane();
pane.setPrefSize(100, 250);
AnchorPane.setBottomAnchor(linePane, 0.0);
AnchorPane.setLeftAnchor(linePane, 0.0);
AnchorPane.setRightAnchor(linePane, 0.0);
AnchorPane.setTopAnchor(linePane, 0.0);
pane.getChildren().add(linePane);
ListView<String> lView = new ListView<>();
lView.setPrefSize(100, 250);
CustomCellFactory lCellFactory = new CustomCellFactory();
lView.setCellFactory(lCellFactory);
ListView<String> rView = new ListView<>();
rView.setPrefSize(100, 250);
CustomCellFactory rCellFactory = new CustomCellFactory();
rView.setCellFactory(rCellFactory);
lView.getItems().addAll("Bill", "Doctor", "Steve", "Joanne");
rView.getItems().addAll("Seuss", "Rowling", "King", "Shakespeare");
HBox root = new HBox();
root.getChildren().addAll(lView, pane, rView);
Scene scene = new Scene(root, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
connectCells(lCellFactory, "Bill", rCellFactory, "Shakespeare", linePane);
connectCells(lCellFactory, "Doctor", rCellFactory, "Seuss", linePane);
connectCells(lCellFactory, "Steve", rCellFactory, "King", linePane);
connectCells(lCellFactory, "Joanne", rCellFactory, "Rowling", linePane);
}
// Looks up the ListCell<> for each String and creates a Line
// with the coordinates from each Cell. The calculation is very
// contrived because I know that all the components have the same
// x-coordinate. You'll need more complicated calculations if your
// containers are not aligned this way.
private void connectCells(CustomCellFactory lCellFactory, String lVal,
CustomCellFactory rCellFactory, String rVal, Pane linePane) {
List<ListCell<String>> lList = lCellFactory.getAllCells();
ListCell<String> lCell = null;
for (ListCell<String> lc : lList) {
if (lc.getItem() != null && lc.getItem().equals(lVal)) {
lCell = lc;
break;
}
}
List<ListCell<String>> rList = rCellFactory.getAllCells();
ListCell<String> rCell = null;
for (ListCell<String> rc : rList) {
if (rc.getItem() != null && rc.getItem().equals(rVal)) {
rCell = rc;
break;
}
}
if (lCell != null && rCell != null) {
double startY = lCell.getLayoutY() +
(lCell.getBoundsInLocal().getHeight() / 2);
double endY = rCell.getLayoutY() +
(rCell.getBoundsInLocal().getHeight() / 2);
Line line = new Line(0, startY,
linePane.getBoundsInParent().getWidth(), endY);
line.setStrokeWidth(2);
line.setStroke(Color.BLACK);
linePane.getChildren().add(line);
}
}
public static void main(String[] args) {
launch(args);
}
}

Resources