I am writing a breakout Game and everything is working fine except one little thing.
I have a ball object which extends from thread and RepainterThread which can either implement Runnable or extends Thread which calls the paint method which re-renders the game field with bricks, a paddle, and a Ball
I have a Singelton GameController which connects all things.
I have a GameState with an isActive boolean to decide if the game should be paused or not.
I start the game and I can play and the program behaves as it should.
The Frame gets drawn the ball moves the bricks break when the ball hits everything fine.
Then I pause the game via a Button where I set is Active to false.
The Ball stops as it should. Then I hit the continue Button and isActive is again true. The ball object starts running again and the running method of Repainter Thread is allso triggered but the Swing Frame Freezes completely.
I tried various things these are my nearest approaches i have.
I spent days on it please help
public class Ball extends MovingObject {
private double hSpeed; // Horizontal velocity
private double vSpeed; // Vertical velocity
public Ball() {
this.color = Color.MAGENTA;
this.height = GameSettings.ballSize;
this.width = GameSettings.ballSize;
this.position = new Point2D.Double(GameSettings.defaultBallX, GameSettings.defaultBallY);
this.hSpeed = GameSettings.ballHSpeed;
this.vSpeed = GameSettings.ballYSpeed;
}
public Ball(Ball ball) {
color = ball.color;
height = ball.height;
width = ball.width;
position = ball.position;
hSpeed = ball.hSpeed;
vSpeed = ball.vSpeed;
}
public double getHSpeed() {
return this.hSpeed;
}
public double getVSpeed() {
return this.vSpeed;
}
public void run() {
try {
while (GameController.getInstance().getGameState().isActive()) {
System.out.println("Ich run im Ball");
Thread.sleep(10);
this.meetingWall();
this.meetingBrick();
this.position.setLocation(this.getPosition().getX() + this.hSpeed,
this.getPosition().getY() + this.vSpeed);
if (this.meetingPaddle()) {
this.newDirection();
}
if (this.out()) {
GameController.getInstance().stopGame();
this.stopThread();
}
this.position = this.getPosition();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stopThread() {
GameController.getInstance().getGameState().setActive(false);
}
public void startThreadAgain() {
this.run();
}
#Override
public synchronized void start() {
this.position.setLocation(GameSettings.defaultBallX, GameSettings.defaultBallY);
super.start();
}
class GamePanel extends JPanel {
private static final long serialVersionUID = 1L;
private final Color backgroundColor = Color.BLACK;
GameState gameState = GameController.getInstance().getGameState();
public GamePanel() {
super();
this.addKeyListener(new KeyListener() {
// Dieser KeyListener soll auf Inputs der Pfeiltasten nach links
// <- und rechts -> hoeren und eine entsprechende Bewegung des
// Schlaegers erwirken, aber nur, wenn das Spiel nicht
// pausiert/gestoppt ist.
public void keyPressed(KeyEvent keyEvent) {
if (gameState.isActive()) {// gameState.isActive()
if (keyEvent.getKeyCode() == KeyEvent.VK_RIGHT) {
gameState.getPaddle().setPositionRigth();
}
if (keyEvent.getKeyCode() == KeyEvent.VK_LEFT) {
gameState.getPaddle().setPositionLeft();
}
}
}
public void keyReleased(KeyEvent keyEvent) {
// TODO
}
public void keyTyped(KeyEvent arg0) {
}
});
}
public void paint(Graphics g) {
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.setColor(this.backgroundColor);
graphics2D.fillRect(0, 0, this.getWidth(), this.getHeight());
for (int i = 0; i < gameState.getBricks().length; i++) {
for (int j = 0; j < gameState.getBricks()[i].length; j++) {
if ((gameState.getBricks()[i][j] != null)) {
graphics2D.setColor(gameState.getBricks()[i][j].getColor());
graphics2D.fillRect(
(i * GameSettings.brickWidth) + (i + 1) * (GameSettings.spaceAroundBrick + 1),
(j * GameSettings.brickHeight) + (j + 1) * GameSettings.spaceAroundBrick,
GameSettings.brickWidth, GameSettings.brickHeight);
}
scoreLabel.setText(this.gameState.getScore() + "");
gameState.getPaddle().draw(graphics2D);
gameState.getBall().draw(graphics2D);
}
}
}
}
// First Approach
private class RepainterThread implements Runnable {
public RepainterThread() {
System.out.println("RepainThreadCOntructor");
}
private void updateGUI() {
System.out.println("before invoke later");
System.out.println("repaint");
getGamePanel().requestFocus();
getGamePanel().repaint();
}
#Override
public void run() {
System.out.println("Repainter run");
System.out.println(GameController.getInstance().getGameState().isActive());
while (GameController.getInstance().getGameState().isActive()) {
System.out.println("inside while");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
updateGUI();
}
}
}
// Second Approach
private class RepainterThread implements Runnable {
public RepainterThread() {
System.out.println("RepainThreadCOntructor");
}
private void updateGUI(){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
System.out.println("repaint");
getGamePanel().requestFocus();
getGamePanel().repaint();
}
});
}
#Override
public void run() {
System.out.println("Repainter run");
System.out.println(GameController.getInstance().getGameState().isActive());
while (GameController.getInstance().getGameState().isActive()) {
System.out.println("inside while");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
updateGUI();
}
}
}
Frame should no longer freeze
please help
This is kind of a wild guess, but since according to your description the program works correctly until you pause and unpause it, the problem likely lies with the startThreadAgain method:
public void startThreadAgain() {
this.run();
}
Your code does not show how you call this method, but in that method you just call this.run(), i.e. you call the run method as a regular, blocking method, while probably still in the UI thread. Instead, you should call start() to start a proper new thread executing the run method.
public void startThreadAgain() {
super.start(); // not this start, otherwise it resets the ball's location
}
I would like to make my JToolBar impossible to detach from its container but still let the user drag it to one of the container's sides.
I know about
public void setFloatable( boolean b )
but this won't allow the user to move the JToolBar at all.
Is there any way of doing this without overwriting ToolBarUI?
Also, is there an option to highlight its new position before dropping it?
It's not the most elegant solution, but it works.
public class Example extends JFrame {
BasicToolBarUI ui;
Example() {
JToolBar tb = new JToolBar();
tb.add(new JButton("AAAAA"));
tb.setBackground(Color.GREEN);
ui = (BasicToolBarUI) tb.getUI();
getContentPane().addContainerListener(new Listener());
getContentPane().add(tb, BorderLayout.PAGE_START);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(300, 300);
setLocationRelativeTo(null);
setVisible(true);
}
class Listener implements ContainerListener {
#Override
public void componentAdded(ContainerEvent e) {}
#Override
public void componentRemoved(ContainerEvent e) {
if (ui.isFloating()) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
ui.setFloating(false, null);
}
});
}
}
}
public static void main(String[] args) {
new Example();
}
}
Explanation:
Whenever the toolbar is moving to a floating state, it is instructed not do so. The only problem is that you have to wait for the EDT to finish the process for creating the floating window, and only then can you tell it not to float. The result is that you actually see the window created and then hidden.
Note:
I think that overriding the UI for the toolbar is a better solution, though it's possible that with a more intricate approach doing something similar to what I did will also work well.
works for me quite correctly on WinOS, old code from SunForum
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CaptiveToolBar {
private Robot robot;
private JDialog dialog;
private JFrame frame;
public static void main(String[] args) {
//JFrame.setDefaultLookAndFeelDecorated(true);
//JDialog.setDefaultLookAndFeelDecorated(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new CaptiveToolBar().makeUI();
}
});
}
public void makeUI() {
try {
robot = new Robot();
} catch (AWTException ex) {
ex.printStackTrace();
}
final JToolBar toolBar = new JToolBar();
for (int i = 0; i < 3; i++) {
toolBar.add(new JButton("" + i));
}
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.add(toolBar, BorderLayout.NORTH);
final ComponentListener dialogListener = new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
dialog = (JDialog) e.getSource();
setLocations(false);
}
};
toolBar.addHierarchyListener(new HierarchyListener() {
#Override
public void hierarchyChanged(HierarchyEvent e) {
Window window = SwingUtilities.getWindowAncestor(toolBar);
if (window instanceof JDialog) {
boolean listenerAdded = false;
for (ComponentListener listener : window.getComponentListeners()) {
if (listener == dialogListener) {
listenerAdded = true;
break;
}
}
if (!listenerAdded) {
window.addComponentListener(dialogListener);
}
}
}
});
frame.addComponentListener(new ComponentAdapter() {
#Override
public void componentMoved(ComponentEvent e) {
if (dialog != null && dialog.isShowing()) {
setLocations(true);
}
}
});
frame.setVisible(true);
}
private void setLocations(boolean moveDialog) {
int dialogX = dialog.getX();
int dialogY = dialog.getY();
int dialogW = dialog.getWidth();
int dialogH = dialog.getHeight();
int frameX = frame.getX();
int frameY = frame.getY();
int frameW = frame.getWidth();
int frameH = frame.getHeight();
boolean needToMove = false;
if (dialogX < frameX) {
dialogX = frameX;
needToMove = true;
}
if (dialogY < frameY) {
dialogY = frameY;
needToMove = true;
}
if (dialogX + dialogW > frameX + frameW) {
dialogX = frameX + frameW - dialogW;
needToMove = true;
}
if (dialogY + dialogH > frameY + frameH) {
dialogY = frameY + frameH - dialogH;
needToMove = true;
}
if (needToMove) {
if (!moveDialog && robot != null) {
robot.mouseRelease(InputEvent.BUTTON1_MASK);
}
dialog.setLocation(dialogX, dialogY);
}
}
}
I am trying to build an audio player with an integrated JSlider, which updates the interface every microsecond.
In order to do so I am using the following:
sliderTime.setMinimum(0);
sliderTime.setMaximum((int) audioClip.getMicrosecondPosition(););
I have the feeling that this is not the best implementation out there (any suggestions to improve it is highly appreciated)
By the way, the issue I am facing is that for the first second the JSlider does not update.
Please find MCVE below:
It plays only wav uncompressed files
Main
public class Main
{
public static void main(final String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JFrame f = new JFrame();
PlayerView pw = new PlayerView();
Border border = new EmptyBorder(15,15,15,15);
pw.setBorder(border);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(pw, BorderLayout.CENTER);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}
AudioPlayer
public class AudioPlayer implements LineListener
{
private SimpleDateFormat dateFormater = new SimpleDateFormat("HH:mm:ss.SSS");
private TimeZone timeZone = Calendar.getInstance().getTimeZone();
public static final int REWIND_IN_MICROSECONDS = 3000000;
public static final int FORWARD_IN_MICROSECONDS = 3000000;
private boolean playCompleted;
private boolean isStopped;
private boolean isPaused;
private boolean isRewinded;
private boolean isForwarded;
private Clip audioClip;
public Clip getAudioClip()
{
return audioClip;
}
public void load(String audioFilePath) throws UnsupportedAudioFileException, IOException, LineUnavailableException
{
File encodedFile = new File(audioFilePath);
AudioInputStream pcmStream = AudioSystem.getAudioInputStream(encodedFile);
AudioFormat format =pcmStream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
audioClip = (Clip) AudioSystem.getLine(info);
audioClip.addLineListener(this);
audioClip.open(pcmStream);
}
public long getClipMicroSecondLength()
{
return audioClip.getMicrosecondLength();
}
public long getClipMicroSecondPosition()
{
return audioClip.getMicrosecondPosition();
}
public String getClipLengthString()
{
long yourmilliseconds = audioClip.getMicrosecondLength() / 1_000;
Date resultdate = new Date(yourmilliseconds);
dateFormater.setTimeZone(TimeZone.getTimeZone(timeZone.getDisplayName(false, TimeZone.SHORT)));
return dateFormater.format(resultdate);
}
public void play() throws IOException
{
audioClip.start();
playCompleted = false;
isStopped = false;
while (!playCompleted)
{
try
{
Thread.sleep(30);
}
catch (InterruptedException ex)
{
if (isStopped)
{
audioClip.stop();
break;
}
else if (isPaused)
{
audioClip.stop();
}
else if (isRewinded)
{
if( audioClip.getMicrosecondPosition() <= REWIND_IN_MICROSECONDS)
{
audioClip.setMicrosecondPosition(0);
isRewinded =false;
}
else
{
audioClip.setMicrosecondPosition(audioClip.getMicrosecondPosition() - REWIND_IN_MICROSECONDS);
isRewinded =false;
}
}
else if (isForwarded)
{
if((audioClip.getMicrosecondLength() - audioClip.getMicrosecondPosition()) >= FORWARD_IN_MICROSECONDS)
{
audioClip.setMicrosecondPosition(audioClip.getMicrosecondPosition() + FORWARD_IN_MICROSECONDS);
isForwarded =false;
}
else
{
audioClip.stop();
isForwarded =false;
}
}
else
{
audioClip.start();
}
}
}
audioClip.close();
}
public void stop()
{
isStopped = true;
}
public void pause()
{
isPaused = true;
}
public void resume()
{
isPaused = false;
}
public void rewind()
{
isRewinded = true;
}
public void forward()
{
isForwarded = true;
}
#Override
public void update(LineEvent event)
{
Type type = event.getType();
if (type == Type.STOP)
{
if (isStopped || !isPaused)
{
playCompleted = true;
}
}
}
}
PlayingTimer
public class PlayingTimer extends Thread
{
private SimpleDateFormat dateFormater = new SimpleDateFormat("HH:mm:ss.SSS");
private TimeZone timeZone = Calendar.getInstance().getTimeZone();
private boolean isRunning = false;
private boolean isPause = false;
private boolean isReset = false;
private boolean isRewinded = false;
private boolean isForwarded = false;
private long startTime;
private long pauseTime;
private long rewindTime;
private long forwardTime;
private JLabel labelRecordTime;
private JSlider slider;
private Clip audioClip;
public void setAudioClip(Clip audioClip)
{
this.audioClip = audioClip;
}
public PlayingTimer(JLabel labelRecordTime, JSlider slider)
{
this.labelRecordTime = labelRecordTime;
this.slider = slider;
dateFormater.setTimeZone(TimeZone.getTimeZone(timeZone.getDisplayName(false, TimeZone.SHORT)));
}
public void run()
{
isRunning = true;
startTime = System.currentTimeMillis();
while (isRunning)
{
try
{
Thread.sleep(30);
if (!isPause)
{
if (audioClip != null && audioClip.isRunning())
{
long currentMicros = audioClip.getMicrosecondPosition();
// Compute the progress as a value between 0.0 and 1.0
double progress =
(double)currentMicros / audioClip.getMicrosecondLength();
// Compute the slider value to indicate the progress
final int sliderValue = (int)(progress * slider.getMaximum());
// Update the slider with the new value, on the Event Dispatch Thread
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
labelRecordTime.setText(toTimeString());
slider.setValue(sliderValue);
}
});
}
}
else
{
pauseTime += 30;
}
}
catch (InterruptedException ex)
{
if (isReset)
{
slider.setValue(0);
labelRecordTime.setText("00:00:00.000");
isRunning = false;
break;
}
if (isRewinded)
{
if( audioClip.getMicrosecondPosition() <= AudioPlayer.REWIND_IN_MICROSECONDS)
{
//go back to start
rewindTime += audioClip.getMicrosecondPosition() / 1_000;
}
else
{
rewindTime += 3000;
}
isRewinded =false;
}
if (isForwarded)
{
if((audioClip.getMicrosecondLength()- audioClip.getMicrosecondPosition()) <= AudioPlayer.FORWARD_IN_MICROSECONDS)
{
forwardTime -= (audioClip.getMicrosecondLength()- audioClip.getMicrosecondPosition())/1_000;
}
else
{
forwardTime -= 3000;
}
isForwarded=false;
}
}
}
}
public void reset()
{
isReset = true;
isRunning = false;
}
public void rewind()
{
isRewinded = true;
}
public void forward()
{
isForwarded = true;
}
public void pauseTimer()
{
isPause = true;
}
public void resumeTimer()
{
isPause = false;
}
private String toTimeString()
{
long now = System.currentTimeMillis();
Date resultdate = new Date(now - startTime - pauseTime - rewindTime - forwardTime);
return dateFormater.format(resultdate);
}
}
PlayerView
public class PlayerView extends JPanel implements ActionListener
{
private static final int BUTTON_HEIGTH =60;
private static final int BUTTON_WIDTH =120;
private AudioPlayer player = new AudioPlayer();
private Thread playbackThread;
private PlayingTimer timer;
private boolean isPlaying = false;
private boolean isPause = false;
private String audioFilePath;
private String lastOpenPath;
private JLabel labelFileName;
private JLabel labelTimeCounter;
private JLabel labelDuration;
private JButton buttonOpen;
private JButton buttonPlay;
private JButton buttonPause;
private JButton buttonRewind;
private JButton buttonForward;
private JSlider sliderTime;
private Dimension buttonDimension = new Dimension(BUTTON_WIDTH,BUTTON_HEIGTH);
public PlayerView()
{
setLayout(new BorderLayout());
labelFileName = new JLabel("File Loaded:");
labelTimeCounter = new JLabel("00:00:00.000");
labelDuration = new JLabel("00:00:00.000");
sliderTime = new JSlider(0, 1000, 0);;
sliderTime.setValue(0);
sliderTime.setEnabled(false);
buttonOpen = new JButton("Open");
buttonOpen.setPreferredSize(buttonDimension);
buttonOpen.addActionListener(this);
buttonPlay = new JButton("Play");
buttonPlay.setEnabled(false);
buttonPlay.setPreferredSize(buttonDimension);
buttonPlay.addActionListener(this);
buttonPause = new JButton("Pause");
buttonPause.setEnabled(false);
buttonPause.setPreferredSize(buttonDimension);
buttonPause.addActionListener(this);
buttonRewind = new JButton("Rewind");
buttonRewind.setEnabled(false);
buttonRewind.setPreferredSize(buttonDimension);
buttonRewind.addActionListener(this);
buttonForward= new JButton("Forward");
buttonForward.setEnabled(false);
buttonForward.setPreferredSize(buttonDimension);
buttonForward.addActionListener(this);
init();
}
public void enableButtonPlay()
{
buttonPlay.setEnabled(true);
}
#Override
public void actionPerformed(ActionEvent event)
{
Object source = event.getSource();
if (source instanceof JButton)
{
JButton button = (JButton) source;
if (button == buttonOpen)
{
openFile();
}
else if (button == buttonPlay)
{
if (!isPlaying)
{
playBack();
}
else
{
stopPlaying();
}
}
else if (button == buttonPause)
{
if (!isPause)
{
pausePlaying();
}
else
{
resumePlaying();
}
}
else if (button == buttonRewind)
{
if (!isPause)
{
rewind();
}
}
else if (button == buttonForward)
{
if (!isPause)
{
forward();
}
}
}
}
public void openFile(String path)
{
audioFilePath = path ;
if (isPlaying || isPause)
{
stopPlaying();
while (player.getAudioClip().isRunning())
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
}
playBack();
}
private void openFile()
{
JFileChooser fileChooser = null;
if (lastOpenPath != null && !lastOpenPath.equals(""))
{
fileChooser = new JFileChooser(lastOpenPath);
}
else
{
fileChooser = new JFileChooser();
}
FileFilter wavFilter = new FileFilter()
{
#Override
public String getDescription()
{
return "Sound file (*.WAV)";
}
#Override
public boolean accept(File file)
{
if (file.isDirectory())
{
return true;
}
else
{
return file.getName().toLowerCase().endsWith(".wav");
}
}
};
fileChooser.setFileFilter(wavFilter);
fileChooser.setDialogTitle("Open Audio File");
fileChooser.setAcceptAllFileFilterUsed(false);
int userChoice = fileChooser.showOpenDialog(this);
if (userChoice == JFileChooser.APPROVE_OPTION)
{
audioFilePath = fileChooser.getSelectedFile().getAbsolutePath();
lastOpenPath = fileChooser.getSelectedFile().getParent();
if (isPlaying || isPause)
{
stopPlaying();
while (player.getAudioClip().isRunning())
{
try
{
Thread.sleep(100);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
}
playBack();
}
}
private void playBack()
{
timer = new PlayingTimer(labelTimeCounter, sliderTime);
timer.start();
isPlaying = true;
playbackThread = new Thread(new Runnable()
{
#Override
public void run()
{
try
{
buttonPlay.setText("Stop");
buttonPlay.setEnabled(true);
buttonRewind.setEnabled(true);
buttonForward.setEnabled(true);
buttonPause.setText("Pause");
buttonPause.setEnabled(true);
player.load(audioFilePath);
timer.setAudioClip(player.getAudioClip());
labelFileName.setText("Playing File: " + ((File)new File(audioFilePath)).getName());
sliderTime.setMinimum(0);
sliderTime.setMaximum((int)player.getClipMicroSecondLength());
labelDuration.setText(player.getClipLengthString());
player.play();
labelFileName.setText("File Loaded: " + ((File)new File(audioFilePath)).getName());
resetControls();
}
catch (UnsupportedAudioFileException ex)
{
JOptionPane.showMessageDialog(
PlayerView.this,
"The audio format is unsupported!",
"Error",
JOptionPane.ERROR_MESSAGE);
resetControls();
}
catch (LineUnavailableException ex)
{
JOptionPane.showMessageDialog(
PlayerView.this,
"Could not play the audio file because line is unavailable!",
"Error",
JOptionPane.ERROR_MESSAGE);
resetControls();
}
catch (IOException ex)
{
JOptionPane.showMessageDialog(
PlayerView.this,
"I/O error while playing the audio file!",
"Error",
JOptionPane.ERROR_MESSAGE);
resetControls();
}
}
});
playbackThread.start();
}
private void stopPlaying()
{
isPause = false;
buttonPause.setText(" Pause ");
buttonPause.setEnabled(false);
buttonRewind.setEnabled(false);
buttonForward.setEnabled(false);
timer.reset();
timer.interrupt();
player.stop();
playbackThread.interrupt();
}
private void pausePlaying()
{
labelFileName.setText("File Loaded: " + ((File)new File(audioFilePath)).getName());
buttonRewind.setEnabled(false);
buttonForward.setEnabled(false);
buttonPause.setText("Resume");
isPause = true;
player.pause();
timer.pauseTimer();
playbackThread.interrupt();
}
private void resumePlaying()
{
labelFileName.setText("Playing File: " + ((File)new File(audioFilePath)).getName());
buttonPause.setText(" Pause ");
buttonRewind.setEnabled(true);
buttonForward.setEnabled(true);
isPause = false;
player.resume();
timer.resumeTimer();
playbackThread.interrupt();
}
private void rewind()
{
player.rewind();
timer.rewind();
timer.interrupt();
playbackThread.interrupt();
}
private void forward()
{
player.forward();
timer.forward();
timer.interrupt();
playbackThread.interrupt();
}
private void resetControls()
{
timer.reset();
timer.interrupt();
isPlaying = false;
buttonPlay.setText("Play");
buttonPause.setEnabled(false);
buttonRewind.setEnabled(false);
buttonForward.setEnabled(false);
}
private void init()
{
add(labelFileName, BorderLayout.NORTH);
add(labelTimeCounter, BorderLayout.WEST);
add(labelDuration, BorderLayout.EAST);
add(sliderTime, BorderLayout.CENTER);
JPanel buttonContainer =new JPanel();
add(buttonContainer, BorderLayout.SOUTH);
buttonContainer.add(buttonOpen);
buttonContainer.add(buttonPlay);
buttonContainer.add(buttonPause);
buttonContainer.add(buttonRewind);
buttonContainer.add(buttonForward);
}
}
Okay, so, the issue with Clip. Here is an MCVE that, from the way you've described the problem, may reproduce it:
class TestFramePosition {
public static void main(String[] a) throws Exception {
File file = new File(a.length > 0 ? a[0] : "path/to/file.extension");
AudioInputStream ais = AudioSystem.getAudioInputStream(file);
final Clip clip = AudioSystem.getClip();
clip.open(ais);
clip.start();
new Thread(new Runnable() {
#Override
public void run() {
while(clip.isRunning()) {
try {
System.out.println(clip.getMicrosecondPosition());
Thread.sleep(1000 / 10);
} catch(InterruptedException ignored) {}
}
}
}).start();
System.in.read();
System.exit(0);
}
}
I was unable to reproduce it on OSX 10.6.8 and Windows XP, but you may run that code to see if it does on your particular platform.
So, the issue here is that, as I said in comments, since sound playback is dependent on platform-specific stuff, classes like Clip will have varied implementations. These will behave slightly differently.
For example, I found that when a Clip is done playing, the Clip on my Mac computer (a com.sun.media.sound.MixerClip) returns 0 for the position, while the Clip on my Windows computer (a com.sun.media.sound.DirectAudioDevice$DirectClip) returns the maximum value for the position. Just another small example of implementations being programmed differently.
The issue is that the contract for these methods is defined a little vaguely but, specifically, it is defined by 'the number of sample frames captured by, or rendered from, the line since it was opened'. This means it may not accurately represent the playback position, rather it is the amount of data read and written.
I did spend awhile yesterday perusing JDK source code but I was unable to find anything that would point towards the behavior you are seeing.
Anyway, what it comes down to is whether you are OK with slightly anomalous behavioral differences from platform to platform. What you are seeing may be a bug and if the above MCVE reproduces it, you may report it; however I would not personally expect it to get fixed in any timely manner because this is a section of the JDK that does not get a lot of attention. Also it is gradually being superseded by JavaFX.
Some other things:
You are sharing state between threads without synchronization. This leads to memory errors. You should read the concurrency tutorials, specifically synchronization.
You should always cap frame rate when working with Swing. Swing will not paint at 1000FPS, it will merge repaints aggressively. Updating the slider at this rate was just flooding the EDT.
You may use SourceDataLine because it gives you much greater control over the buffering behavior. The downside is that you have to basically reimplement the functionality of Clip.
Here is an MCVE demonstrating a playback loop to power a JSlider.
This example doesn't demonstrate seeking. Also since, AudioInputStream does not generally support mark operations, seeking backwards is a bit of a hassle. A backwards seek process is:
Stop the current playback and discard it.
Create a new AudioInputStream and seek forwards.
Start the new playback.
Also, if you are planning to use the JSlider to seek, you will probably run in to an issue where calling setValue on a JSlider will cause it to fire a ChangeEvent. So you can't update the slider's value programmatically and also listen to it without rejiggering it. This is really a Q&A itself so if you experience this problem I recommend you ask a new question.
import javax.sound.sampled.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.io.File;
import java.io.IOException;
public class PlaybackSlider implements Runnable, ActionListener {
public static void main(String[] args) {
SwingUtilities.invokeLater(new PlaybackSlider());
}
JButton open;
JButton play;
JSlider slider;
JLabel label;
File file;
PlaybackLoop player;
#Override
public void run() {
JFrame frame = new JFrame("Playback Slider");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel content = new JPanel(new BorderLayout()) {
#Override
public Dimension getPreferredSize() {
Dimension pref = super.getPreferredSize();
pref.width = 480;
return pref;
}
};
slider = new JSlider(JSlider.HORIZONTAL, 0, 1000, 0);
content.add(slider, BorderLayout.CENTER);
JToolBar bar = new JToolBar(JToolBar.HORIZONTAL);
bar.setFloatable(false);
content.add(bar, BorderLayout.SOUTH);
open = new JButton("Open");
play = new JButton("Play");
open.addActionListener(this);
play.addActionListener(this);
label = new JLabel("");
bar.add(open);
bar.add(new JLabel(" "));
bar.add(play);
bar.add(new JLabel(" "));
bar.add(label);
frame.setContentPane(content);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ae) {
Object source = ae.getSource();
if(source == open) {
File f = getFile();
if(f != null) {
file = f;
label.setText(file.getName());
stop();
}
}
if(source == play) {
if(file != null) {
if(player != null) {
stop();
} else {
start();
}
}
}
}
File getFile() {
JFileChooser diag = new JFileChooser();
int choice = diag.showOpenDialog(null);
if(choice == JFileChooser.APPROVE_OPTION) {
return diag.getSelectedFile();
} else {
return null;
}
}
void start() {
try {
player = new PlaybackLoop(file);
new Thread(player).start();
play.setText("Stop");
} catch(Exception e) {
player = null;
showError("the file couldn't be played", e);
}
}
void stop() {
if(player != null) {
player.stop();
}
}
void showError(String msg, Throwable cause) {
JOptionPane.showMessageDialog(null,
"There was an error because " + msg +
(cause == null ? "." : "\n(" + cause + ").")
);
}
class PlaybackLoop implements Runnable {
AudioInputStream in;
SourceDataLine line;
AudioFormat fmt;
int bufferSize;
boolean stopped;
PlaybackLoop(File file) throws Exception {
try {
in = AudioSystem.getAudioInputStream(file);
fmt = in.getFormat();
bufferSize = (int)(fmt.getFrameSize() * (fmt.getSampleRate() / 15));
line = AudioSystem.getSourceDataLine(fmt);
line.open(fmt, bufferSize);
} catch(Exception e) {
if(in != null)
in.close();
if(line != null)
line.close();
throw e;
}
}
void stop() {
synchronized(this) {
this.stopped = true;
}
}
#Override
public void run() {
line.start();
byte[] buf = new byte[bufferSize];
try {
try {
int b;
long elapsed = 0;
long total = in.getFrameLength();
for(;;) {
synchronized(this) {
if(stopped) {
break;
}
}
b = in.read(buf, 0, buf.length);
if(b < 0) {
break;
}
elapsed += b / fmt.getFrameSize();
updateSlider(elapsed, total);
line.write(buf, 0, b);
}
} finally {
line.close();
in.close();
}
} catch(IOException e) {
e.printStackTrace(System.err);
showError("there was a problem during playback", e);
}
endOnEDT();
}
void updateSlider(double elapsed, double total) {
final double amt = elapsed / total;
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
slider.setValue((int)Math.round(slider.getMaximum() * amt));
}
});
}
void endOnEDT() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
player = null;
slider.setValue(0);
play.setText("Play");
}
});
}
}
}
I assume you are wanting to use the JSlider as a progress bar and that at the moment you are setting the max value, the current position is at the end of the audioClip. (Are you dealing with Clip or AudioClip? AudioClip doesn't have a way to read its position AFAIK.) If you are using Clip, it would be safer to set the max with audioClip.getMicrosecondLength().
Since the audio has to play on a different thread than the one where the JSlider is being updated, I'd recommend making your audioClip a volatile variable. That might help with cross-thread weirdness that sometimes occurs.
Thread.sleep(1) at best can only update every millisecond. On some systems (older Windows) the method's reliance on the system clock means the actual updates are as slow as 16 millis apart. But updating the JSlider at more than 60 fps is probably moot. Screen monitors often are set to 60Hz, and there's only so much the human eye can take in.
Also there is only so much the ear can discern in terms of timing. For example, it is hard to tell if two percussive events happen at the same time if there is less than a couple milliseconds difference.
There are several issues with your code.
As Phil Freihofner pointed out, the sleep(1) and the treatment of the isRunning and isPause fields look highly dubious. To some extent, this is unrelated to your actual question, but worth noting here, because it may also cause problems later.
Regardless of that, the approach that Zoran Regvart showed is basically the way to go. The code in the given form may have suffered from some rounding issues. However, the general idea for cases like this is always the same:
You have a source interval [minA...maxA]
You have a target interval [minB...maxB]
You want a mapping between the two
In this case, it's a good practice to normalize the intervals. That is, to map the value from the source interval to a value between 0.0 and 1.0, and then map this normalized value to the target interval.
In the most generic form, this can be written as
long minA = ...
long maxA = ...
long a = ... // The current value in the source interval
int minB = ...
int maxB = ...
int b; // The value to compute in the target interval
// Map the first value to a value between 0.0 and 1.0
double normalized = (double)(a - minA)/(maxA-minA);
b = (int)(minB + normalized * (maxB - minB));
Fortunately, your "min" values are all zero here, so it's a bit simpler. Here is a MCVE (with some dummy classes). The most relevant part is the updateSlider method at the bottom.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class SliderMappingTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JButton startButton = new JButton("Start");
final JSlider progressSlider = new JSlider(0, 1000, 0);
startButton.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
startButton.setEnabled(false);
SliderMappingDummyAudioClip audioClip =
new SliderMappingDummyAudioClip();
SliderMappingDummyPlayer player =
new SliderMappingDummyPlayer(progressSlider, audioClip);
player.start();
}
});
f.getContentPane().setLayout(new GridLayout());
f.getContentPane().add(startButton);
f.getContentPane().add(progressSlider);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class SliderMappingDummyAudioClip
{
private long startMicros;
void start()
{
startMicros = System.nanoTime() / 1000L;
}
long getMicrosecondLength()
{
// 10 seconds
return 10L * 1000L * 1000L;
}
long getMicrosecondPosition()
{
return (System.nanoTime() / 1000L) - startMicros;
}
public boolean isRunning()
{
return getMicrosecondPosition() <= getMicrosecondLength();
}
}
class SliderMappingDummyPlayer
{
private final SliderMappingDummyAudioClip audioClip;
private final JSlider slider;
SliderMappingDummyPlayer(
JSlider slider,
SliderMappingDummyAudioClip audioClip)
{
this.slider = slider;
this.audioClip = audioClip;
}
void start()
{
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
doRun();
}
});
t.setDaemon(true);
t.start();
}
private void doRun()
{
audioClip.start();
while (audioClip.isRunning())
{
updateSlider();
try
{
Thread.sleep(30);
}
catch (InterruptedException ex)
{
Thread.currentThread().interrupt();
return;
}
}
}
private void updateSlider()
{
long currentMicros = audioClip.getMicrosecondPosition();
// Compute the progress as a value between 0.0 and 1.0
double progress =
(double)currentMicros / audioClip.getMicrosecondLength();
// Compute the slider value to indicate the progress
final int sliderValue = (int)(progress * slider.getMaximum());
System.out.println("update "+progress);
// Update the slider with the new value, on the Event Dispatch Thread
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
slider.setValue(sliderValue);
}
});
}
}
Are you sure you want to set the maximum to current position?
How about mapping longs to ints by division:
long coefficient = clip.getMicrosecondLength() / Integer.MAX_VALUE;
slider.setMinimum(0);
slider.setMaximum((int) (clip.getMicrosecondLength() / coefficient));
...
slider.setValue((int) (clip.getMicrosecondPosition() / coefficient));
I want to make a slider in Java, similar to JSlider, but it can have muliple sliders within itself. To be precise, I want to implement the slider used in Fixed Width Functionality in Microsoft Excel(From Data-> Fixed Width).
A normal slider has just one point, which we can drag to various positions. Now if i click within a position at a slider, a new point should get added, and the dragging operation of this point should be independent of the previous ones. Also if I do a double click, the point is removed from the slider.
import java.awt.*;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.plaf.basic.BasicSliderUI;
public class MultiSlider extends JComponent
{
public static void main(String[] args)
{ // main method just for showing a usage example
try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
catch(Exception ex){}
JFrame f=new JFrame();
final MultiSlider slider = new MultiSlider();
slider.setValue(0, 80);
slider.addValue(20);
f.getContentPane().add(slider);
f.setSize(200, 100);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
public MultiSlider()
{
super.setLayout(null);
addSlider(0);
}
public void setValue(int slider, int value)
{
((JSlider)getComponent(slider)).setValue(value);
}
public void addValue(int value)
{
addSlider(value);
}
#Override
public boolean isOptimizedDrawingEnabled()
{
return false;
}
#Override
public void doLayout()
{
Insets i=getInsets();
int x=i.left, y=i.top, width=getWidth()-x-i.right, height=getHeight()-y-i.bottom;
for(int ix=0, n=getComponentCount(); ix<n; ix++)
getComponent(ix).setBounds(x, y, width, height);
}
class SubSlider extends JSlider
{
private SubSlider active;
#Override
protected void processMouseEvent(MouseEvent e)
{
SubSlider sl=getClosestSlider(e);
if(e.getID()==MouseEvent.MOUSE_PRESSED) active=sl;
else if(e.getID()==MouseEvent.MOUSE_RELEASED) active=null;
if(e.getID()==MouseEvent.MOUSE_CLICKED)
{
if(sl==null && e.getClickCount()==1) addSlider(e.getPoint());
else if(sl!=null && e.getClickCount()==2)
{
removeSlider(sl);
return;
}
}
if(sl!=null) sl.realProcessMouseEvent(e);
}
private void realProcessMouseEvent(MouseEvent e)
{
e.setSource(this);
super.processMouseEvent(e);
}
#Override
protected void processMouseMotionEvent(MouseEvent e)
{
if(e.getID()==MouseEvent.MOUSE_MOVED)
toAllSliders(e);
else
{
if(active==null) active=getClosestSlider(e);
if(active!=null) active.realProcessMouseMotionEvent(e);
}
}
private void realProcessMouseMotionEvent(MouseEvent e)
{
e.setSource(this);
super.processMouseMotionEvent(e);
}
}
final void toAllSliders(MouseEvent e)
{
for(int ix=0, n=getComponentCount(); ix<n; ix++)
((SubSlider)getComponent(ix)).realProcessMouseMotionEvent(e);
}
public void removeSlider(SubSlider sl)
{
if(getComponentCount()<=1) return;// must keep the last slider
remove(sl);
JSlider slider=(JSlider)getComponent(getComponentCount()-1);
slider.setOpaque(true);
slider.setPaintTrack(true);
revalidate();
repaint();
}
final SubSlider getClosestSlider(MouseEvent e)
{
SubSlider s=(SubSlider)getComponent(0);
BasicSliderUI bsUI=(BasicSliderUI)s.getUI();
int value = bsUI.valueForXPosition(e.getX());
if(Math.abs(s.getValue()-value)<=1) return s;
for(int ix=1, n=getComponentCount(); ix<n; ix++)
{
s=(SubSlider)getComponent(ix);
if(Math.abs(s.getValue()-value)<=1) return s;
}
return null;
}
void addSlider(Point point)
{
BasicSliderUI bsUI = (BasicSliderUI)((JSlider)getComponent(0)).getUI();
addSlider(bsUI.valueForXPosition(point.x));
}
void addSlider(int value)
{
final JSlider slider = new SubSlider();
slider.setFocusable(false);
slider.setValue(value);
if(getComponentCount()!=0)
{
slider.setOpaque(false);
slider.setPaintTrack(false);
}
super.add(slider, 0);
revalidate();
repaint();
}
}
I search the forum and see this codes:
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
System.out.println(" and it's a double click!");
wasDoubleClick = true;
} else {
Integer timerinterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty(
"awt.multiClickInterval");
timer = new Timer(timerinterval.intValue(), new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (wasDoubleClick) {
wasDoubleClick = false; // reset flag
} else {
System.out.println(" and it's a simple click!");
}
}
});
timer.setRepeats(false);
timer.start();
}
}
but the code runs incorrectly(Sometime it prints out " and it's a single click!" 2 times . It should print out " and it's a double click!"). Can anybody show me why? or can you give me some better ways to do this?
Thank you!
Sometime it prints out " and it's a single click!" 2 times . It should print out " and it's a double click!").
That is normal. A double click only happens if you click twice within the specified time interval. So sometimes if you don't click fast enough you will get two single clicks in a row.
Integer timerinterval = (Integer) Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
The above line of code determines how fast the double click must be.
For what its worth here is some code I have used to do the same thing. Don't know if its any better or worse than the code you have:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ClickListener extends MouseAdapter implements ActionListener
{
private final static int clickInterval = (Integer)Toolkit.getDefaultToolkit().
getDesktopProperty("awt.multiClickInterval");
MouseEvent lastEvent;
Timer timer;
public ClickListener()
{
this(clickInterval);
}
public ClickListener(int delay)
{
timer = new Timer( delay, this);
}
public void mouseClicked (MouseEvent e)
{
if (e.getClickCount() > 2) return;
lastEvent = e;
if (timer.isRunning())
{
timer.stop();
doubleClick( lastEvent );
}
else
{
timer.restart();
}
}
public void actionPerformed(ActionEvent e)
{
timer.stop();
singleClick( lastEvent );
}
public void singleClick(MouseEvent e) {}
public void doubleClick(MouseEvent e) {}
public static void main(String[] args)
{
JFrame frame = new JFrame( "Double Click Test" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.addMouseListener( new ClickListener()
{
public void singleClick(MouseEvent e)
{
System.out.println("single");
}
public void doubleClick(MouseEvent e)
{
System.out.println("double");
}
});
frame.setSize(200, 200);
frame.setVisible(true);
}
}
public void mouseClicked(MouseEvent evt) {
if (evt.getButton()==MouseEvent.BUTTON1){
leftClick = true; clickCount = 0;
if(evt.getClickCount() == 2) doubleClick=true;
Integer timerinterval = (Integer)Toolkit.getDefaultToolkit().getDesktopProperty("awt.multiClickInterval");
timer = new Timer(timerinterval, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(doubleClick){
System.out.println("double click.");
sb = new StringBuffer();
sb.append("Double Click");
clickCount++;
if(clickCount == 2){
clickCount=0;
doubleClick = false;
}
} else {
sb = new StringBuffer();
sb.append("Left Mouse");
System.out.println("single click.");
}
}
});
timer.setRepeats(false);
timer.start();
if(evt.getID()==MouseEvent.MOUSE_RELEASED) timer.stop();
}