/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package renoria.sound;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.BooleanControl;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import net.idgames.archive.idx.IDXException;
/**
* An {@link AsyncSoundPlayer} is a sound player with a thread pool using an
* asynchronous read/write non-blocking method to play sounds concurrently
* using a few, limited number of threads.
* @author David
*/
public class AsyncSoundPlayer {
private Deque<SoundInfo> queuedSounds = new LinkedList();
private final Object poolSync = new Object();
private final Object queueSync = new Object();
private boolean shutDown = false;
private List<Player> playerPool;
private int poolsize;
private AudioFormat playBackFormat;
private AtomicInteger playingSounds = new AtomicInteger(0);
private int maxPlayingSoundsPerPool = 32;
private Mixer mixer;
private boolean soundAvailable = false;
private float masterVolume = 1.0f;
/**
* Creates a {@link AsyncSoundPlayer} with a pool size of one, and the default mixer.
*/
public AsyncSoundPlayer() {
this(1, null);
}
public AsyncSoundPlayer(int poolsize, Mixer useMixer) {
Mixer.Info[] infos = AudioSystem.getMixerInfo();
if (infos == null || infos.length < 1) {
throw new SoundUnavailableException();
}
if (poolsize <= 0) {
throw new IllegalArgumentException();
}
if (useMixer != null) {
this.mixer = useMixer;
} else {
this.mixer = SoundUtil.getDefaultMixer(SoundUtil.PLAYBACK_MIXER);
}
this.poolsize = poolsize;
soundAvailable = true;
//this.playBackFormat = playBackFormat;
}
public float getMasterVolume() {
return masterVolume;
}
public void setMasterVolume(float masterVolume) {
this.masterVolume = masterVolume;
}
public void start() {
if (!soundAvailable) {
throw new SoundUnavailableException();
}
if (playerPool == null) {
playerPool = new LinkedList();
for (int i = 0; i < poolsize; i++) {
Player p = new Player();
p.start();
playerPool.add(p);
}
try {
mixer.open();
} catch (LineUnavailableException ex) {
throw new IDXException(ex);
}
} else {
throw new IllegalStateException();
}
}
/**
* Stops the Internal Thread Pool that this {@link AsyncSoundPlayer} runs on.
* This method blocks until all threads in the thread pool finish.
*/
public void stop() {
stop(0);
}
/**
* Waits at most {@code maxwait} milliseconds for this {@link AsyncSoundPlayer} to die.
* @param maxwait Max waiting time in milliseconds
*/
public void stop(int maxwait) {
if (playerPool != null) {
try {
synchronized (poolSync) {
Iterator<Player> pItr = playerPool.iterator();
while (pItr.hasNext()) {
Player p = pItr.next();
p.join(maxwait);
}
}
} catch (InterruptedException ex) {}
playerPool = null;
mixer.close();
} else {
throw new IllegalStateException();
}
}
/**
* Sets the Pool size of this <tt>AsyncSoundPlayer</tt>.
*
* If this method is called while the sound player is active, it will throw a {@link IllegalArgumentException}.
* @param ps New pool size
* @throws IllegalStateException If the sound player is still active
*/
public void setPoolSize(int ps)
throws IllegalStateException {
if (playerPool != null) {
throw new IllegalStateException();
}
poolsize = ps;
}
/**
* Gets the pool size that this sound player uses.
* @return Pool size
*/
public int getPoolsize() {
return poolsize;
}
public AudioInputStream getAudioInputStream(InputStream is) {
try {
if (!is.markSupported()) {
is = new BufferedInputStream(is);
}
// open the source stream
AudioInputStream source = AudioSystem.getAudioInputStream(is);
// convert to playback format
return playBackFormat != null ? convertStream(source) : source;
} catch (UnsupportedAudioFileException ex) {
throw new IDXException(ex);
} catch (IOException ex) {
throw new IDXException(ex);
} catch (IllegalArgumentException ex) {
throw new IDXException(ex);
}
}
/**
* Stops all sounds playing from this sound player.
* @param instant Whether it should instantly stop playing, or wait until all sounds have finished playing.
*/
public void stopAllSounds(boolean instant) {
Iterator<Player> pItr = playerPool.iterator();
while (pItr.hasNext()) {
Player p = pItr.next();
p.stopAllSounds(instant);
}
playingSounds.set(0);
}
static int getMaxDefaultMixerLines(AudioFormat format) {
try {
Mixer mixer = AudioSystem.getMixer(null);
DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
return mixer.getMaxLines(info);
} catch (Exception e) {
renoria.Core.handleException(e);
return -1;
}
}
private AudioInputStream convertStream(AudioInputStream stream) {
if (playBackFormat == null) {
return stream;
}
try {
return AudioSystem.getAudioInputStream(playBackFormat, stream);
} catch (IllegalArgumentException e) {
renoria.Core.handleException(e);
return stream;
}
}
/**
* Plays a sound instantly.
* @param input Input
* @return sound info context
*/
public SoundInfo playSound(InputStream input) {
return playSound(input, true);
}
NOTE: Split into 3 posts due to character limit
Have fun, and report to me any problems you may encounter.
PS: Some exception handing and a few bits of other code are reliant on some of my other code. That code can be edited if you wish