Reputation: 995
I coded these two classes to test how I can use Java Sound API:
enum SoundEffect
: enumeration encapsulates all the sound I used.
Class SoundEffectDemo
: to Test the SoundEffect enum in a Swing application
When I click on any button of SoundEffectDemo (CALLING,RING,BUSY), the sound starts playing. And when I click on Stop Sound button, the sound is stopped. But when I click on on any button of SoundEffectDemo (CALLING,RING,BUSY) second time, there is no any sound.
import java.io.*;
import java.net.URL;
import javax.sound.sampled.*;
public enum SoundEffect {
BUSY("resources/phone-busy.wav"),
CALLING("resources/phone-calling.wav"),
DISCONNECT("resources/phone-disconnect.wav"),
RING("resources/telephone-ring.wav");
// Each sound effect has its own clip, loaded with its own sound file.
private Clip clip;
private URL url;
private AudioInputStream audioInputStream;
// Constructor to construct each element of the enum with its own sound file.
SoundEffect(String soundFileName) {
try {
// Use URL (instead of File) to read from disk and JAR.
this.url = this.getClass().getClassLoader().getResource(soundFileName);
// Set up an audio input stream piped from the sound file.
this.audioInputStream = AudioSystem.getAudioInputStream(url);
// Get a clip resource.
clip = AudioSystem.getClip();
// Open audio clip and load samples from the audio input stream.
clip.open(audioInputStream);
clip.addLineListener( new LineListener() {
public void update(LineEvent evt) {
if (evt.getType() == LineEvent.Type.STOP) {
evt.getLine().close();
}
}
});
} catch (UnsupportedAudioFileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (LineUnavailableException e) {
e.printStackTrace();
}
}
// Play or Re-play the sound effect from the beginning, by rewinding.
public void play() {
clip.loop(Clip.LOOP_CONTINUOUSLY);
}
public void stop(){
clip.stop(); // Stop the player if it is still running
}
// Optional static method to pre-load all the sound files.
static void init() {
values(); // calls the constructor for all the elements
}
public boolean isActive(){
return clip.isActive();
}
public boolean isOpen() {
return clip.isOpen();
}
public void setFramePosition() {
clip.setFramePosition(0);
}
}
import java.awt.*;
import java.awt.event.*;
import javax.sound.sampled.LineEvent;
import javax.swing.*;
// Testing the SoundEffect enum in a Swing application
@SuppressWarnings("serial")
public class SoundEffectDemo extends JFrame {
// Constructor
public SoundEffectDemo() {
// Pre-load all the sound files
// Set up UI components
Container cp = this.getContentPane();
cp.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
JButton btnSound1 = new JButton("CALLING");
btnSound1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SoundEffect.CALLING.play();
}
});
cp.add(btnSound1);
JButton btnSound2 = new JButton("RING");
btnSound2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SoundEffect.RING.play();
}
});
cp.add(btnSound2);
JButton btnSound3 = new JButton("BUSY");
btnSound3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SoundEffect.BUSY.play();
}
});
cp.add(btnSound3);
JButton btnSound4 = new JButton("Stop Sound ");
btnSound4.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
for(SoundEffect value : SoundEffect.values()){
if(value.isActive()){
value.stop();
}
}
}
});
cp.add(btnSound4);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("Test SoundEffct");
this.pack();
this.setVisible(true);
}
public static void main(String[] args) {
new SoundEffectDemo();
}
}
Upvotes: 0
Views: 3177
Reputation: 1518
It is not really clear what you mean by restart playing audio in java.
Your code is not relevant because from what it looks like, you've put everything in one single code block which makes it hard to read. You're missing several brackets. But back to your question. I've put together a simple class which does what you're asking for.
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
public class PlaybackWAV {
private final int BUFFER_SIZE = 128000;
private AudioInputStream audioStream;
private AudioFormat audioFormat;
private SourceDataLine sourceLine;
private boolean running = false;
/**
* @param filename
* the name of the file that is going to be played
*/
public synchronized void playSound(final String filename) {
new Thread(new Runnable() {
public void run() {
try {
InputStream audioSrc = this.getClass().getResourceAsStream(filename);
// add buffer for mark/reset support
InputStream bufferedIn = new BufferedInputStream(audioSrc);
audioStream = AudioSystem.getAudioInputStream(bufferedIn);
audioFormat = audioStream.getFormat();
DataLine.Info info = new DataLine.Info(
SourceDataLine.class, audioFormat);
sourceLine = (SourceDataLine) AudioSystem.getLine(info);
sourceLine.open(audioFormat);
sourceLine.start();
running = true;
while (running) {
int nBytesRead = 0;
byte[] abData = new byte[BUFFER_SIZE];
while (nBytesRead != -1) {
try {
nBytesRead = audioStream.read(abData, 0,
abData.length);
} catch (IOException e) {
e.printStackTrace();
}
if (nBytesRead >= 0) {
sourceLine.write(abData, 0, nBytesRead);
}
}
if (running)
audioStream.reset();
}
sourceLine.drain();
sourceLine.close();
} catch (Exception e) {
System.err
.printf("Exception occured while trying to playback file '%s'. (%s)%n",
filename, e.getLocalizedMessage());
e.printStackTrace();
}
}
}).start();
}
public void stopSound() {
this.running = false;
if (sourceLine != null) {
sourceLine.stop();
sourceLine.drain();
sourceLine.close();
}
}
}
There is two methods in this class, playSound which starts a new thread and plays a specific sound in your class folder and stopSound which obviously stops whatever sound that is currently playing. Here is a simple snippet which will create three buttons to play either sound of the three you have in your example.
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Snippet extends JFrame {
public Snippet(){
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
JPanel panel = new JPanel(new FlowLayout());
this.getContentPane().add(panel);
final PlaybackWAV player = new PlaybackWAV();
JButton play1 = new JButton("CALLING");
panel.add(play1);
JButton play2 = new JButton("RING");
panel.add(play2);
JButton play3 = new JButton("BUSY");
panel.add(play3);
JButton play4 = new JButton("STOP");
panel.add(play4);
this.pack();
play1.setAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player.stopSound();
player.playSound("resources/phone-calling.wav");
}
});
play2.setAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player.stopSound();
player.playSound("resources/telephone-ring.wav");
}
});
play3.setAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player.stopSound();
player.playSound("resources/phone-busy.wav");
}
});
play4.setAction(new AbstractAction(){
@Override
public void actionPerformed(ActionEvent arg0) {
player.stopSound();
}
});
}
public static void main(String[] args){
Snippet snippet = new Snippet();
snippet.setVisible(true);
}
}
Happy coding!
EDIT:
My apologies, I forgot to invoke the 'mark' method and therefor the reset method wouldn't work properly. Here is the new code:
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
public class PlaybackWAV {
private final int BUFFER_SIZE = 128000;
private AudioInputStream audioStream;
private AudioFormat audioFormat;
private SourceDataLine sourceLine;
private boolean running = false;
/**
* @param filename
* the name of the file that is going to be played
*/
public synchronized void playSound(final String filename) {
new Thread(new Runnable() {
public void run() {
try {
InputStream audioSrc = this.getClass().getResourceAsStream(filename);
// add buffer for mark/reset support
InputStream bufferedIn = new BufferedInputStream(audioSrc);
audioStream = AudioSystem.getAudioInputStream(bufferedIn);
audioFormat = audioStream.getFormat();
DataLine.Info info = new DataLine.Info(
SourceDataLine.class, audioFormat);
sourceLine = (SourceDataLine) AudioSystem.getLine(info);
sourceLine.open(audioFormat);
sourceLine.start();
running = true;
while (running) {
audioStream.mark(BUFFER_SIZE);
int nBytesRead = 0;
byte[] abData = new byte[BUFFER_SIZE];
while (nBytesRead != -1) {
try {
nBytesRead = audioStream.read(abData, 0,
abData.length);
} catch (IOException e) {
e.printStackTrace();
}
if (nBytesRead >= 0) {
sourceLine.write(abData, 0, nBytesRead);
}
}
if (running)
audioStream.reset();
}
sourceLine.drain();
sourceLine.close();
} catch (Exception e) {
System.err
.printf("Exception occured while trying to playback file '%s'. (%s)%n",
filename, e.getLocalizedMessage());
e.printStackTrace();
}
}
}).start();
}
public void stopSound() {
this.running = false;
if (sourceLine != null) {
sourceLine.stop();
sourceLine.drain();
sourceLine.close();
}
}
}
Upvotes: 0
Reputation: 37875
clip.addLineListener( new LineListener() {
public void update(LineEvent evt) {
if (evt.getType() == LineEvent.Type.STOP) {
evt.getLine().close();
}
}
});
This is probably not something you want to do, closing the line when it receives the stop event. After closing the Clip you would need to reopen it before it will play again.
Given what you're doing it's probably the case you don't need to close the line at all. Calling close indicates you are done with it.
See Line#close
and AutoCloseable#close
.
A more appropriate interim action for your stop event would be drain
or flush
. Then perhaps set the frame position to 0 if you want to "reset" it.
So remove the call to close
and do something like
public void stop() {
clip.stop();
clip.flush();
clip.setFramePosition(0);
}
As a side note, I noticed you aren't starting your Swing application on the Event Dispatch Thread. You should always initialize your GUI with a call to invokeLater
, e.g.:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new SoundEffectDemo();
}
});
}
Thread safety with Swing is important because not doing it can lead to subtle errors that are difficult to diagnose.
Upvotes: 1