Playing multiple sound clips using Clip objects
Solution 1:
There's a couple of ways you might be able to achieve this, but the basic idea is, you want to register a LineListener
to a Clip
and monitor for the LineEvent.Type.STOP
event and reenable the button
For example. This looks for all the .wav
files in a given directory and creates a button for each one. When clicked, the button (or more importantly, the underlying Action
) is disabled and the audio is played. When it STOP
s, the Action
(and the button by extension) is re-enabled.
The Sound API can play multiple sounds simultaneous anyway
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
File[] musicFiles = new File("a directory somewhere").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".wav");
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
for (File music : musicFiles) {
try {
JButton btn = new JButton(new AudioAction(music.getName(), music.toURI().toURL()));
add(btn, gbc);
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
}
}
}
public class AudioAction extends AbstractAction {
private URL audio;
public AudioAction(String name, URL audioSource) {
super(name);
this.audio = audioSource;
}
public URL getAudioSource() {
return audio;
}
@Override
public void actionPerformed(ActionEvent e) {
setEnabled(false);
try (InputStream is = getAudioSource().openStream()) {
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(is);
Clip play = AudioSystem.getClip();
play.addLineListener(new LineListener() {
@Override
public void update(LineEvent event) {
System.out.println(event.getFramePosition());
if (event.getType().equals(LineEvent.Type.STOP)) {
setEnabled(true);
}
}
});
play.open(audioInputStream);
play.start();
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException exp) {
exp.printStackTrace();
}
}
}
}
nb: I tried using Clip#drain
(in a background thread), but it only worked for the first clip, subsequent clips basically skipped over the method, thus the reason I went for the LineListener
Now with better resource management
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
File[] musicFiles = new File("...").listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().toLowerCase().endsWith(".wav");
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
for (File music : musicFiles) {
try {
JButton btn = new JButton(new AudioAction(music.getName(), music.toURI().toURL()));
add(btn, gbc);
} catch (MalformedURLException exp) {
exp.printStackTrace();
}
}
}
}
public class AudioAction extends AbstractAction {
private AudioPlayer player;
public AudioAction(String name, URL audioSource) {
super(name);
player = new AudioPlayer(audioSource);
}
@Override
public void actionPerformed(ActionEvent e) {
if (player.isPlaying()) {
player.stop();
} else {
try {
player.play();
} catch (IOException | LineUnavailableException | UnsupportedAudioFileException ex) {
ex.printStackTrace();
}
}
}
}
public class AudioPlayer {
private Clip clip;
private URL url;
public AudioPlayer(URL url) {
this.url = url;
}
public boolean isPlaying() {
return clip != null && clip.isRunning();
}
protected void open() throws IOException, LineUnavailableException, UnsupportedAudioFileException {
clip = AudioSystem.getClip();
clip.open(AudioSystem.getAudioInputStream(url.openStream()));
}
public void play() throws IOException, LineUnavailableException, UnsupportedAudioFileException {
if (clip == null || !clip.isRunning()) {
open();
clip.setFramePosition(0);
clip.start();
}
}
public void stop() {
if (clip != null && clip.isRunning()) {
clip.stop();
clip.flush();
dispose();
}
}
public void dispose() {
try {
clip.close();
} finally {
clip = null;
}
}
}
}
Solution 2:
The one clip per button should be fine. When the user clicks the button run this to restart the clip:
sound.stop();
sound.setFramePosition(0);// set the location to the start of the file
sound.play();// restart your sound