package tmcsim.cadsimulator; import jaco.mp3.player.MP3Player; import java.applet.*; import java.io.File; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import tmcsim.client.cadclientgui.data.IncidentEvent; /** * SoundPlayer is used to play audio files associated with IncidentEvents that * occur during a simulation. When the SoundPlayer has been enabled audio clips * are enqueued by calling the enqueueClip() method. The audio files are played * in order of receipt and when finished, the IncidentEvent is notified. Audio * playing may be disabled or enabled through the setAudioEnabled() method. * Disabling audio will cause a currently playing audio clip to stop and be * requeued at the front of the queue. Re-enabling the audio will then continue * playing all queued clips. The deQueueAll() method is used to clear the list * of audio clips that have been queued. * * @author Matthew Cechini (mcechini@calpoly.edu) * @version Date: 2006/06/06 20:46:41 * @author Nicolas Russo modified to play mp3 audio. * @version 2016.07.03 */ public class SoundPlayer extends Thread { /** * Error Logger. */ private Logger soundLogger = Logger.getLogger("tmcsim.cadsimulator"); /** * The base location that wav files are referenced from */ private String baseAudioDir = ""; /** * The private vector of audioClips used during a simulation */ private Vector enqueuedClips = null; /** * The audio clip that is currently being played by the AudioPlayer. */ private AudioClipInfo currentClip = null; /** * Audio stream for playing audio files. */ private AudioClip clip = null; private MP3Player mp3Player = null; /** * Flag to designate when an audio file is playing. This prevents multiple * audio files from being played simultaneously. Flag is initialized to * false. */ private boolean audioPlaying = false; /** * Flag to designate whether audio playing is enabled. If audio is disabled, * then enquque events will be ignored. Flag is initialized to false. */ private boolean audioEnabled = false; /** * Timer used to pause audio player during an audio file. */ private Timer timer = null; /** * Inner class used to contain the audio file's name and duration. */ private class AudioClipInfo { String fileName; int duration; IncidentEvent theEvent; AudioClipInfo(IncidentEvent ie) { fileName = ie.waveFile; duration = ie.waveLength; theEvent = ie; } AudioClipInfo(String name, int dur) { fileName = name; duration = dur; theEvent = null; } public void wavePlayed() { if (theEvent != null) { theEvent.wavePlayed(); } } } /** * Constructor. Establish the base file path for wav file referencing. * * @param wavFilePath Pathname for where the simulation wav files will be * referenced. */ public SoundPlayer(String baseFilePath) { baseAudioDir = baseFilePath; audioPlaying = false; audioEnabled = true; timer = new Timer(); enqueuedClips = new Vector(); } /** * Add a audioClipInfo object to the audioClips Vector. The enqueued audio * file will play when all previously enqueued audio files have been played. * If audio is not enabled, the clip will not be queued, and the incident * event will be notified that the audio clip has finished playing to allow * the simulation to continue. * * @param ie The Incident Event to enqueue to the current list of events. */ public void enqueueClip(IncidentEvent ie) { if (audioEnabled) { synchronized (enqueuedClips) { enqueuedClips.add(new AudioClipInfo(ie)); } } else { ie.wavePlayed(); } } /** * Method called when user presses reset in simulation. All queued events * need to be deQueued. */ public void deQueueAll() { synchronized (enqueuedClips) { enqueuedClips.clear(); } } /** * Get the current audio enabled status. * * @return True if enabled, false if disabled. */ public boolean getAudioEnabled() { return audioEnabled; } /** * Set the current audio enabled status. A false value will cause all * enqueue events to be ignored. A currently playing audio file will be * stopped and requeued. All other queued events will remain in the queue. * * @param enable True if enabling, false if disabling. */ public void setAudioEnabled(boolean enable) { if (!enable) { timer.cancel(); if (clip != null) { clip.stop(); } if (mp3Player != null) { mp3Player.stop(); } if (currentClip != null) { enqueuedClips.add(0, currentClip); } audioPlaying = false; } audioEnabled = enable; } /** * Method declaration for the Thread.run() method. While this thread is not * interrupted, check if there are enqueued audio files. If so, remove the * first one(FIFO) create an AudioClip object, and play the audio file. A * timer tis then started for the duration of the audio file. When the timer * expires the audioPlaying flag is reset to false and the corresponding * incident event is notified of the audio file's completion. */ public void run() { AudioClipInfo theClip = null; while (!isInterrupted()) { synchronized (enqueuedClips) { if (enqueuedClips.size() > 0 && !audioPlaying && audioEnabled) { theClip = enqueuedClips.remove(0); } } //if nothing to play, wait a second if (theClip == null) { try { Thread.sleep(1000); } catch (Exception e) { } } else { try { if (theClip.duration > 0) { String filename = baseAudioDir + theClip.fileName; String fileExt = filename.substring(filename.lastIndexOf('.')); File audioFile = new File(filename); if (audioFile.exists() && fileExt.equals(".mp3")) { mp3Player = new MP3Player(audioFile); mp3Player.play(); } else if (audioFile.exists() && fileExt.equals(".wav")) { clip = Applet.newAudioClip(audioFile.toURI().toURL()); clip.play(); } else { throw new Exception(); } } currentClip = theClip; audioPlaying = true; timer = new Timer(); timer.schedule(new TimerTask() { public void run() { clipPlayed(); audioPlaying = false; } }, theClip.duration * 1000); theClip = null; } catch (Exception e) { soundLogger.logp(Level.WARNING, "SoundPlayer", "run", "Unable to play audio file: " + baseAudioDir + theClip.fileName, e); theClip.theEvent.wavePlayed(); theClip = null; timer.cancel(); } } } } /** * Called when audio file finishes playing. Notifies the corresponding * Incident Event that its audio file has finished playing. */ private void clipPlayed() { currentClip.wavePlayed(); } }