The same sound file can be played multiple times simultaneously.
Sounds can be "locked" so that they don't expire from the LRU cache until they are unlocked. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1947 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// $Id: SoundManager.java,v 1.10 2002/11/13 07:12:40 ray Exp $
|
// $Id: SoundManager.java,v 1.11 2002/11/15 01:49:47 ray Exp $
|
||||||
|
|
||||||
package com.threerings.media;
|
package com.threerings.media;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -34,7 +35,7 @@ import javax.swing.Timer;
|
|||||||
|
|
||||||
import org.apache.commons.io.StreamUtils;
|
import org.apache.commons.io.StreamUtils;
|
||||||
|
|
||||||
import com.samskivert.util.LRUHashMap;
|
import com.samskivert.util.LockableLRUHashMap;
|
||||||
import com.samskivert.util.Queue;
|
import com.samskivert.util.Queue;
|
||||||
|
|
||||||
import com.threerings.resource.ResourceManager;
|
import com.threerings.resource.ResourceManager;
|
||||||
@@ -91,43 +92,56 @@ public class SoundManager
|
|||||||
// save things off
|
// save things off
|
||||||
_rmgr = rmgr;
|
_rmgr = rmgr;
|
||||||
|
|
||||||
// // obtain the mixer
|
|
||||||
// _mixer = AudioSystem.getMixer(null);
|
|
||||||
// if (_mixer == null) {
|
|
||||||
// Log.warning("Can't get default mixer, aborting audio " +
|
|
||||||
// "initialization.");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// create a thread to plays sounds and load sound
|
// create a thread to plays sounds and load sound
|
||||||
// data from the resource manager
|
// data from the resource manager
|
||||||
_player = new Thread() {
|
_player = new Thread() {
|
||||||
public void run () {
|
public void run () {
|
||||||
|
Object command = null;
|
||||||
|
SoundType type = null;
|
||||||
|
String path = null;
|
||||||
|
|
||||||
while (amRunning()) {
|
while (amRunning()) {
|
||||||
try {
|
try {
|
||||||
// Log.info("Waiting for clip");
|
|
||||||
|
|
||||||
// wait until there is an item to get from the queue
|
// Get the next command and arguments
|
||||||
Object o = _queue.get();
|
synchronized (_queue) {
|
||||||
|
command = _queue.get();
|
||||||
|
|
||||||
// play sounds
|
if (PLAY == command) {
|
||||||
if (o instanceof SoundKey) {
|
type = (SoundType) _queue.get();
|
||||||
playSound((SoundKey) o);
|
path = (String) _queue.get();
|
||||||
|
|
||||||
// see if we got the flush rsrcs signal
|
} else if (LOCK == command) {
|
||||||
} else if (o == FLUSH) {
|
path = (String) _queue.get();
|
||||||
|
|
||||||
|
} else if (UNLOCK == command) {
|
||||||
|
path = (String) _queue.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute the command outside of the queue synch
|
||||||
|
if (PLAY == command) {
|
||||||
|
playSound(type, path);
|
||||||
|
|
||||||
|
} else if (LOCK == command) {
|
||||||
|
_dataCache.lock(path);
|
||||||
|
getAudioData(path); // preload
|
||||||
|
|
||||||
|
} else if (UNLOCK == command) {
|
||||||
|
_dataCache.unlock(path);
|
||||||
|
|
||||||
|
} else if (FLUSH == command) {
|
||||||
flushResources(false);
|
flushResources(false);
|
||||||
// and re-start the resource freer timer to do
|
// and re-start the resource freer timer
|
||||||
// it again in 3 seconds
|
// to do it again in 3 seconds
|
||||||
_resourceFreer.restart();
|
_resourceFreer.restart();
|
||||||
|
|
||||||
// see if we got the die signal
|
} else if (DIE == command) {
|
||||||
} else if (o == DIE) {
|
|
||||||
_resourceFreer.stop();
|
_resourceFreer.stop();
|
||||||
flushResources(true);
|
flushResources(true);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.warning("Captured exception playing sound.");
|
Log.warning("Captured exception in SoundManager loop.");
|
||||||
Log.logStackTrace(e);
|
Log.logStackTrace(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,8 +201,31 @@ public class SoundManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues up the sound file with the given pathname to be played when
|
* Optionally lock the sound data prior to playing, to guarantee
|
||||||
* the sound manager deems the time appropriate.
|
* that it will be quickly available for playing.
|
||||||
|
*/
|
||||||
|
public void lock (String path)
|
||||||
|
{
|
||||||
|
synchronized (_queue) {
|
||||||
|
_queue.append(LOCK);
|
||||||
|
_queue.append(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlock the specified sound so that its resources can be freed.
|
||||||
|
*/
|
||||||
|
public void unlock (String path)
|
||||||
|
{
|
||||||
|
synchronized (_queue) {
|
||||||
|
_queue.append(UNLOCK);
|
||||||
|
_queue.append(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the specified sound of as the specified type of sound.
|
||||||
|
* Note that a sound need not be locked prior to playing.
|
||||||
*/
|
*/
|
||||||
public void play (SoundType type, String path)
|
public void play (SoundType type, String path)
|
||||||
{
|
{
|
||||||
@@ -198,11 +235,12 @@ public class SoundManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_player != null && (0f != getVolume(type))) {
|
if (_player != null && (0f != getVolume(type))) {
|
||||||
// Log.info("Queueing sound [path=" + path + "].");
|
|
||||||
synchronized (_queue) {
|
synchronized (_queue) {
|
||||||
if (_queue.size() < MAX_SOUNDS_ENQUEUED) {
|
if (_queue.size() < MAX_QUEUE_SIZE) {
|
||||||
SoundKey key = new SoundKey(path, type);
|
_queue.append(PLAY);
|
||||||
_queue.append(key);
|
_queue.append(type);
|
||||||
|
_queue.append(path);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log.warning("SoundManager not playing sound because " +
|
Log.warning("SoundManager not playing sound because " +
|
||||||
"too many sounds in queue [path=" + path +
|
"too many sounds in queue [path=" + path +
|
||||||
@@ -213,25 +251,22 @@ public class SoundManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays the sound file with the given pathname.
|
* On the SoundManager thread,
|
||||||
|
* plays the sound file with the given pathname.
|
||||||
*/
|
*/
|
||||||
protected void playSound (SoundKey key)
|
protected void playSound (SoundType type, String path)
|
||||||
{
|
{
|
||||||
// Log.info("Playing sound [path=" + path + "].");
|
// see if we can restart a previously used sound that's still
|
||||||
|
// hanging out.
|
||||||
// see if we're playing the same sound that was just played.
|
if (restartSound(type, path)) {
|
||||||
SoundRecord rec = (SoundRecord) _active.get(key);
|
|
||||||
if (rec != null) {
|
|
||||||
rec.restart();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the sound data from our LRU cache
|
// get the sound data from our LRU cache
|
||||||
byte[] data = getAudioData(key.path);
|
byte[] data = getAudioData(path);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return; // borked!
|
return; // borked!
|
||||||
}
|
}
|
||||||
// Log.info("got data [length=" + data.length + "].");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AudioInputStream stream = AudioSystem.getAudioInputStream(
|
AudioInputStream stream = AudioSystem.getAudioInputStream(
|
||||||
@@ -242,25 +277,43 @@ public class SoundManager
|
|||||||
Clip clip = (Clip) AudioSystem.getLine(info);
|
Clip clip = (Clip) AudioSystem.getLine(info);
|
||||||
clip.open(stream);
|
clip.open(stream);
|
||||||
|
|
||||||
rec = new SoundRecord(key, clip);
|
SoundRecord rec = new SoundRecord(path, clip);
|
||||||
rec.start();
|
rec.start(type);
|
||||||
_active.put(key, rec);
|
_active.add(rec);
|
||||||
// Log.info("clip played [path=" + path + "]");
|
|
||||||
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
Log.warning("Error loading sound file [path=" + key.path +
|
Log.warning("Error loading sound file [path=" + path +
|
||||||
", e=" + ioe + "].");
|
", e=" + ioe + "].");
|
||||||
|
|
||||||
} catch (UnsupportedAudioFileException uafe) {
|
} catch (UnsupportedAudioFileException uafe) {
|
||||||
Log.warning("Unsupported sound format [path=" + key.path + ", e=" +
|
Log.warning("Unsupported sound format [path=" + path + ", e=" +
|
||||||
uafe + "].");
|
uafe + "].");
|
||||||
|
|
||||||
} catch (LineUnavailableException lue) {
|
} catch (LineUnavailableException lue) {
|
||||||
Log.warning("Line not available to play sound [path=" + key.path +
|
Log.warning("Line not available to play sound [path=" + path +
|
||||||
", e=" + lue + "].");
|
", e=" + lue + "].");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reuse a clip that's already been loaded.
|
||||||
|
*/
|
||||||
|
protected boolean restartSound (SoundType type, String path)
|
||||||
|
{
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// we just go through all the sounds. There'll be 32 max, so fuckit.
|
||||||
|
for (int ii=0, nn=_active.size(); ii < nn; ii++) {
|
||||||
|
SoundRecord rec = (SoundRecord) _active.get(ii);
|
||||||
|
if (rec.path.equals(path) && rec.isStoppedSince(now)) {
|
||||||
|
rec.restart(type);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called occaisionally to flush sound resources.
|
* Called occaisionally to flush sound resources.
|
||||||
*
|
*
|
||||||
@@ -272,7 +325,7 @@ public class SoundManager
|
|||||||
{
|
{
|
||||||
long then = System.currentTimeMillis() - EXPIRE_TIME;
|
long then = System.currentTimeMillis() - EXPIRE_TIME;
|
||||||
|
|
||||||
for (Iterator iter=_active.values().iterator(); iter.hasNext(); ) {
|
for (Iterator iter=_active.iterator(); iter.hasNext(); ) {
|
||||||
SoundRecord rec = (SoundRecord) iter.next();
|
SoundRecord rec = (SoundRecord) iter.next();
|
||||||
if (force || rec.isStoppedSince(then)) {
|
if (force || rec.isStoppedSince(then)) {
|
||||||
iter.remove();
|
iter.remove();
|
||||||
@@ -304,36 +357,6 @@ public class SoundManager
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A key for looking up a SoundRecord.
|
|
||||||
*/
|
|
||||||
protected static class SoundKey
|
|
||||||
{
|
|
||||||
public String path;
|
|
||||||
public SoundType type;
|
|
||||||
|
|
||||||
public SoundKey (String path, SoundType type)
|
|
||||||
{
|
|
||||||
this.path = path;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int hashCode ()
|
|
||||||
{
|
|
||||||
return path.hashCode() ^ type.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean equals (Object other)
|
|
||||||
{
|
|
||||||
if (other instanceof SoundKey) {
|
|
||||||
SoundKey that = (SoundKey) other;
|
|
||||||
return this.path.equals(that.path) &&
|
|
||||||
this.type.equals(that.type);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record to help us manage the use of sound resources.
|
* A record to help us manage the use of sound resources.
|
||||||
* We don't free the resources associated with a clip immediately, because
|
* We don't free the resources associated with a clip immediately, because
|
||||||
@@ -342,13 +365,14 @@ public class SoundManager
|
|||||||
protected class SoundRecord
|
protected class SoundRecord
|
||||||
implements LineListener
|
implements LineListener
|
||||||
{
|
{
|
||||||
|
public String path;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a SoundRecord.
|
* Construct a SoundRecord.
|
||||||
*/
|
*/
|
||||||
public SoundRecord (SoundKey key, Clip clip)
|
public SoundRecord (String path, Clip clip)
|
||||||
{
|
{
|
||||||
_key = key;
|
this.path = path;
|
||||||
_clip = clip;
|
_clip = clip;
|
||||||
|
|
||||||
// The mess:
|
// The mess:
|
||||||
@@ -386,9 +410,9 @@ public class SoundManager
|
|||||||
/**
|
/**
|
||||||
* Start playing the sound.
|
* Start playing the sound.
|
||||||
*/
|
*/
|
||||||
public void start ()
|
public void start (SoundType type)
|
||||||
{
|
{
|
||||||
adjustVolume(_clip, _key.type);
|
adjustVolume(_clip, type);
|
||||||
_clip.start();
|
_clip.start();
|
||||||
didStart();
|
didStart();
|
||||||
}
|
}
|
||||||
@@ -396,7 +420,7 @@ public class SoundManager
|
|||||||
/**
|
/**
|
||||||
* Restart the sound from the beginning.
|
* Restart the sound from the beginning.
|
||||||
*/
|
*/
|
||||||
public void restart ()
|
public void restart (SoundType type)
|
||||||
{
|
{
|
||||||
// this seems to be unneeded
|
// this seems to be unneeded
|
||||||
// if (_clip.isRunning()) {
|
// if (_clip.isRunning()) {
|
||||||
@@ -404,7 +428,7 @@ public class SoundManager
|
|||||||
// _clip.stop();
|
// _clip.stop();
|
||||||
// }
|
// }
|
||||||
_clip.setFramePosition(0);
|
_clip.setFramePosition(0);
|
||||||
start();
|
start(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -493,9 +517,6 @@ public class SoundManager
|
|||||||
|
|
||||||
/** The clip we're wrapping. */
|
/** The clip we're wrapping. */
|
||||||
protected Clip _clip;
|
protected Clip _clip;
|
||||||
|
|
||||||
/** Other information associated with this clip. */
|
|
||||||
protected SoundKey _key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -576,9 +597,6 @@ public class SoundManager
|
|||||||
/** The resource manager from which we obtain audio files. */
|
/** The resource manager from which we obtain audio files. */
|
||||||
protected ResourceManager _rmgr;
|
protected ResourceManager _rmgr;
|
||||||
|
|
||||||
// /** The default mixer. */
|
|
||||||
// protected Mixer _mixer;
|
|
||||||
//
|
|
||||||
/** The thread that plays sounds. */
|
/** The thread that plays sounds. */
|
||||||
protected Thread _player;
|
protected Thread _player;
|
||||||
|
|
||||||
@@ -586,19 +604,26 @@ public class SoundManager
|
|||||||
protected Queue _queue = new Queue();
|
protected Queue _queue = new Queue();
|
||||||
|
|
||||||
/** The cache of recent audio clips . */
|
/** The cache of recent audio clips . */
|
||||||
protected LRUHashMap _dataCache = new LRUHashMap(10);
|
protected LockableLRUHashMap _dataCache = new LockableLRUHashMap(10);
|
||||||
|
|
||||||
/** The clips that are currently active. */
|
/** The clips that are currently active. */
|
||||||
protected HashMap _active = new HashMap();
|
protected ArrayList _active = new ArrayList();
|
||||||
|
|
||||||
/** A set of soundTypes for which sound is enabled. */
|
/** A set of soundTypes for which sound is enabled. */
|
||||||
protected HashMap _volumes = new HashMap();
|
protected HashMap _volumes = new HashMap();
|
||||||
|
|
||||||
/** Signals to the queue to do different things. */
|
/** Signals to the queue to do different things. */
|
||||||
protected Object FLUSH = new Object(), DIE = new Object();
|
protected Object PLAY = new Object();
|
||||||
|
protected Object LOCK = new Object();
|
||||||
|
protected Object UNLOCK = new Object();
|
||||||
|
protected Object FLUSH = new Object();
|
||||||
|
protected Object DIE = new Object();
|
||||||
|
|
||||||
/** The maximum number of sounds we'll allow to be enqueued. */
|
/** Default volume float object if no others found for a sound type. */
|
||||||
protected static final int MAX_SOUNDS_ENQUEUED = 25;
|
protected static final Float DEFAULT_VOLUME = new Float(1f);
|
||||||
|
|
||||||
|
/** The queue size at which we start to ignore requests to play sounds. */
|
||||||
|
protected static final int MAX_QUEUE_SIZE = 100;
|
||||||
|
|
||||||
/** The buffer size in bytes used when reading audio file data. */
|
/** The buffer size in bytes used when reading audio file data. */
|
||||||
protected static final int BUFFER_SIZE = 2048;
|
protected static final int BUFFER_SIZE = 2048;
|
||||||
|
|||||||
Reference in New Issue
Block a user