package greenfoot.sound;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.sound.sampled.DataLine.Info;
import bluej.utility.Debug;
| Plays sound from a URL. To avoid loading the entire sound clip into memory,
| the sound is streamed. The sound can either be a standard sound supported by
| the core Java libraries or an MP3.
|
| @see Mp3AudioInputStream
| @see JavaAudioInputStream
|
| @author Poul Henriksen
|
public class SoundStream
implements Sound, Runnable{
private static void printDebug(String s)
{
}
| How long to wait until closing the line and stopping the playback thread
| after playback has finished. In ms.
|
private static final int CLOSE_TIMEOUT = 1000;
| Signals that the sound should loop.
|
private boolean loop = false;
| Signals that the playback should stop.
|
private boolean stop = true;
| Signals that the playback should pause.
|
private boolean pause = false;
| Signals that playback should start over from the beginning.
|
private boolean restart = false;
| Flag that indicates whether the sound is currently stopped (not playing
| or paused). Almost the same as the stop signal, except that this flag
| will be set to false when the end of the input has been reached.
|
private boolean stopped = true;
| Stream where data is read from. This stream should only be accessed from
| the playThread.
|
private final GreenfootAudioInputStream inputStream;
| Listener for state changes.
|
private final SoundPlaybackListener playbackListener;
| The line that we play the sound through
|
private volatile AudioLine line;
private AudioFormat format;
private Info info;
| Thread that handles the actual playback of the sound.
|
private Thread playThread;
public SoundStream(GreenfootAudioInputStream inputStream, SoundPlaybackListener playbackListener)
{
this.playbackListener = playbackListener;
this.inputStream = inputStream;
try {
format = inputStream.getFormat();
info = new DataLine.Info(SourceDataLine.class, format);
line = initialiseLine(info, format);
}
catch (IllegalArgumentException e) {
SoundExceptionHandler.handleIllegalArgumentException(e, inputStream.getSource());
}
catch (LineUnavailableException e) {
SoundExceptionHandler.handleLineUnavailableException(e);
}
}
@Override
public synchronized void play()
{
if (isPlaying()) {
loop = false;
}
else {
startPlayback();
}
}
@Override
public synchronized void loop()
{
loop = true;
if (!isPlaying()) {
startPlayback();
}
}
| Starts playback by creating the thread if necessary, clearing the
| stop, stopped, and pause flags and notifying listeners.
|
private void startPlayback()
{
if (!pause) {
restart = true;
if (playThread == null) {
printDebug("Starting new playthread");
playThread = new Thread(this, "SoundStream:" + inputStream.getSource());
playThread.start();
}
if (line != null) {
line.reset();
}
}
stopped = false;
pause = false;
stop = false;
if (line != null) {
line.start();
}
notifyAll();
playbackListener.playbackStarted(this);
}
@Override
public synchronized void close()
{
if (line != null) {
reset();
line.close();
notifyAll();
playbackListener.playbackStopped(this);
}
}
@Override
public synchronized void stop()
{
if (!stop) {
stop = true;
stopped = true;
pause = false;
line.reset();
notifyAll();
playbackListener.playbackStopped(this);
}
}
@Override
public synchronized void pause()
{
if (!stopped && !pause) {
line.stop();
pause = true;
notifyAll();
playbackListener.playbackPaused(this);
}
}
@Override
public synchronized boolean isPlaying()
{
return !stopped && !pause;
}
@Override
public synchronized boolean isStopped()
{
return stopped && !pause;
}
@Override
public synchronized boolean isPaused()
{
return pause;
}
@Override
public String toString()
{
return inputStream.getSource() + " " + super.toString();
}
@Override
public void run()
{
boolean stayAlive = true;
try {
while (stayAlive){
inputStream.restart();
synchronized (this) {
if (line == null || !format.matches(inputStream.getFormat())) {
format = inputStream.getFormat();
info = new DataLine.Info(SourceDataLine.class, format);
line = initialiseLine(info, format);
}
line.open();
restart = false;
}
int frameSize = format.getFrameSize();
int bufferSize = SoundUtils.getBufferSizeToHold(format, 0.5);
if (bufferSize == -1) {
bufferSize = 64 * 1024;
}
byte[] buffer = new byte[bufferSize];
printDebug("Stream available (in bytes): " + inputStream.available() + " in frames: "
+ inputStream.available() / frameSize);
int bytesRead = inputStream.read(buffer, 0, bufferSize);
int bytesInBuffer = bytesRead;
printDebug(" read: " + bytesRead);
while (bytesInBuffer > 0){
int bytesToWrite = (bytesInBuffer / frameSize) * frameSize;
synchronized (this) {
if (stop) {
break;
}
if (pause) {
doPause();
}
if (restart) {
printDebug("restart in thread");
line.reset();
inputStream.restart();
restart = false;
bytesInBuffer = 0;
bytesRead = 0;
bytesToWrite = 0;
printDebug("inputStream available after restart in thread: " + inputStream.available());
}
}
int written = line.write(buffer, 0, bytesToWrite);
printDebug(" wrote: " + written);
int remaining = bytesInBuffer - written;
if (remaining > 0) {
printDebug("remaining: " + remaining + " written: " + written + " bytesInBuffer: "
+ bytesInBuffer + " bytesToWrite: " + bytesToWrite);
System.arraycopy(buffer, written, buffer, 0, remaining);
}
bytesInBuffer = remaining;
printDebug("remaining: " + remaining + " written: " + written + " bytesInBuffer: "
+ bytesInBuffer + " bytesToWrite: " + bytesToWrite);
bytesRead = inputStream.read(buffer, bytesInBuffer, buffer.length - bytesInBuffer);
if (bytesRead != -1) {
bytesInBuffer += bytesRead;
}
printDebug(" read: " + bytesRead);
}
line.drain();
synchronized (this) {
if (!loop || stop) {
line.reset();
}
if ((!restart && !loop) || stop) {
stopped = true;
playbackListener.playbackStopped(this);
try {
if (line.isOpen()) {
printDebug("WAIT");
wait(CLOSE_TIMEOUT);
}
}
catch (InterruptedException e) {
}
if ((!restart && !loop) || stop) {
line.close();
stayAlive = false;
reset();
printDebug("KILL THREAD");
}
}
printDebug(" 2 restart = " + restart + " stop = " + stop);
if (restart) {
restart = false;
}
}
}
}
catch (IllegalArgumentException e) {
SoundExceptionHandler.handleIllegalArgumentException(e, inputStream.getSource());
}
catch (UnsupportedAudioFileException e) {
SoundExceptionHandler.handleUnsupportedAudioFileException(e, inputStream.getSource());
}
catch (LineUnavailableException e) {
SoundExceptionHandler.handleLineUnavailableException(e);
}
catch (IOException e) {
SoundExceptionHandler.handleIOException(e, inputStream.getSource());
}
finally {
if (stayAlive == true) {
reset();
}
if (line != null) {
line.close();
}
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
playbackListener.soundClosed(this);
}
}
| Pauses as long as the pause signal is true.
|
private synchronized void doPause()
{
if (pause) {
while (pause){
try {
printDebug("In pause loop");
line.stop();
printDebug("In pause loop 2");
wait();
}
catch (InterruptedException e) {
Debug.reportError(
"Interrupted while pausing sound: " + inputStream.getSource(), e);
}
}
line.start();
}
}
| Initialise the line by creating it and setting up listeners.
|
| @param info
| @throws LineUnavailableException
| if a matching line is not available due to resource
| restrictions
| @throws SecurityException
| if a matching line is not available due to security
| restrictions
| @throws IllegalArgumentException
| if the system does not support at least one line matching the
| specified
|
private AudioLine initialiseLine(DataLine.Info info, AudioFormat format)
throws LineUnavailableException, IllegalArgumentException
{
SourceDataLine l = (SourceDataLine) AudioSystem.getLine(info);
printDebug("buffer size: " + l.getBufferSize());
return new AudioLine(l, format);
}
| Stops the thread and reset all flags and signals to initial values.
|
private synchronized void reset()
{
stopped = true;
pause = false;
loop = false;
stop = true;
playThread = null;
}
public long getLongFramePosition()
{
return line.getLongFramePosition();
}
@Override
public void setVolume(int level)
{
line.setVolume(SoundUtils.logToLin(level));
}
@Override
public int getVolume()
{
return line.getVolume();
}
}
top,
use,
map,
class SoundStream
. printDebug
. SoundStream
. play
. loop
. startPlayback
. close
. stop
. pause
. isPlaying
. isStopped
. isPaused
. toString
. run
. doPause
. initialiseLine
. reset
. getLongFramePosition
. setVolume
. getVolume
527 neLoCode
+ 35 LoComm