I'm trying to make a Java application that simulates someone typing on their keyboard. The keystroke sound is played in a loop (Java chose a keystroke sound among others randomly and plays it) at a variable interval (to simulate a real person typing).
It works fine in the beginning, but after around the 95th iteration, it stops playing the sound (while still looping) for less than 4 seconds then plays the sound again. And after the 160th iteration, it plays the sound almost every second (instead of every third to sixth of a second).
After a while, it stops playing the sound for a long time, then forever.
Here is the source for the AudioPlayer.java class:
package entity;
import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
public class AudioPlayer implements Runnable {
private String audioFilePath;
public void setAudioFilePath(String audioFilePath) {
this.audioFilePath = audioFilePath;
}
#Override
public void run() {
File audioFile = new File(audioFilePath);
try {
AudioInputStream audioStream = AudioSystem.getAudioInputStream(audioFile);
AudioFormat format = audioStream.getFormat();
DataLine.Info info = new DataLine.Info(Clip.class, format);
Clip audioClip = (Clip) AudioSystem.getLine(info);
audioClip.open(audioStream);
audioClip.start();
boolean playCompleted = false;
while (!playCompleted) {
try {
Thread.sleep(500);
playCompleted = true;
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
}
audioClip.close();
} catch (UnsupportedAudioFileException ex) {
System.out.println("The specified audio file is not supported.");
ex.printStackTrace();
} catch (LineUnavailableException ex) {
System.out.println("Audio line for playing back is unavailable.");
ex.printStackTrace();
} catch (IOException ex) {
System.out.println("Error playing the audio file.");
ex.printStackTrace();
}
}
}
And here is the Main.java class to test the keystroke simulator:
package sandbox;
import java.util.Random;
import entity.AudioPlayer;
public class Main {
public static void main(String[] args) {
Random rnd = new Random();
AudioPlayer audio;
for(int i = 0; i < 10000; i++) {
int delay = rnd.nextInt(200)+75;
try {
Thread.sleep(delay);
}
catch (InterruptedException ie) {}
int index = rnd.nextInt(3)+1;
audio = new AudioPlayer();
audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
Thread thread = new Thread(audio);
thread.start();
System.out.println("iteration "+i);
}
}
}
I used multiple short (less than 200ms) wave files of different sounding keystrokes (3 in total) all in the resources directory.
EDIT
I read your answers and comments. And I'm thinking maybe I misundertood them because the suggested solutions don't work, or maybe I should have made myself clear on what I exactly wanted. Also, I need to note that I don't use threads often (and have no clue what a mutex is).
So I'll first explain what I exactly want the program to do. It should be able to simulate keystroke and so I used a Thread because it allows two keystroke sounds to overlap just like when a real person is typing. Basically the sound clips I am using are keystroke sounds and a keystroke sound is composed of two sounds:
the sound of a key being pressed.
the sound of a key being released.
If at some point the program allows two keystroke to overlap it will sound as if someone pressed one key then another and then released the first key. That's how really typing sounds like!
Now the issues I encountered using the proposed solutions are:
When calling the run() method of the AudioPlayer directly,
public static void main(String[] args)
{
// Definitions here
while (running) {
Date previous = new Date();
Date delay = new Date(previous.getTime()+rnd.nextInt(300)+75);
// Setting the audio here
audio.run();
Date now = new Date();
if (now.before(delay)) {
try {
Thread.sleep(delay.getTime()-now.getTime());
} catch (InterruptedException e) {
}
}
System.out.println("iteration: "+(++i));
}
}
the sounds play sequentially (one after the other) and at a rate that depends on the sleep duration of the AudioPlayer (or depends on the delay if the delay in the main() method is higher than the sleep duration of the AudioPlayer), which is no good because it won't sound like the average typist (more like someone who is new to typing and still looking for every keys when typing).
When calling the join() method of the AudioPlayer's Thread,
public static void main(String[] args)
{
//Variable definitions here
while (running) {
int delay = rnd.nextInt(200)+75;
try
{
Thread.sleep(delay);
}
catch (InterruptedException ie)
{
}
//Setting the AudioPlayer and creating its Thread here
thread.start();
try
{
thread.join();
}
catch(InterruptedException ie)
{
}
System.out.println("iteration "+(++i));
}
}
the sounds play sequentially as well and at a rate that depends on the sleep duration of the AudioPlayer (or depends on the delay if the delay in the main() method is higher than the sleep duration of the AudioPlayer) which, again, is no good for the same reason as before.
So, to answer one of the commenter's question. Yes! there are other concerns not expressed before which require the threads in the first place.
I found a workaround that "solves" my issue (but that I don't consider as a proper solution since I am, in a way, cheating): What I did is increase the sleep duration of the AudioPlayer to something that is unlikely to be reached before the program is stopped (24 hours) and from what I've seen it doesn't use much resources even after more than an hour.
You can check out what I want, what I get when running the suggested solutions and what I get using my workaround on this youtube videos (Unfortunately StackOverflow doesn't have video uploading feature. so I had to put it on youtube).
EDIT
The sound effects can be downloaded here.
How about this single-threaded solution which is a cleaner version of your own, but re-using already opened clips from buffers? To me the typing sounds pretty natural even though there are no two sounds playing at the same time. You can adjust the typing speed by changing the corresponding static constants in the Application class.
package de.scrum_master.stackoverflow.q61159885;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine.Info;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static javax.sound.sampled.AudioSystem.getAudioInputStream;
import static javax.sound.sampled.AudioSystem.getLine;
public class AudioPlayer implements Closeable {
private final Map<String, Clip> bufferedClips = new HashMap<>();
public void play(String audioFilePath) throws IOException, UnsupportedAudioFileException, LineUnavailableException {
Clip clip = bufferedClips.get(audioFilePath);
if (clip == null) {
AudioFormat audioFormat = getAudioInputStream(new File(audioFilePath)).getFormat();
Info lineInfo = new Info(Clip.class, audioFormat);
clip = (Clip) getLine(lineInfo);
bufferedClips.put(audioFilePath, clip);
clip.open(getAudioInputStream(new File(audioFilePath)));
}
clip.setMicrosecondPosition(0);
clip.start();
}
#Override
public void close() {
bufferedClips.values().forEach(Clip::close);
}
}
package de.scrum_master.stackoverflow.q61159885;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.IOException;
import java.util.Random;
public class Application {
private static final Random RANDOM = new Random();
private static final int ITERATIONS = 10000;
private static final int MINIMUM_WAIT = 75;
private static final int MAX_RANDOM_WAIT = 200;
public static void main(String[] args) throws UnsupportedAudioFileException, IOException, LineUnavailableException {
try (AudioPlayer audioPlayer = new AudioPlayer()) {
for (int i = 0; i < ITERATIONS; i++) {
sleep(MINIMUM_WAIT + RANDOM.nextInt(MAX_RANDOM_WAIT));
audioPlayer.play(randomAudioFile());
}
}
}
private static void sleep(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) {}
}
private static String randomAudioFile() {
return "resources/keystroke-0" + (RANDOM.nextInt(3) + 1) + ".wav";
}
}
You might have noticed that the AudioPlayer is Closeable, i.e. you can use "try with resouces" in the calling application. That way it makes sure that at the end of the program all clips are closed automatically.
The key to replaying the same clip is of course clip.setMicrosecondPosition(0) before you start.
Update: If you want to simulate multiple persons, just modify the main class like this. BTW, I don't know anything about audio programming and whether there is a way to better deal with mixers and overlapping sounds. It is just a proof of concept in order to give you an idea. There is one thread per person, but each person types in a serial fashion, not two keys at the same time. But multiple persons can overlap because there is one AudioPlayer per person with its own set of buffered clips.
package de.scrum_master.stackoverflow.q61159885;
import java.util.Random;
public class Application {
private static final Random RANDOM = new Random();
private static final int PERSONS = 2;
private static final int ITERATIONS = 10000;
private static final int MINIMUM_WAIT = 150;
private static final int MAX_RANDOM_WAIT = 200;
public static void main(String[] args) {
for (int p = 0; p < PERSONS; p++)
new Thread(() -> {
try (AudioPlayer audioPlayer = new AudioPlayer()) {
for (int i = 0; i < ITERATIONS; i++) {
sleep(MINIMUM_WAIT + RANDOM.nextInt(MAX_RANDOM_WAIT));
audioPlayer.play(randomAudioFile());
}
} catch (Exception ignored) {}
}).start();
}
private static void sleep(int delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException ignored) {}
}
private static String randomAudioFile() {
return "resources/keystroke-0" + (RANDOM.nextInt(3) + 1) + ".wav";
}
}
Besides the things EdwinBuck said, I believe you are doing way to much work in your AudioPlayer class (and every time). Try pre-create an AudioPlayer instance one time for all of your audio files (I believe it is 4?), and add a separate play() method, so that inside your loop you can do something like audioplayers[index].play().
Also note that in your AudioPlayer class you are waiting 500ms for the sound to finish which is longer than you wait to play the next sound. This will - after a while - lead to you running out of available threads... perhaps there's a callback you could use when the AudioClip is finished, instead of waiting.
With threads, you are talking about independent flows of execution. Your program is designed such that the picking of a delay an the playing of the sound are not independent.
something like
for (int i = 0; i < 10000; i++) {
int delay = rnd.nextInt(200)+75;
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
}
int index = rnd.nextInt(3)+1;
audio = new AudioPlayer();
audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
audio.run();
System.out.println("iteration "+i);
}
Would express that you "wait" then "run a wav" then "repeat".
Right now you're relying on consistent scheduling of threads of execution on the cores to get the desired result; except that threads aren't intended to be the way to express consistent scheduling of execution. Threads are intended to be the way to express independent scheduling of execution.
Odds are between your current wait and the current "play the wave" a few other things get in between, and with enough time, perhaps even the wav files could be played out-of-order. I'd make the ordering explicit.
If you need good control on the timing within the loop, look to a gaming loop type setup. It's similar to your for loop, but looks like
while (running) {
SomeTimeType previous = new TimeType();
SomeTimeOffset delay = new TimeOffset(rnd.nextInt(200)+75);
...
audio.run();
SomeTimeType now = new TimeType();
if (now.minus(offset).compareTo(previous) > 0) {
try {
Thread.sleep(now.minus(offset).toMillis())
} catch (InterruptedException e) {
}
}
}
The primary difference here is that your random delays start from the beginning of the wav file's play time to the beginning of the next wav file's play time, and there is no delay between files if the delay is shorter than the wav file's play time.
Also, I'd look into if the AudioPlayer can be reused between wave file playbacks, as that will probably get you even better results.
Now on the off chance you really need to keep the playing in a separate thread, you need to join the thread of the loop to the thread of the AudioPlayer to ensure that the AudioPlayer finishes before the loop thread advances. Even though you are waiting a longer time in the for loop, remember, any process can come off the CPU core at any time, so your wait isn't an assurance that the for loop takes more time per iteration than the AudioPlayer takes per wav file, if the CPU had to handle something else (like a network packet).
for (int i = 0; i < 10000; i++) {
int delay = rnd.nextInt(200)+75;
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
}
int index = rnd.nextInt(3)+1;
audio = new AudioPlayer();
audio.setAudioFilePath("resources/keystroke-0"+index+".wav");
Thread thread = new Thread(audio);
thread.start();
thread.join();
System.out.println("iteration "+i);
}
The thread.join() will force the for loop to go into a sleep state (possibly shifting it off the CPU) until the audio thread completes.
The library AudioCue was made for exactly this sort of thing. You might try running the "frog pond" demo, simulating a number of frogs all croaking, all generated from a single frog croak recording.
You can take a single typewriter click and run everything from it, create a cue with, say 10 simultaneous overlaps allowed. Then use an RNG to pick which of the 10 "cursors" to click. The 10 typists can each have their own volume & pan location, and can be pitched slightly differently so that it sounds like the typewriters are different models or the keys are being hit with different weights (as if an old manual typewriter).
One can tweak RNG algorithms for the different typing speeds (using varying sleep times).
For myself, I wrote an event system where the play commands are queued up on an event system using a ConcurrentSkipListSet, where the stored objects include a timing value (milliseconds after a given zero point) that is used for sorting as well as controlling when the play gets executed. That might be overkill if you don't intend to do this sort of thing very often.
Related
I am creating "Air Percussion" using IMU sensors and Arduino to communicate with computer (3 separate IMUs and Arduinos). They are connected to the computer through USBs. I am gathering data on separate Threads (each thread for each sensor). When I connect only one "set" my program is working really fast. I can get even 5 plays of sound per second. Unfortunatelly when i am trying to connect 3 sensors and run them on separate Threads at the same time my program slows down horribly. Even when im moving only one of sensors, I can get like 1 "hit" per second and sometimes it's even losing some of the sounds it should play. I'll show only important parts of the code below.
In the main i've got ActionListener for button, where it should start gathering the data. I run there 3 separate Threads for each USB Port.
connectButton.addActionListener(new ActionListener(){
#Override public void actionPerformed(ActionEvent arg0) {
int dialogButton = 1;
if(!flagaKalibracjiLewa || !flagaKalibracjiPrawa){ //some unimportant flags
dialogButton = JOptionPane.showConfirmDialog(null, "Rozpoczynając program bez kalibracji będziesz miał do dyspozycji mniejszą ilość dzwięków. Czy chcesz kontynuować?","Warning",JOptionPane.YES_NO_OPTION);
}else{
dialogButton = JOptionPane.YES_OPTION;
}
if(dialogButton == JOptionPane.YES_OPTION){
if(connectButton.getText().equals("Connect")) {
if(!flagaKalibracjiLewa && !flagaKalibracjiPrawa) podlaczPorty();
Thread thread = new Thread(){
#Override public void run() {
Scanner data = new Scanner(chosenPort.getInputStream());
dataIncoming(data, "lewa");
data.close();
}
};
Thread thread2 = new Thread(){
#Override public void run() {
Scanner data = new Scanner(chosenPort2.getInputStream());
dataIncoming(data, "prawa");
data.close();
}
};
Thread thread3 = new Thread(){
#Override public void run() {
Scanner data = new Scanner(chosenPort3.getInputStream());
dataIncoming(data, "stopa");
data.close();
}
};
thread.start();
thread2.start();
thread3.start();
connectButton.setText("Disconnect");
} else {
// disconnect from the serial port
chosenPort.closePort();
chosenPort2.closePort();
chosenPort3.closePort();
portList.setEnabled(true);
portList2.setEnabled(true);
portList3.setEnabled(true);
connectButton.setText("Connect");
}
}
}
});
in "dataIncoming" method there is bunch of not important things (like picking, which sound should be played etc.). The important part is in the while loop. In the "while" im gathering next lines of data from sensor. When one of the values is higher than something it should play a sound but only if some time has passed and the sensor has moved a certain way. (when the drumstick is going down the "imuValues[4]" is increasing, when its going up its decreasing, so when its past 160 it means that the player has taken the drumstick up so its ready for the next hit)
while(data.hasNextLine()) {
try{
imuValues = data.nextLine().split(",");
if(Double.parseDouble(imuValues[4])>200 && flagaThreada) {
flagaThreada = false;
playSound(sound1);
}
if(Double.parseDouble(imuValues[4])<160 && System.currentTimeMillis()-startTime>100) {
flagaThreada = true;
startTime=System.currentTimeMillis();
}
}catch(Exception e){
System.out.println("ERROR");
}
}
and finally the method for playing the sound is :
public static synchronized void playSound(String sound) {
try {
String url = "/sounds/"+sound+".wav";
Clip clip = AudioSystem.getClip();
AudioInputStream inputStream = AudioSystem.getAudioInputStream(
Main.class.getResourceAsStream(url));
clip.open(inputStream);
clip.start();
} catch (Exception e) {
System.err.println("ERROR IN OPENING");
}
}
Is my computer to slow to compute and play sounds for 3 sensors at the same time? Or is there a way to create those Threads in a better fashion?
I wrote a version of Clip, called AudioCue, which allows multi-threading on the play commands. It is open source, BSD license (free), consists of three files which you can cut and paste into your program. There is also an API link for it. More info at AudioCue. The site has code examples as well as link to API and source code. There is also some dialogue about its use at Java-gaming.org, under the "Sound" topic thread.
The basic principle behind the code is to make the audio data available in a float array, and send multiple, independent "cursors" through it (one per play command). The setup lets us also do real time volume fading, pitch changes and panning. The audio is output via a SourceDataLine which you can configure (set thread priority, buffer size).
I'm maybe a week or two away from sharing a more advanced version that allows all AudioCues to be mixed through a single output line. This version has five classes/interfaces instead of three, and is being set up for release on github. I'm also hoping to get a donate button and the like set up for this next iteration. The next version might be more useful for Arduino in that I believe you are only allowed up to 8 audio outputs on that system.
Other than that, the steps you have taken (separating the open from the play, using setFramePosition for restarts) are correct. I can't think of anything else to add to help out besides writing your own mixer/cue player (as I have done and am willing to share).
Sorry for my bad english
I write a Java desktop application that plays musical instruments audio files samples.
Each time a note is received by the application, it must stop the current playing note and play the new one. if the user stops playing, the app must do a fade-out to the active note.
By the way, smaller latency is better.
I created a runnable which owns a play and stop method allowing me to play or stop a note(with fade out)
My code works but the sound quality is bad(many click and clipping etc)..
-How to improve my code? Did I make mistake?
-Otherwise it is there any other Java technology than JavaFx media player that is best suited for my need?
public class SamplePlayer implements Runnable{
private boolean fadeOut=false; // true when a fade out is needed
private int soundToFadeOut=0; //the sound number to fade out
private int sound=0; // the sound number to play (nothing if zero)
private int lastSound=0; //the last sound number played that wee need to stop befor playing a new one
MediaPlayer[] sample = new MediaPlayer[42]; //array to store all pre-loaded sounds
public SamplePlayer()
{
for(int i=0;i<42;i++) // load all sounds
{
Media pick = new Media(new File("./src/files/samples/1_"+(i+55)+".wav" ).toURI().toString());
sample[i] = new MediaPlayer(pick);
}
}
/**
* Here i try to play a sound each time the sound variable has changed
* and fade out the sound specified in soundToFadeOut variable if fadeOut variable come to true;
*/
#Override
public void run() {
while(true) // bad, but it's just for testing
{
try {
if(sound!=0) // if a sound need to be played
{
if(lastSound!=0) // if i's not the first sound to played i stop the previous one.
{
sample[lastSound].stop();
}
sample[sound].setVolume(1); //returns the volume to 1 in case we made a fade out
lastSound=sound;
sound=0;//sound = 0 to monitor new other sound
}
if(fadeOut==true) // if wee need to make a fade out
{
fadeOut=false;
fadeOut(sample[soundToFadeOut],50);
}
Thread.sleep(1); // if not the thread keep busy and play, stop method not working properly
} catch (InterruptedException ex) {
Logger.getLogger(SamplePlayer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private void fadeOut(MediaPlayer player,double itteration) // fade out method : run a new thread to itterate on volume
{Runnable task = () -> {
try{
for(int i=0;i<itteration;i++)
{
double value=(itteration-i)/itteration; // set value to 1.0 to near 0.0 each itteration
if(value<0.1)
{
value=0;
}
player.setVolume(value);
Thread.sleep(5);
}
}catch(Exception e){e.printStackTrace();}
};
new Thread(task).start();
}
/*
*
* play and stop method which are called when a sound need to be played or stoped (fade out)
*
*/
public void play(int note)
{
sound=note-55; // -55 just to match with our array range
}
public void stop(int note)
{
soundToFadeOut=note-55;
fadeOut=true;
}
}
Hello!
I am trying to display a text on the Screen (with Java), but I want it to be delayed, like, every 0.1 seconds, a letter of the text would appear on the screen. It's like Pokemons dialogs. Here's what I am talking about: https://www.youtube.com/watch?v=yUS1IcC5CBY
I don't want the fade and the acceleration of the text, I just want the text to appear letter-by-letter. Also, I would like the text to be a String. Please, can you help me?
Thanks a lot in advance!
You can use two methods:
One is Thread.sleep(), which is shown above:
private static String message = "Your Message";
private static JLable label = new JLabel();
private static String labelMessage = "";
for(int i = 0; i < message.length(); i++){
labelMessage += Character.toString(message.charAt(i));
label.setText(labelMessage);
try{
Thread.sleep(howManyMillisecondsYouShouldWait);//if you want to do it every .1
//seconds, just wait 100 milliseconds.
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
}
that will forever print it to the screen every 100 milliseconds. However, the only trouble with using Thread.sleep is (and I somehow just learned this the other day, even though I've been programming for a long while) it is not always accurate. It may sleep 100 ms, it may sleep 150, etc. Secondly, a slower computer may take longer to sleep through it.
The other method which you will use more often (probably) is to check the actual time of your system and see if it's been long enough since you last printed it to the screen, like this:
private static long timeOfLastWrite;//at what time did you last update the text?
private static long deltaTimeSinceLastWrite;//how long has it been since you last updated the text?
private static long timeOfFirstWrite;//when did you start?
private static long deltaTimeSinceFirstWrite;//how long has it been since you started?
private static String message = "Your Message";
private static JLabel label = new JLabel();
private static String labelMessage = "";
//print once here:
timeOfFirstWrite = System.currentTimeMillis();
timeOfLastWrite = System.currentTimeMillis();//every time you print to the screen, make
//sure that you make note of it by setting the timeOfLastWrite variable equal to the current time.
labelMessage += Character.toString(message.chatAt(0));
while(!labelMessage.equals(message)){
deltaTimeSinceLastWrite = System.currentTimeMillis() - timeOfLastWrite;
if(deltaTimeSinceLastWrite >= 100){
timeOfLastWrite = System.currentTimeMillis();
deltaTimeSinceFirstWrite = System.currentTimeMillis() - timeOfFirstWrite;
int currentIndexOfChain = (int) deltaTimeSinceFirstWrite / 100;
if(currentIndexOfChain >= message.length()){
currentIndexOfChain = message.length() - 1;
}
labelMessage = message.substring(0, currentIndexOfChain + 1);
label.setText(labelMessage);
}
}
This method isn't even slightly necessary for a program so simple as writing text to the screen 10 times a second. However, it's good to get into the practice of it. You'll learn that if you create a character and tell him to move 10 pixels, Thread.sleep(100), and move again and etc... that on a slower computer, the character will move slower. However, if you tell it to wait until a certain amount of time has passed according to your computer's time, if the user lags out and it takes 200 milliseconds before it tells the character to move again, you can account for that by simply making him move twice as far -- I think it's called framerate independence.
If I did anything wrong with the delta time management please let me now. Again, I just learned about this the other day even though I've been programming for awhile, so don't worry about it too much if you're just now learning to program.
And that's how you make an incredibly long (possibly too long) answer to an incredibly simple question. I hope you benefit from this response.
I'm unable to use Thread.sleep(x) or wait(): java.lang.InterruptedException; must be caught or declared to be thrown
try {
Thread.sleep(100);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
You may use this code for doing so. Simply put a thread to print the text onto a jLabel.
new Thread(new Runnable() {
#Override
public void run() {
String x="";
String txt="Hello this is a sample text. Let us see how this works.";
for(int i=0;i<txt.length();i++){
try {
jLabel1.setText(x=x+txt.charAt(i));
Thread.sleep(100);
} catch(InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}).start();
How about this?
import javax.swing.Timer;
import java.awt.event.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class DelayText
{
public String example = "As you can see, this sentence is being printed out a character at a time.";
public String transfer = "";
public Timer t;
public int i = 0;
public JFrame f;
public JLabel l;
public DelayText()
{
f = new JFrame("Example");
l = new JLabel();
f.add(l);
f.setSize(450, 200);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
TimerListener tl = new TimerListener();
t = new Timer(100, tl);
t.start();
}
public static void main(String[] args)
{
DelayText d = new DelayText();
}
private class TimerListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(i < example.length())
{
transfer += (example.charAt(i));
l.setText(transfer);
i++;
}
if(i >= example.length())
{
t.stop();
}
}
}
}
I am using the timer to create a delay between each character outputted on the JFrame. I noticed a lot of these other ones were a bit more complex, thought this might make things a bit easier to understand.
What I am doing is making a program that constantly takes screenshots of the users desktop and saves them as long as the user wants. I was initially placed a call to the method that captures in a while method but that was too slow and I need as many images take as soon as possible. What I decided to do was use threading.
While the program is running my PC get's slow (ie mouse flashes, moves slow, ect) and when I stop the program I get the following error.
Exception in thread "Thread-294" java.lang.OutOfMemoryError: Java heap space
at sun.awt.windows.WRobotPeer.getRGBPixels(Unknown Source)
at java.awt.Robot.createScreenCapture(Unknown Source)
at maple.Record.run(Record.java:29)
Here is the line that produces the error.
img = r.createScreenCapture(new Rectangle (0, 0, width, height));
Here is my class that contains the thread.
import java.awt.AWTException;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.media.MediaLocator;
public class Record implements Runnable {
Robot r;
static int width = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
static int height = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
BufferedImage img;
public static boolean stop = false;
public void run() {
try {
r = new Robot();
img = r.createScreenCapture(new Rectangle (0, 0, width, height));
ImageIO.write(img, "png", new File(JavCapture.tmpLocation + "\\tmp\\" + System.currentTimeMillis() + ".png"));
} catch (IOException | AWTException e) { e.printStackTrace(); }
}
}
The error is produced when I am calling the JpegImagesToMovies that has been edited to work with .png files.
Please check out this link for more information on that.
How can I solve the problem, and how can I make the thread use less memory so that it doesn't slow the PC down.
Here is the call in the main method.
do {
(new Thread(new Record())).start();
} while (!Record.stop);
From the looks of it you are creating a separate thread for every screenshot, this will run you out of memory very fast. Instead you should have a single thread that sits in loop, sleeps and takes screenshots from time to time.
public class ScreenshotTaker implements Runnable {
private volatile boolean done = false;
public void run( ) {
while (!done) {
... take screenshot...
...sleep ...
}
}
public void setDone( ) {
done = true;
}
}
You're asking for trouble, you should go for some Threadpool implementation that would limit your thread count. To get idea on usage, see the official tutorial: http://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html
As in your solution:
do {
(new Thread(new Record())).start();
} while (!Record.stop);
you create threads faster than they can process their job. Basically you just say create many threads as fast as possible, there is no guarantee on count of them processed till new one is created. So it's just a question of time till program dies (resources are exhausted = CPU + Memory)
You should limit the thread creation process here
(new Thread(new Record())).start();
Those threads start almost together and bloats your memory up.
With so little code to go with it is hard to say, but you should definitely make the following:
Robot r;
BufferedImage img;
into local variables instead of instance variables. The way you have it, the objects they refer to will be reachable for much longer than your program actually needs them. That's one road to an OutOfMemoryException.
I have a program that rolls dice, and uses a new thread to loop through in order to update the image and repaint. Here is my code:
public int roll()
{
new Thread(
new Runnable() {
public void run() {
synchronized(o) {
o.notify();
for (int i = 0; i < 10; i++) {
image = randomImage();
repaint();
try {
Thread.sleep(100);
}
catch(InterruptedException ex) {
System.out.println("InterruptedException caught");
}
}
}
}
}
).start();
synchronized(o) {
try {
o.wait();
}
catch(InterruptedException ex) {
System.out.println("InterruptedException caught");
}
}
return rolled;
}
In my other class, I have:
int rolled = dicePanel.roll();
label.setText("Rolled a + rolled");
The problem is that with the current code with synchronization, the dice images do not animate, but do return the correct int rolled. Without the synchronized code, the images will animate but the roll method will return a 0 as the int because it does not let the other thread finish.
Is there any way to have the image code loop through and repaint each time, but wait until the thread has finished to return the int rolled?
This looks like an overly complicated solution. You should certainly perform your dice rolling / image updating in thread other than the EDT, but you needn't split that task into two separate threads.
Just have one thread that fiddles with your dice images then when it's finished doing that, it can set the chosen dice value in your label (and presumably in your image too).
Put the o.notify(); to the end of the run() method. Btw. using notifyAll() should be preferred. Or you may find useful the Future object pattern. Here is an article about it http://www.vogella.com/articles/JavaConcurrency/article.html
Or if you are developing Swing application look to SwingWorker. However, SwingWorker is probably overkill for this task.
Wait¬ify is quite low level api and there are many good abstraction in Java to work with concurrency.