/* * Audio Clip Player TCSS 305 - Autumn 2008 (Zimmerman) */ package tetris.audio; import java.io.IOException; import java.util.HashMap; import java.util.Map; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.Clip; import javax.sound.sampled.Line; import javax.sound.sampled.LineEvent; import javax.sound.sampled.LineListener; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.UnsupportedAudioFileException; /** * A class to play audio clips. Caches previously-played clips, allowing fast * re-playback of previously played sounds. * * @author Marty Stepp * @author Daniel M. Zimmerman (code cleanup) * @version 1.0 */ public class SoundPlayer { /** * A cache of previously-played audio clips. */ private final Map my_clips = new HashMap(); /** * Plays the audio file with the given file name. This method returns * instantly, without waiting for the clip to finish playing. * * @param the_filename The name of the file (relative to the current working * directory) to play. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip play(final String the_filename) throws IllegalArgumentException { final Clip clip = getClip(the_filename); if (clip != null) { clip.start(); } return clip; } /** * Plays the audio file with the given file name, waiting until the clip is * done playing before returning. * * @param the_filename The name of the file (relative to the current working * directory) to play. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip playAndWait(final String the_filename) throws IllegalArgumentException { final Clip clip = getClip(the_filename); if (clip != null) { clip.start(); try { synchronized (clip) { while (clip.isRunning()) { clip.wait(); // wait for clip to finish } } } catch (final InterruptedException ie) { // ignored return clip; } } return clip; } /** * Plays the clip with the given file name in a continuous loop. The clip * keeps looping until it is later stopped by calling the stop() method. This * function returns instantly * * @param the_filename The name of the file (relative to the current working * directory) to play. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip loop(final String the_filename) throws IllegalArgumentException { return loop(the_filename, Clip.LOOP_CONTINUOUSLY); } /** * Plays the clip with the given file name in a loop. The clip loops until it * has played the specified number of times, or until it is later stopped by * calling the stop() method. This function returns instantly, without waiting * for the clip to finish looping. * * @param the_filename The name of the file (relative to the current working * directory) to play. * @param the_number_of_times The number of times to loop the clip. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip loop(final String the_filename, final int the_number_of_times) throws IllegalArgumentException { final Clip clip = getClip(the_filename); if (clip != null) { clip.loop(the_number_of_times); } return clip; } /** * Plays the clip with the given file name in a loop. The clip loops * continuously until it is stopped by calling the stop() method. This method * waits until the clip is done looping before returning. Note that since the * clip loops continuously, this method will not return unless some other * thread stops the clip. * * @param the_filename The name of the file (relative to the current working * directory) to play. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip loopAndWait(final String the_filename) throws IllegalArgumentException { return loopAndWait(the_filename, Clip.LOOP_CONTINUOUSLY); } /** * Plays the clip with the given file name in a loop. The clip loops until it * has played the specified number of times, or until it is stopped by calling * the stop() method. This method waits until the clip is done looping before * returning. * * @param the_filename The name of the file (relative to the current working * directory) to play. * @param the_number_of_times The number of times to loop the clip. * @return a Clip object representing the sound played. (for convenience; this * result need not be used/stored). * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip loopAndWait(final String the_filename, final int the_number_of_times) throws IllegalArgumentException { final Clip clip = getClip(the_filename); if (clip != null) { clip.loop(the_number_of_times); try { while (clip.isRunning()) { clip.wait(); // wait for clip to finish } } catch (final InterruptedException ie) { return clip; } } return clip; } /** * Pauses the clip with the given file name. If the clip is later played, it * will resume from where it was paused. Calling this method does not resume a * thread that is suspended on a playAndWait() or a loopAndWait(). * * If stop() is called on a paused clip, it will reset to the beginning of the * clip for the next play. * * @param the_filename The name of the file (relative to the current working * directory) to pause. * @exception IllegalArgumentException if there is a problem reading from or * playing the sound file. */ public void pause(final String the_filename) throws IllegalArgumentException { final Clip clip = getClip(the_filename); if (clip != null) { final int pos = clip.getFramePosition(); clip.stop(); clip.setFramePosition(pos); } } /** * Stops the clip with the specified filename (and wakes up any threads * waiting for it to finish playing). * * @param the_filename The name of the file (relative to the current working * directory) to stop. * @return a Clip object representing the sound stopped. * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip stop(final String the_filename) throws IllegalArgumentException { final Clip clip = getClip(the_filename); stopClip(clip); return clip; } /** * Stops all currently playing sound clips (and wakes up the threads waiting * for them to finish playing). */ public void stopAll() { for (Clip clip : my_clips.values()) { stopClip(clip); } } /** * Preloads the clip at the given file name. This means the clip will be * available faster, when requested for playing the first time. * * @param the_filename The name of the file (relative to the current working * directory) to preload. * @return a Clip object representing the preloaded sound. * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ public Clip preLoad(final String the_filename) throws IllegalArgumentException { return getClip(the_filename); } /** * Returns a Clip object for a filename, either by creating a new one or * loading it from the cache. * * @param the_filename The name of the file (relative to the current working * directory) to load. * @return a Clip object, or null if one is not found. * @exception IllegalArgumentException if there is a problem reading from the * sound file. */ private Clip getClip(final String the_filename) throws IllegalArgumentException { Clip clip = null; AudioInputStream ais = null; if (my_clips.containsKey(the_filename)) { clip = my_clips.get(the_filename); } else { // read audio file from disk try { ais = AudioSystem.getAudioInputStream(ClassLoader.getSystemResource(the_filename)); clip = (Clip) AudioSystem.getLine(new Line.Info(Clip.class)); clip.open(ais); clip.addLineListener(new LineListener() { /** * Responds to audio events generated by clips. * * @param the_event The event generated. */ public void update(final LineEvent the_event) { if (the_event.getType() == LineEvent.Type.STOP) { // clip is done playing stopClip((Clip) the_event.getSource()); } } }); my_clips.put(the_filename, clip); } catch (final UnsupportedAudioFileException uafe) { throw new IllegalArgumentException("Not a valid supported audio file: \"" + the_filename + "\""); } catch (final LineUnavailableException lue) { throw new IllegalArgumentException("Line is not available to play sound \"" + the_filename + "\""); } catch (final IOException ioe) { throw new IllegalArgumentException("I/O error while reading file: \"" + the_filename + "\""); } } return clip; } /** * Stops the playing of the specified clip. * * @param the_clip The clip. */ private void stopClip(final Clip the_clip) { if (the_clip != null) { synchronized (the_clip) { the_clip.stop(); the_clip.setFramePosition(0); the_clip.notifyAll(); // awaken threads waiting for this Clip } } } } // end of class SoundPlayer