/** * File: AudioPlayer.java * Author: Brian Borowski * Date created: August 1, 2011 * Date last modified: January 21, 2013 */ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; 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.Mixer; import javax.sound.sampled.UnsupportedAudioFileException; public class AudioPlayer implements Runnable { public static final int LAUNCH_MISSILE = 0, SPACESHIP_IMPACT = 1, LAUNCHER_IMPACT = 2; private final Clip[] clips = new Clip[3]; private final boolean[] play = new boolean[3]; private Thread thread; private boolean isRunning; private static AudioPlayer audioPlayer = null; private AudioPlayer() { // We use the Java Sound Audio Engine because the default JApplet // AudioClip and even Direct Audio Device sometimes skip playing a sound. // The Java Sound Audio Engine is quite slow, so it will be run from // within a separate thread to keep the animation responsive. Mixer mixer = null; final Mixer.Info[] mixers = AudioSystem.getMixerInfo(); for (int i = 0; i < mixers.length; i++) { if ("Primary Sound Driver".equals(mixers[i].getName())) { mixer = AudioSystem.getMixer(mixers[i]); } } clips[LAUNCH_MISSILE] = loadClip(mixer, "audio/launch_missile.wav"); clips[SPACESHIP_IMPACT] = loadClip(mixer, "audio/spaceship_impact.wav"); clips[LAUNCHER_IMPACT] = loadClip(mixer, "audio/launcher_impact.wav"); start(); } public static AudioPlayer getInstance() { if (audioPlayer == null) { audioPlayer = new AudioPlayer(); } return audioPlayer; } private void start() { isRunning = true; thread = new Thread(this); thread.start(); } public static void stop() { try { audioPlayer.isRunning = false; if (audioPlayer.thread != null) { audioPlayer.thread.join(); } } catch (final InterruptedException ie) { } audioPlayer = null; } public void run() { while (isRunning) { final long startTime = System.currentTimeMillis(); boolean stopped = false; synchronized (this) { for (int i = LAUNCH_MISSILE; i <= LAUNCHER_IMPACT; i++) { if (play[i] && clips[i].isRunning()) { clips[i].stop(); stopped = true; } } if (stopped) { try { // This is a hack. If I don't give the Clip some time // to stop, the sound is garbled the next time it is // played. Thread.sleep(1); } catch (final InterruptedException ie) { } } for (int i = LAUNCH_MISSILE; i <= LAUNCHER_IMPACT; i++) { if (play[i]) { clips[i].setFramePosition(0); clips[i].start(); play[i] = false; } } } long sleepTime = 10 - (System.currentTimeMillis() - startTime); if (sleepTime < 0) { sleepTime = 10; } try { // Millisecond resolution sleep Thread.sleep(sleepTime); } catch (final InterruptedException ie) { } } } public synchronized void play(final int clipIndex) { play[clipIndex] = true; } private static Clip loadClip(final Mixer mixer, final String filename) { URL url = AudioPlayer.class.getResource(filename); if (url == null) { try { final File file = new File(filename); if (file.canRead()) url = file.toURI().toURL(); } catch (final IllegalArgumentException iae) { // URL is not absolute. } catch (final MalformedURLException murle) { // Not a URL. } } Clip clip = null; if (url == null) { System.err.println("Audio file " + filename + " not found."); return clip; } try { final AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(url); if (mixer != null) { final DataLine.Info info = new DataLine.Info(Clip.class, audioInputStream.getFormat()); clip = (Clip)mixer.getLine(info); } else { clip = AudioSystem.getClip(); } clip.open(audioInputStream); } catch (final UnsupportedAudioFileException uafe) { } catch (final IOException ioe) { } catch (final LineUnavailableException lue) { } return clip; } }