package tmcsim.cadsimulator; import java.io.File; import java.io.FileInputStream; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import sun.audio.AudioPlayer; import sun.audio.AudioStream; 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 $ $Revision: 1.3 $ */ 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 AudioStream audioStream = 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(); AudioPlayer.player.stop(audioStream); 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) { File audioFile = new File(baseAudioDir + theClip.fileName); if(audioFile.exists()) { audioStream = new AudioStream (new FileInputStream(audioFile)); AudioPlayer.player.start(audioStream); } 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(); } }