Files
nenya/src/java/com/threerings/media/sound/MusicManager.java
T
Dave Hoover 120e9c0db9 Actually substitute keywords (these files are chock full of stale $Id$ tags)
git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@119 ed5b42cb-e716-0410-a449-f6a68f950b19
2007-01-24 00:22:12 +00:00

354 lines
9.8 KiB
Java

//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.media.sound;
import java.util.Iterator;
import java.util.LinkedList;
import com.samskivert.util.Config;
import com.samskivert.util.RandomUtil;
import com.samskivert.util.RunQueue;
import com.threerings.media.Log;
/**
* Manages the playing of audio files.
*/
// TODO:
// - fade music out when stopped?
// - be able to pause music
public class MusicManager
{
/**
* Constructs a music manager.
*
* @param smgr The soundManager we work with.
* @param runQueue the client event run queue.
*
*/
public MusicManager (SoundManager smgr, RunQueue runQueue)
{
_smgr = smgr;
_runQueue = runQueue;
}
/**
* Shut the damn thing off.
*/
public void shutdown ()
{
_musicStack.clear();
stopMusicPlayer();
}
/**
* Returns a string summarizing our volume settings and disabled sound
* types.
*/
public String summarizeState ()
{
StringBuilder buf = new StringBuilder();
buf.append("musicVol=").append(_musicVol);
return buf.append("]").toString();
}
/**
* Sets the volume for music.
*
* @param vol a volume parameter between 0f and 1f, inclusive.
*/
public void setMusicVolume (float vol)
{
float oldvol = _musicVol;
_musicVol = Math.max(0f, Math.min(1f, vol));
if (_musicPlayer != null) {
_musicPlayer.setVolume(_musicVol);
}
if ((oldvol == 0f) && (_musicVol != 0f)) {
playTopMusic();
} else if ((oldvol != 0f) && (_musicVol == 0f)) {
stopMusicPlayer();
}
}
/**
* Get the music volume.
*/
public float getMusicVolume ()
{
return _musicVol;
}
/**
* Start playing the specified music repeatedly.
*/
public void pushMusic (String pkgPath, String key)
{
pushMusic(pkgPath, key, -1);
}
/**
* Start playing music for the specified number of loops. If no other
* music is pushed, the specified music will play for the number of loops
* specified, and will then be popped off the stack.
*/
public void pushMusic (String pkgPath, String key, int numloops)
{
MusicKey mkey = new MusicKey(pkgPath, key, numloops);
// stop any existing playing music
if (_musicPlayer != null) {
_musicPlayer.stop();
handleMusicStopped();
}
// add the new song
_musicStack.addFirst(mkey);
// and play it
playTopMusic();
}
/**
* Remove the specified music from the playlist. If it is currently
* playing, it will be stopped and the previous song will be started.
*/
public void removeMusic (String pkgPath, String key)
{
MusicKey mkey = new MusicKey(pkgPath, key, -1);
if (!_musicStack.isEmpty()) {
MusicKey current = (MusicKey) _musicStack.getFirst();
// if we're currently playing this song..
if (mkey.equals(current)) {
// stop it
if (_musicPlayer != null) {
_musicPlayer.stop();
}
// remove it from the stack
_musicStack.removeFirst();
// start playing the next..
playTopMusic();
return;
} else {
// we aren't currently playing this song. Simply remove.
for (Iterator iter=_musicStack.iterator(); iter.hasNext(); ) {
if (key.equals(iter.next())) {
iter.remove();
return;
}
}
}
}
Log.debug("Sequence stopped that wasn't in the stack anymore " +
"[key=" + mkey + "].");
}
/**
* Start the specified sequence.
*/
protected void playTopMusic ()
{
if (_musicStack.isEmpty()) {
return;
}
// if the volume is off, we don't actually want to play anything
// but we want to at least decrement any loopers by one
// and keep them on the top of the queue
if (_musicVol == 0f) {
handleMusicStopped();
return;
}
MusicKey info = (MusicKey) _musicStack.getFirst();
Config c = _smgr.getConfig(info);
String[] names = c.getValue(info.key, (String[])null);
if ((names == null) || (names.length == 0)) {
Log.warning("No such music [key=" + info + "].");
_musicStack.removeFirst();
playTopMusic();
return;
}
String music = names[RandomUtil.getInt(names.length)];
Class playerClass = getMusicPlayerClass(music);
// if we don't have a player for this song, play the next song
if (playerClass == null) {
_musicStack.removeFirst();
playTopMusic();
return;
}
// shutdown the old player if we're switching music types
if (! playerClass.isInstance(_musicPlayer)) {
if (_musicPlayer != null) {
_musicPlayer.shutdown();
}
// set up the new player
try {
_musicPlayer = (MusicPlayer) playerClass.newInstance();
_musicPlayer.init(_playerListener);
} catch (Exception e) {
Log.warning("Unable to instantiate music player [class=" +
playerClass + ", e=" + e + "].");
// scrap it, try again with the next song
_musicPlayer = null;
_musicStack.removeFirst();
playTopMusic();
return;
}
_musicPlayer.setVolume(_musicVol);
}
// play!
String bundle = c.getValue("bundle", (String)null);
try {
// TODO: buffer for the music player?
_musicPlayer.start(_smgr._rmgr.getResource(bundle, music));
} catch (Exception e) {
Log.warning("Error playing music, skipping [e=" + e +
", bundle=" + bundle + ", music=" + music + "].");
_musicStack.removeFirst();
playTopMusic();
return;
}
}
/**
* Get the appropriate music player for the specified music file.
*/
protected static Class getMusicPlayerClass (String path)
{
path = path.toLowerCase();
// if (path.endsWith(".mid") || path.endsWith(".rmf")) {
// return MidiPlayer.class;
// } else if (path.endsWith(".mod")) {
// return ModPlayer.class;
// } else if (path.endsWith(".mp3")) {
// return Mp3Player.class;
// } else if (path.endsWith(".ogg")) {
// return OggPlayer.class;
// } else {
return null;
// }
}
/**
* Stop whatever song is currently playing and deal with the
* MusicKey associated with it.
*/
protected void handleMusicStopped ()
{
if (_musicStack.isEmpty()) {
return;
}
// see what was playing
MusicKey current = (MusicKey) _musicStack.getFirst();
// see how many times the song was to loop and act accordingly
switch (current.loops) {
default:
current.loops--;
break;
case 1:
// sorry charlie
_musicStack.removeFirst();
break;
}
}
/**
* Stop the current music player.
*/
protected void stopMusicPlayer ()
{
if (_musicPlayer != null) {
_musicPlayer.stop();
_musicPlayer.shutdown();
_musicPlayer = null;
}
}
/**
* A class that tracks the information about our playing music files.
*/
protected static class MusicKey extends SoundManager.SoundKey
{
/** How many times to loop, or -1 for forever. */
public int loops;
public MusicKey (String set, String path, int loops)
{
super((byte) -1, set, path);
this.loops = loops;
}
}
/** The sound manager we work with. */
protected SoundManager _smgr;
/** The client event run queue. */
protected RunQueue _runQueue;
/** Volume level for music. */
protected float _musicVol = 1f;
/** The stack of songs that we're playing. */
protected LinkedList _musicStack = new LinkedList();
/** The current music player, if any. */
protected MusicPlayer _musicPlayer;
/** Event listener for receiving information about a song ending. */
protected MusicPlayer.MusicEventListener _playerListener =
new MusicPlayer.MusicEventListener() {
public void musicStopped () {
_runQueue.postRunnable(new Runnable() {
public void run() {
handleMusicStopped();
playTopMusic();
}
});
}
};
}