package greenfoot.sound;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
| Plays sound from a URL. The sound is loaded into memory the first time it is
| played.
|
| <p>Much of the complexity of this code comes from the need to work around bugs in
| the audio support of various Java runtimes.
|
| @author Poul Henriksen
|
public class SoundClip
implements Sound, LineListener{
private static ClipCache clipCache = new ClipCache();
private static ClipProcessThread processThread = new ClipProcessThread();
private static ClipCloserThread closerThread = new ClipCloserThread();
| URL of the sound data.
|
private final URL url;
| Data for the clip (used for caching)
|
private ClipData clipData;
| The clip that this SoundClip represents. Can be null (when state is
| CLOSED)
|
private Clip soundClip;
| The states a clip can be in.
|
private enum ClipState
{
STOPPED, PLAYING, PAUSED_LOOPING, PAUSED_PLAYING, CLOSED, LOOPING, STOPPING
};
| requested clip state (never STOPPING)
|
private ClipState clipState = ClipState.CLOSED;
| actual state playing vs looping vs stopping vs stopped
|
private ClipState currentState = ClipState.STOPPED;
| The master volume of the sound clip.
|
private int masterVolume = 100;
| Listener for state changes.
|
private SoundPlaybackListener playbackListener;
private boolean resumedLoop;
| Sound has been stopped; if played again, should be played from start rather than current position
|
private boolean resetToStart;
| Creates a new sound clip
|
public SoundClip(String name, URL url, SoundPlaybackListener listener)
{
this.url = url;
playbackListener = listener;
}
| Load the sound file supplied by the parameter into this sound engine.
|
private boolean open()
{
try {
load();
soundClip.addLineListener(this);
return true;
}
catch (SecurityException e) {
SoundExceptionHandler.handleSecurityException(e, url.toString());
}
catch (IllegalArgumentException e) {
SoundExceptionHandler.handleIllegalArgumentException(e, url.toString());
}
catch (FileNotFoundException e) {
SoundExceptionHandler.handleFileNotFoundException(e, url.toString());
}
catch (IOException e) {
SoundExceptionHandler.handleIOException(e, url.toString());
}
catch (UnsupportedAudioFileException e) {
SoundExceptionHandler.handleUnsupportedAudioFileException(e,
url.toString());
}
catch (LineUnavailableException e) {
SoundExceptionHandler.handleLineUnavailableException(e);
}
return false;
}
private void load() throws UnsupportedAudioFileException, IOException,
LineUnavailableException
{
clipData = clipCache.getCachedClip(url);
InputStream is = new ByteArrayInputStream(clipData.getBuffer());
AudioFormat format = clipData.getFormat();
AudioInputStream stream = new AudioInputStream(is, format, clipData.getLength());
DataLine.Info info = new DataLine.Info(Clip.class, format);
soundClip = (Clip) AudioSystem.getLine(info);
soundClip.open(stream);
setVolume(masterVolume);
}
| Preloads the clip, by opening it.
|
public synchronized void preLoad()
{
try
{
clipData = clipCache.getCachedClip(url);
clipCache.releaseClipData(clipData);
}
catch (IOException e) {
}
catch (UnsupportedAudioFileException e) {
}
}
|
| @see greenfoot.sound.Sound#play()
|
@Override
public synchronized void play()
{
if (clipState == ClipState.PLAYING) {
return;
}
resumedLoop = false;
if (soundClip == null) {
processThread.addToQueue(this);
currentState = ClipState.STOPPED;
}
else if (currentState == ClipState.STOPPED) {
if (resetToStart) {
resetToStart = false;
soundClip.setFramePosition(0);
}
soundClip.loop(0);
soundClip.start();
}
else if (currentState == ClipState.STOPPING) {
setState(ClipState.PLAYING);
return;
}
else if (currentState == ClipState.LOOPING) {
soundClip.loop(0);
}
setState(ClipState.PLAYING);
if (soundClip != null) {
currentState = ClipState.PLAYING;
}
}
| Play this sound from the beginning of the sound and loop around when the
| end have been reached.
|
@Override
public synchronized void loop()
{
if (clipState == ClipState.LOOPING) {
return;
}
if (soundClip == null) {
processThread.addToQueue(this);
currentState = ClipState.STOPPED;
}
else if (currentState == ClipState.STOPPED) {
if (resetToStart) {
resetToStart = false;
soundClip.setFramePosition(0);
}
soundClip.setLoopPoints(0, -1);
soundClip.loop(Clip.LOOP_CONTINUOUSLY);
}
else if (currentState == ClipState.STOPPING) {
setState(ClipState.LOOPING);
return;
}
else if (currentState == ClipState.PLAYING) {
soundClip.setLoopPoints(0, -1);
soundClip.loop(Clip.LOOP_CONTINUOUSLY);
resumedLoop = true;
}
setState(ClipState.LOOPING);
if (soundClip != null) {
currentState = ClipState.LOOPING;
}
}
public void processState()
{
ClipState toState;
Clip soundClip;
synchronized (this) {
toState = clipState;
soundClip = this.soundClip;
if (clipState == ClipState.PLAYING) {
if (currentState != ClipState.PLAYING) {
if (soundClip == null && !open()) {
return;
}
soundClip = this.soundClip;
soundClip.start();
currentState = ClipState.PLAYING;
}
return;
}
else if (clipState == ClipState.LOOPING) {
if (currentState != ClipState.LOOPING) {
if (soundClip == null && !open()) {
return;
}
soundClip = this.soundClip;
soundClip.setFramePosition(0);
soundClip.setLoopPoints(0, -1);
soundClip.loop(Clip.LOOP_CONTINUOUSLY);
resumedLoop = false;
currentState = ClipState.LOOPING;
}
return;
}
else if (clipState == ClipState.CLOSED) {
return;
}
else if (isPaused() || clipState == ClipState.STOPPED) {
if (currentState == ClipState.PLAYING || currentState == ClipState.LOOPING) {
currentState = ClipState.STOPPING;
resumedLoop = false;
}
else {
return;
}
}
}
if (toState == ClipState.STOPPED
|| toState == ClipState.PAUSED_LOOPING
|
|| toState == ClipState.PAUSED_PLAYING) {
soundClip.stop();
synchronized (this) {
if (resetToStart) {
resetToStart = false;
soundClip.setFramePosition(0);
}
currentState = ClipState.STOPPED;
if (clipState == ClipState.PLAYING) {
soundClip.loop(0);
soundClip.start();
currentState = ClipState.PLAYING;
}
else if (clipState == ClipState.LOOPING) {
soundClip.setLoopPoints(0, -1);
soundClip.loop(Clip.LOOP_CONTINUOUSLY);
currentState = ClipState.LOOPING;
}
}
}
}
| Set the volume level for this sound.
| @param level the volume level.
|
@Override
public synchronized void setVolume(int level)
{
this.masterVolume = level;
if (soundClip != null) {
if (soundClip.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
FloatControl volume = (FloatControl) soundClip.getControl(FloatControl.Type.MASTER_GAIN);
volume.setValue(SoundUtils.convertMinMax(level, volume.getMinimum(), volume.getMaximum()));
}
}
}
| Get the volume level.
| @return the volume level.
|
@Override
public synchronized int getVolume()
{
return masterVolume;
}
| Stop this sound.
|
@Override
public synchronized void stop()
{
if (isStopped()) {
return;
}
setState(ClipState.STOPPED);
if (soundClip != null) {
if (currentState == ClipState.STOPPED) {
closerThread.addClip(soundClip);
soundClip = null;
}
else {
resetToStart = true;
processThread.addToQueue(this);
}
}
}
| Closes this sound. It will release all the resources for this sound
| immediately.
|
@Override
public synchronized void close()
{
if (clipState != ClipState.CLOSED) {
if (soundClip != null) {
setVolume(0);
clipCache.releaseClipData(clipData);
closerThread.addClip(soundClip);
soundClip = null;
}
setState(ClipState.CLOSED);
}
}
| Pause the clip. Paused sounds can be resumed.
|
@Override
public synchronized void pause()
{
resumedLoop = false;
if (soundClip == null) {
return;
}
if (clipState == ClipState.PLAYING) {
setState(ClipState.PAUSED_PLAYING);
processThread.addToQueue(this);
}
if (clipState == ClipState.LOOPING) {
setState(ClipState.PAUSED_LOOPING);
processThread.addToQueue(this);
}
}
private void setState(ClipState newState)
{
if (clipState != newState) {
clipState = newState;
switch (clipState) {
case PLAYING:
playbackListener.playbackStarted(this);
break;
case STOPPED:
playbackListener.playbackStopped(this);
break;
case PAUSED_LOOPING:
playbackListener.playbackPaused(this);
break;
case PAUSED_PLAYING:
playbackListener.playbackPaused(this);
break;
case LOOPING:
playbackListener.playbackStarted(this);
break;
case CLOSED:
playbackListener.soundClosed(this);
}
}
}
| True if the sound is currently playing.
|
@Override
public synchronized boolean isPlaying()
{
return clipState == ClipState.PLAYING || clipState == ClipState.LOOPING;
}
| True if the sound is currently paused.
|
@Override
public synchronized boolean isPaused()
{
return clipState == ClipState.PAUSED_PLAYING || clipState == ClipState.PAUSED_LOOPING;
}
| True if the sound is currently stopped.
|
@Override
public synchronized boolean isStopped()
{
return clipState == ClipState.STOPPED || clipState == ClipState.CLOSED;
}
@Override
public void update(LineEvent event)
{
if (event.getType() == LineEvent.Type.STOP) {
synchronized (this) {
if (currentState == ClipState.STOPPING) {
}
else {
currentState = ClipState.STOPPED;
if (resumedLoop && clipState == ClipState.LOOPING) {
closerThread.addClip(soundClip);
soundClip = null;
processThread.addToQueue(this);
}
else if (! isPaused()) {
setState(ClipState.STOPPED);
if (clipState == ClipState.STOPPED && soundClip != null) {
closerThread.addClip(soundClip);
soundClip = null;
}
}
}
}
}
}
@Override
public String toString()
{
return url + " " + super.toString();
}
}
top,
use,
map,
class SoundClip
. SoundClip
. open
. load
. preLoad
. play
. loop
. processState
. setVolume
. getVolume
. stop
. close
. pause
. setState
. isPlaying
. isPaused
. isStopped
. update
. toString
640 neLoCode
+ 33 LoComm