|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  */ | 
|  |  | 
|  | package com.sun.media.sound; | 
|  |  | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.io.BufferedInputStream; | 
|  | import java.io.ByteArrayOutputStream; | 
|  | import java.applet.AudioClip; | 
|  |  | 
|  | import javax.sound.sampled.AudioSystem; | 
|  | import javax.sound.sampled.Clip; | 
|  | import javax.sound.sampled.AudioInputStream; | 
|  | import javax.sound.sampled.AudioFormat; | 
|  | import javax.sound.sampled.DataLine; | 
|  | import javax.sound.sampled.SourceDataLine; | 
|  | import javax.sound.sampled.LineEvent; | 
|  | import javax.sound.sampled.LineListener; | 
|  | import javax.sound.sampled.UnsupportedAudioFileException; | 
|  |  | 
|  | import javax.sound.midi.MidiSystem; | 
|  | import javax.sound.midi.MidiFileFormat; | 
|  | import javax.sound.midi.MetaMessage; | 
|  | import javax.sound.midi.Sequence; | 
|  | import javax.sound.midi.Sequencer; | 
|  | import javax.sound.midi.InvalidMidiDataException; | 
|  | import javax.sound.midi.MidiUnavailableException; | 
|  | import javax.sound.midi.MetaEventListener; | 
|  |  | 
|  | /** | 
|  |  * Java Sound audio clip; | 
|  |  * | 
|  |  * @author Arthur van Hoff, Kara Kytle, Jan Borgersen | 
|  |  * @author Florian Bomers | 
|  |  */ | 
|  |  | 
|  | public final class JavaSoundAudioClip implements AudioClip, MetaEventListener, LineListener { | 
|  |  | 
|  |     private static final boolean DEBUG = false; | 
|  |     private static final int BUFFER_SIZE = 16384;  | 
|  |  | 
|  |     private long lastPlayCall = 0; | 
|  |     private static final int MINIMUM_PLAY_DELAY = 30; | 
|  |  | 
|  |     private byte loadedAudio[] = null; | 
|  |     private int loadedAudioByteLength = 0; | 
|  |     private AudioFormat loadedAudioFormat = null; | 
|  |  | 
|  |     private AutoClosingClip clip = null; | 
|  |     private boolean clipLooping = false; | 
|  |  | 
|  |     private DataPusher datapusher = null; | 
|  |  | 
|  |     private Sequencer sequencer = null; | 
|  |     private Sequence sequence = null; | 
|  |     private boolean sequencerloop = false; | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private final static long CLIP_THRESHOLD = 1048576; | 
|  |      | 
|  |     private final static int STREAM_BUFFER_SIZE = 1024; | 
|  |  | 
|  |     public JavaSoundAudioClip(InputStream in) throws IOException { | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.<init>"); | 
|  |  | 
|  |         BufferedInputStream bis = new BufferedInputStream(in, STREAM_BUFFER_SIZE); | 
|  |         bis.mark(STREAM_BUFFER_SIZE); | 
|  |         boolean success = false; | 
|  |         try { | 
|  |             AudioInputStream as = AudioSystem.getAudioInputStream(bis); | 
|  |              | 
|  |             success = loadAudioData(as); | 
|  |  | 
|  |             if (success) { | 
|  |                 success = false; | 
|  |                 if (loadedAudioByteLength < CLIP_THRESHOLD) { | 
|  |                     success = createClip(); | 
|  |                 } | 
|  |                 if (!success) { | 
|  |                     success = createSourceDataLine(); | 
|  |                 } | 
|  |             } | 
|  |         } catch (UnsupportedAudioFileException e) { | 
|  |              | 
|  |             try { | 
|  |                 MidiFileFormat mff = MidiSystem.getMidiFileFormat(bis); | 
|  |                 success = createSequencer(bis); | 
|  |             } catch (InvalidMidiDataException e1) { | 
|  |                 success = false; | 
|  |             } | 
|  |         } | 
|  |         if (!success) { | 
|  |             throw new IOException("Unable to create AudioClip from input stream"); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public synchronized void play() { | 
|  |         startImpl(false); | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public synchronized void loop() { | 
|  |         startImpl(true); | 
|  |     } | 
|  |  | 
|  |     private synchronized void startImpl(boolean loop) { | 
|  |          | 
|  |         long currentTime = System.currentTimeMillis(); | 
|  |         long diff = currentTime - lastPlayCall; | 
|  |         if (diff < MINIMUM_PLAY_DELAY) { | 
|  |             if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+"): abort - too rapdly"); | 
|  |             return; | 
|  |         } | 
|  |         lastPlayCall = currentTime; | 
|  |  | 
|  |         if (DEBUG || Printer.debug) Printer.debug("JavaSoundAudioClip.startImpl(loop="+loop+")"); | 
|  |         try { | 
|  |             if (clip != null) { | 
|  |                 // We need to disable autoclosing mechanism otherwise the clip | 
|  |                 // can be closed after "!clip.isOpen()" check, because of | 
|  |                  | 
|  |                 clip.setAutoClosing(false); | 
|  |                 try { | 
|  |                     if (!clip.isOpen()) { | 
|  |                         clip.open(loadedAudioFormat, loadedAudio, 0, | 
|  |                                   loadedAudioByteLength); | 
|  |                     } else { | 
|  |                         clip.flush(); | 
|  |                         if (loop != clipLooping) { | 
|  |                              | 
|  |                             clip.stop(); | 
|  |                         } | 
|  |                     } | 
|  |                     clip.setFramePosition(0); | 
|  |                     if (loop) { | 
|  |                         clip.loop(Clip.LOOP_CONTINUOUSLY); | 
|  |                     } else { | 
|  |                         clip.start(); | 
|  |                     } | 
|  |                     clipLooping = loop; | 
|  |                 } finally { | 
|  |                     clip.setAutoClosing(true); | 
|  |                 } | 
|  |             } else if (datapusher != null ) { | 
|  |                 datapusher.start(loop); | 
|  |                 if (DEBUG || Printer.debug)Printer.debug("Stream should be playing/looping"); | 
|  |  | 
|  |             } else if (sequencer != null) { | 
|  |                 sequencerloop = loop; | 
|  |                 if (sequencer.isRunning()) { | 
|  |                     sequencer.setMicrosecondPosition(0); | 
|  |                 } | 
|  |                 if (!sequencer.isOpen()) { | 
|  |                     try { | 
|  |                         sequencer.open(); | 
|  |                         sequencer.setSequence(sequence); | 
|  |  | 
|  |                     } catch (InvalidMidiDataException e1) { | 
|  |                         if (DEBUG || Printer.err)e1.printStackTrace(); | 
|  |                     } catch (MidiUnavailableException e2) { | 
|  |                         if (DEBUG || Printer.err)e2.printStackTrace(); | 
|  |                     } | 
|  |                 } | 
|  |                 sequencer.addMetaEventListener(this); | 
|  |                 try { | 
|  |                     sequencer.start(); | 
|  |                 } catch (Exception e) { | 
|  |                     if (DEBUG || Printer.err) e.printStackTrace(); | 
|  |                 } | 
|  |                 if (DEBUG || Printer.debug)Printer.debug("Sequencer should be playing/looping"); | 
|  |             } | 
|  |         } catch (Exception e) { | 
|  |             if (DEBUG || Printer.err)e.printStackTrace(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     public synchronized void stop() { | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->stop()"); | 
|  |         lastPlayCall = 0; | 
|  |  | 
|  |         if (clip != null) { | 
|  |             try { | 
|  |                 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.flush()"); | 
|  |                 clip.flush(); | 
|  |             } catch (Exception e1) { | 
|  |                 if (Printer.err) e1.printStackTrace(); | 
|  |             } | 
|  |             try { | 
|  |                 if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip: clip.stop()"); | 
|  |                 clip.stop(); | 
|  |             } catch (Exception e2) { | 
|  |                 if (Printer.err) e2.printStackTrace(); | 
|  |             } | 
|  |             if (DEBUG || Printer.debug)Printer.debug("Clip should be stopped"); | 
|  |  | 
|  |         } else if (datapusher != null) { | 
|  |             datapusher.stop(); | 
|  |             if (DEBUG || Printer.debug)Printer.debug("Stream should be stopped"); | 
|  |  | 
|  |         } else if (sequencer != null) { | 
|  |             try { | 
|  |                 sequencerloop = false; | 
|  |                 sequencer.addMetaEventListener(this); | 
|  |                 sequencer.stop(); | 
|  |             } catch (Exception e3) { | 
|  |                 if (Printer.err) e3.printStackTrace(); | 
|  |             } | 
|  |             try { | 
|  |                 sequencer.close(); | 
|  |             } catch (Exception e4) { | 
|  |                 if (Printer.err) e4.printStackTrace(); | 
|  |             } | 
|  |             if (DEBUG || Printer.debug)Printer.debug("Sequencer should be stopped"); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     // Event handlers (for debugging) | 
|  |  | 
|  |     public synchronized void update(LineEvent event) { | 
|  |         if (DEBUG || Printer.debug) Printer.debug("line event received: "+event); | 
|  |     } | 
|  |  | 
|  |     // handle MIDI track end meta events for looping | 
|  |  | 
|  |     public synchronized void meta( MetaMessage message ) { | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("META EVENT RECEIVED!!!!! "); | 
|  |  | 
|  |         if( message.getType() == 47 ) { | 
|  |             if (sequencerloop){ | 
|  |                  | 
|  |                 sequencer.setMicrosecondPosition(0); | 
|  |                 loop(); | 
|  |             } else { | 
|  |                 stop(); | 
|  |             } | 
|  |         } | 
|  |     } | 
|  |  | 
|  |  | 
|  |     public String toString() { | 
|  |         return getClass().toString(); | 
|  |     } | 
|  |  | 
|  |  | 
|  |     protected void finalize() { | 
|  |  | 
|  |         if (clip != null) { | 
|  |             if (DEBUG || Printer.trace)Printer.trace("JavaSoundAudioClip.finalize: clip.close()"); | 
|  |             clip.close(); | 
|  |         } | 
|  |  | 
|  |          | 
|  |         if (datapusher != null) { | 
|  |             datapusher.close(); | 
|  |         } | 
|  |  | 
|  |         if (sequencer != null) { | 
|  |             sequencer.close(); | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     // FILE LOADING METHODS | 
|  |  | 
|  |     private boolean loadAudioData(AudioInputStream as)  throws IOException, UnsupportedAudioFileException { | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip->openAsClip()"); | 
|  |  | 
|  |          | 
|  |         as = Toolkit.getPCMConvertedAudioInputStream(as); | 
|  |         if (as == null) { | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         loadedAudioFormat = as.getFormat(); | 
|  |         long frameLen = as.getFrameLength(); | 
|  |         int frameSize = loadedAudioFormat.getFrameSize(); | 
|  |         long byteLen = AudioSystem.NOT_SPECIFIED; | 
|  |         if (frameLen != AudioSystem.NOT_SPECIFIED | 
|  |             && frameLen > 0 | 
|  |             && frameSize != AudioSystem.NOT_SPECIFIED | 
|  |             && frameSize > 0) { | 
|  |             byteLen = frameLen * frameSize; | 
|  |         } | 
|  |         if (byteLen != AudioSystem.NOT_SPECIFIED) { | 
|  |              | 
|  |             readStream(as, byteLen); | 
|  |         } else { | 
|  |              | 
|  |             readStream(as); | 
|  |         } | 
|  |  | 
|  |         // if everything went fine, we have now the audio data in | 
|  |          | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |  | 
|  |  | 
|  |     private void readStream(AudioInputStream as, long byteLen) throws IOException { | 
|  |          | 
|  |         int intLen; | 
|  |         if (byteLen > 2147483647) { | 
|  |             intLen = 2147483647; | 
|  |         } else { | 
|  |             intLen = (int) byteLen; | 
|  |         } | 
|  |         loadedAudio = new byte[intLen]; | 
|  |         loadedAudioByteLength = 0; | 
|  |  | 
|  |          | 
|  |         while (true) { | 
|  |             int bytesRead = as.read(loadedAudio, loadedAudioByteLength, intLen - loadedAudioByteLength); | 
|  |             if (bytesRead <= 0) { | 
|  |                 as.close(); | 
|  |                 break; | 
|  |             } | 
|  |             loadedAudioByteLength += bytesRead; | 
|  |         } | 
|  |     } | 
|  |  | 
|  |     private void readStream(AudioInputStream as) throws IOException { | 
|  |  | 
|  |         DirectBAOS baos = new DirectBAOS(); | 
|  |         byte buffer[] = new byte[16384]; | 
|  |         int bytesRead = 0; | 
|  |         int totalBytesRead = 0; | 
|  |  | 
|  |          | 
|  |         while( true ) { | 
|  |             bytesRead = as.read(buffer, 0, buffer.length); | 
|  |             if (bytesRead <= 0) { | 
|  |                 as.close(); | 
|  |                 break; | 
|  |             } | 
|  |             totalBytesRead += bytesRead; | 
|  |             baos.write(buffer, 0, bytesRead); | 
|  |         } | 
|  |         loadedAudio = baos.getInternalBuffer(); | 
|  |         loadedAudioByteLength = totalBytesRead; | 
|  |     } | 
|  |  | 
|  |  | 
|  |     // METHODS FOR CREATING THE DEVICE | 
|  |  | 
|  |     private boolean createClip() { | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createClip()"); | 
|  |  | 
|  |         try { | 
|  |             DataLine.Info info = new DataLine.Info(Clip.class, loadedAudioFormat); | 
|  |             if (!(AudioSystem.isLineSupported(info)) ) { | 
|  |                 if (DEBUG || Printer.err)Printer.err("Clip not supported: "+loadedAudioFormat); | 
|  |                  | 
|  |                 return false; | 
|  |             } | 
|  |             Object line = AudioSystem.getLine(info); | 
|  |             if (!(line instanceof AutoClosingClip)) { | 
|  |                 if (DEBUG || Printer.err)Printer.err("Clip is not auto closing!"+clip); | 
|  |                  | 
|  |                 return false; | 
|  |             } | 
|  |             clip = (AutoClosingClip) line; | 
|  |             clip.setAutoClosing(true); | 
|  |             if (DEBUG || Printer.debug) clip.addLineListener(this); | 
|  |         } catch (Exception e) { | 
|  |             if (DEBUG || Printer.err)e.printStackTrace(); | 
|  |              | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         if (clip==null) { | 
|  |              | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("Loaded clip."); | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |     private boolean createSourceDataLine() { | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSourceDataLine()"); | 
|  |         try { | 
|  |             DataLine.Info info = new DataLine.Info(SourceDataLine.class, loadedAudioFormat); | 
|  |             if (!(AudioSystem.isLineSupported(info)) ) { | 
|  |                 if (DEBUG || Printer.err)Printer.err("Line not supported: "+loadedAudioFormat); | 
|  |                  | 
|  |                 return false; | 
|  |             } | 
|  |             SourceDataLine source = (SourceDataLine) AudioSystem.getLine(info); | 
|  |             datapusher = new DataPusher(source, loadedAudioFormat, loadedAudio, loadedAudioByteLength); | 
|  |         } catch (Exception e) { | 
|  |             if (DEBUG || Printer.err)e.printStackTrace(); | 
|  |              | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         if (datapusher==null) { | 
|  |              | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("Created SourceDataLine."); | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |     private boolean createSequencer(BufferedInputStream in) throws IOException { | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("JavaSoundAudioClip.createSequencer()"); | 
|  |  | 
|  |          | 
|  |         try { | 
|  |             sequencer = MidiSystem.getSequencer( ); | 
|  |         } catch(MidiUnavailableException me) { | 
|  |             if (DEBUG || Printer.err)me.printStackTrace(); | 
|  |             return false; | 
|  |         } | 
|  |         if (sequencer==null) { | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         try { | 
|  |             sequence = MidiSystem.getSequence(in); | 
|  |             if (sequence == null) { | 
|  |                 return false; | 
|  |             } | 
|  |         } catch (InvalidMidiDataException e) { | 
|  |             if (DEBUG || Printer.err)e.printStackTrace(); | 
|  |             return false; | 
|  |         } | 
|  |  | 
|  |         if (DEBUG || Printer.debug)Printer.debug("Created Sequencer."); | 
|  |         return true; | 
|  |     } | 
|  |  | 
|  |  | 
|  |      | 
|  |  | 
|  |  | 
|  |      */ | 
|  |     private static class DirectBAOS extends ByteArrayOutputStream { | 
|  |         DirectBAOS() { | 
|  |             super(); | 
|  |         } | 
|  |  | 
|  |         public byte[] getInternalBuffer() { | 
|  |             return buf; | 
|  |         } | 
|  |  | 
|  |     } // class DirectBAOS | 
|  |  | 
|  | } |