Storing Clip InformationA ClipInfo object is responsible for loading a clip and plays, pauses, resumes, stops, and loops that clip when requested by ClipsLoader. Additionally, an object implementing the SoundsWatcher interface (a watcher) can be notified when the clip loops or stops. Much of the manipulation carried out by ClipInfo, such as clip loading, is almost identical to that found in PlayClip.java in Chapter 7. Perhaps the largest difference is that PlayClip exits when it encounters a problem, and ClipInfo prints an error message and soldiers on. loadClip( ) is similar to PlayClip's loadClip( ), so certain parts have been commented away in the code below to simplify matters: // global private Clip clip = null; private void loadClip(String fnm) { try { // 1. access the audio file as a stream AudioInputStream stream = AudioSystem.getAudioInputStream( getClass( ).getResource(fnm) ); // 2. Get the audio format for the data in the stream AudioFormat format = stream.getFormat( ); // convert ULAW/ALAW formats to PCM format... // several lines, which update stream and format // 3. Gather information for line creation DataLine.Info info = new DataLine.Info(Clip.class, format); // make sure the sound system supports the data line if (!AudioSystem.isLineSupported(info)) { System.out.println("Unsupported Clip File: " + fnm); return; } // 4. create an empty clip using the line information clip = (Clip) AudioSystem.getLine(info); // 5. Start monitoring the clip's line events clip.addLineListener(this); // 6. Open the audio stream as a clip; now it's ready to play clip.open(stream); stream.close( ); // I'm done with the input stream checkDuration( ); } // end of try block // several catch blocks go here ... } // end of loadClip( ) checkDuration( ) checks the length of this clip and issues a warning if it's one second or less. This warning is due to the WAV file bug in Java Sound in J2SE 5.0, first mentioned in Chapter 7 when I coded PlayClip.java.
play( ) starts the loop playing: public void play(boolean toLoop) { if (clip != null) { isLooping = toLoop; // store playing mode clip.start( ); // start playing from where stopped } } The Clip class has a loop( ) method, which is not used by my play( ) method when toLoop is true. Instead, the looping mode is stored in the isLooping global and is utilized later in update( ). This allows the loader to execute a callback method in a watcher at the end of each iteration. Clip's start( ) method is asynchronous, so the play( ) method will not suspend. This makes it possible for a user to start multiple clips playing at the same time.
Stopping ClipsThe stop( ) method stops the clip and resets it to the beginning, ready for future playing: public void stop( ) { if (clip != null) { isLooping = false; clip.stop( ); clip.setFramePosition(0); } } Clip.setFramePosition( ) can set the playing position anywhere inside the clip. Pausing and Resuming ClipsThe pause( ) and resume( ) methods are similar to stop( ) and play( ): public void pause( ) // stop the clip at its current playing position { if (clip != null) clip.stop( ); } public void resume( ) { if (clip != null) clip.start( ); } The big difference between pause( ) and stop( ) is that pause( ) doesn't reset the clip's playing position. Consequently, resume( ) will start playing the clip from the point where the sound was suspended. Handing Line EventsClipInfo implements the LineListener interface, so it is notified when the clip generates line events. Audio lines, such as clips, fire events when they're opened, started, stopped, or closed. update( ) only deals with STOP line events: public void update(LineEvent lineEvent) { // when clip is stopped / reaches its end if (lineEvent.getType( ) == LineEvent.Type.STOP) { clip.stop( ); clip.setFramePosition(0); if (!isLooping) { // it isn't looping if (watcher != null) watcher.atSequenceEnd(name, SoundsWatcher.STOPPED); } else { // else play it again clip.start( ); if (watcher != null) watcher.atSequenceEnd(name, SoundsWatcher.REPLAYED); } } } A STOP event is triggered in two different situations: when the clip reaches its end and when the clip is stopped with Clip.stop( ). When the clip reaches its end, it may have been set to loop. This isn't implemented by using Clip's loop( ) method but by examining the value of the global isLooping Boolean. If isLooping is false, then the watcher (if one exists) is told the clip has stopped. If isLooping is TRue then the clip will start again, and the watcher is told that the clip is playing again. This explicit restarting of a looping clip, instead of calling loop( ), allows me to insert additional processing (e.g., watcher notification) between the clip's finishing and restarting. |