bb.gui
Class SoundUtil.Player

java.lang.Object
  extended by bb.gui.SoundUtil.Player
All Implemented Interfaces:
Closeable, EventListener, LineListener
Enclosing class:
SoundUtil

private static class SoundUtil.Player
extends Object
implements Closeable, LineListener

Generates a Clip from a URL and can play it, but most importantly it robustly ensures that the clip is eventually closed and that this close event gets propagated.

There seems to be a common code idiom for working with Java Sound that is not robust against known bugs and defects. This approach has a LineListener alone listen for the Clip's STOP event and either closes the Clip at that point and/or notifies the calling thread (which is waiting on the Clip instance, if synchronous sound playback is desired). Examples of this code idiom include:

  1. http://www.jsresources.org/examples/ClipPlayer.java.html
  2. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4434125 (see the source code in the initial posting)
  3. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4631192 (see the source code in the initial posting)

Unfortunately, as the bug reports cited above illustrate, there are many problems with Java Sound:

  1. it is a very bad idea to use the Clip instance itself as the wait/notify object, since it is used internally
  2. STOP events may fail to get generated

This class avoids all these problems. First, it uses the Player instance itself as the wait/notify object. Second, it does not solely rely on the event mechanism to close the Clip. Instead, a task that executes in a dedicated background daemon thread is used to guarantee that close is called after the expected amount of playing time has elapsed, which guards against dropped events.

This class is multithread safe.


Nested Class Summary
private  class SoundUtil.Player.Closer
          Sleeps for the duration of the sound playback, and then calls StreamUtil.close( Player.this ).
 
Field Summary
private  Clip clip
           
private  boolean closeExecuted
          Condition predicate for this instance's condition queue (i.e. the wait/notifyAll calls below; see "Java Concurrency in Practice" by Goetz et al p. 296ff, especially p. 299).
private  int numberLoops
           
private  URL url
           
 
Constructor Summary
private SoundUtil.Player(URL url, int numberLoops)
           
 
Method Summary
 void close()
          Calls clip.close, and then calls this instance's notifyAll.
private  long getDuration()
           
private  void play()
          Initiates the playing of clip in a loop that will execute numberLoops times.
 void update(LineEvent event)
           
private  void waitTillClosed()
          Waits until this instance is notified.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

url

private final URL url

numberLoops

private final int numberLoops

clip

private final Clip clip

closeExecuted

private boolean closeExecuted
Condition predicate for this instance's condition queue (i.e. the wait/notifyAll calls below; see "Java Concurrency in Practice" by Goetz et al p. 296ff, especially p. 299).

Constructor Detail

SoundUtil.Player

private SoundUtil.Player(URL url,
                         int numberLoops)
                  throws UnsupportedAudioFileException,
                         IOException,
                         LineUnavailableException,
                         IllegalArgumentException,
                         IllegalStateException,
                         SecurityException
Throws:
UnsupportedAudioFileException
IOException
LineUnavailableException
IllegalArgumentException
IllegalStateException
SecurityException
Method Detail

play

private void play()
           throws IllegalStateException
Initiates the playing of clip in a loop that will execute numberLoops times. (Some other thread in the sound system actually does the playing, not the calling thread, so this method executes very quickly).

Schedules a background task which will call close after a delay of getDuration.

Throws:
IllegalStateException - if clip is currently running

getDuration

private long getDuration()

waitTillClosed

private void waitTillClosed()
                     throws InterruptedException
Waits until this instance is notified.

Throws:
InterruptedException

close

public void close()
Calls clip.close, and then calls this instance's notifyAll.

Specified by:
close in interface Closeable

update

public void update(LineEvent event)
Specified by:
update in interface LineListener