Android: Refactor AndroidAudioRenderer into an interface with 2 implementations (current MediaPlayer/SoundPool and new OpenAL Soft). Added AppSetting that allows AndroidHarness to switch the audio renderer (default is still MediaPlayer/SoundPool).

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10615 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
iwg..om 12 years ago
parent 96f7b179b5
commit e727928731
  1. 26
      engine/src/android/com/jme3/app/AndroidHarness.java
  2. 31
      engine/src/android/com/jme3/asset/AndroidAssetManager.java
  3. 521
      engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java
  4. 523
      engine/src/android/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java
  5. 2
      engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java
  6. 35
      engine/src/android/com/jme3/system/android/JmeAndroidSystem.java
  7. 211
      engine/src/core/com/jme3/system/AppSettings.java

@ -15,7 +15,6 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.jme3.audio.AudioRenderer; import com.jme3.audio.AudioRenderer;
import com.jme3.audio.android.AndroidAudioRenderer; import com.jme3.audio.android.AndroidAudioRenderer;
import com.jme3.audio.android.AndroidOpenALSoftAudioRenderer;
import com.jme3.input.JoyInput; import com.jme3.input.JoyInput;
import com.jme3.input.TouchInput; import com.jme3.input.TouchInput;
import com.jme3.input.android.AndroidSensorJoyInput; import com.jme3.input.android.AndroidSensorJoyInput;
@ -72,6 +71,20 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
* set to 2, 4 to enable multisampling. * set to 2, 4 to enable multisampling.
*/ */
protected int antiAliasingSamples = 0; protected int antiAliasingSamples = 0;
/**
* Sets the type of Audio Renderer to be used.
* <p>
* Android MediaPlayer / SoundPool is the default and can be used on all
* supported Android platform versions (2.2+)<br>
* OpenAL Soft uses an OpenSL backend and is only supported on Android
* versions 2.3+.
* <p>
* Only use ANDROID_ static strings found in AppSettings
*
*/
protected String audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
/** /**
* If true Android Sensors are used as simulated Joysticks Users can use the * If true Android Sensors are used as simulated Joysticks Users can use the
* Android sensor feedback through the RawInputListener or by registering * Android sensor feedback through the RawInputListener or by registering
@ -110,7 +123,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
/** /**
* Set the screen window mode. If screenFullSize is true, then the * Set the screen window mode. If screenFullSize is true, then the
* notification bar and title bar are removed and the screen covers the * notification bar and title bar are removed and the screen covers the
* entire display.   If screenFullSize is false, then the notification bar * entire display. If screenFullSize is false, then the notification bar
* remains visible if screenShowTitle is true while screenFullScreen is * remains visible if screenShowTitle is true while screenFullScreen is
* false, then the title bar is also displayed under the notification bar. * false, then the title bar is also displayed under the notification bar.
*/ */
@ -200,6 +213,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
settings.setSamples(antiAliasingSamples); settings.setSamples(antiAliasingSamples);
settings.setResolution(disp.getWidth(), disp.getHeight()); settings.setResolution(disp.getWidth(), disp.getHeight());
settings.put(AndroidConfigChooser.SETTINGS_CONFIG_TYPE, eglConfigType); settings.put(AndroidConfigChooser.SETTINGS_CONFIG_TYPE, eglConfigType);
settings.setAudioRenderer(audioRendererType);
// Create application instance // Create application instance
@ -487,10 +501,6 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.resumeAll(); renderer.resumeAll();
} }
if (result instanceof AndroidOpenALSoftAudioRenderer) {
AndroidOpenALSoftAudioRenderer renderer = (AndroidOpenALSoftAudioRenderer) result;
renderer.resumeAll();
}
} }
//resume the sensors (aka joysticks) //resume the sensors (aka joysticks)
if (app.getContext() != null) { if (app.getContext() != null) {
@ -530,10 +540,6 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
renderer.pauseAll(); renderer.pauseAll();
} }
if (result instanceof AndroidOpenALSoftAudioRenderer) {
AndroidOpenALSoftAudioRenderer renderer = (AndroidOpenALSoftAudioRenderer) result;
renderer.pauseAll();
}
} }
//pause the sensors (aka joysticks) //pause the sensors (aka joysticks)
if (app.getContext() != null) { if (app.getContext() != null) {

@ -33,8 +33,11 @@ package com.jme3.asset;
import com.jme3.asset.plugins.AndroidLocator; import com.jme3.asset.plugins.AndroidLocator;
import com.jme3.asset.plugins.ClasspathLocator; import com.jme3.asset.plugins.ClasspathLocator;
import com.jme3.audio.android.AndroidAudioRenderer;
import com.jme3.audio.plugins.AndroidAudioLoader; import com.jme3.audio.plugins.AndroidAudioLoader;
import com.jme3.texture.Texture; import com.jme3.audio.plugins.WAVLoader;
import com.jme3.system.AppSettings;
import com.jme3.system.android.JmeAndroidSystem;
import com.jme3.texture.plugins.AndroidImageLoader; import com.jme3.texture.plugins.AndroidImageLoader;
import java.net.URL; import java.net.URL;
import java.util.logging.Level; import java.util.logging.Level;
@ -58,7 +61,7 @@ public class AndroidAssetManager extends DesktopAssetManager {
//this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg")); //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));
this(null); this(null);
} }
private void registerLoaderSafe(String loaderClass, String ... extensions) { private void registerLoaderSafe(String loaderClass, String ... extensions) {
try { try {
Class<? extends AssetLoader> loader = (Class<? extends AssetLoader>) Class.forName(loaderClass); Class<? extends AssetLoader> loader = (Class<? extends AssetLoader>) Class.forName(loaderClass);
@ -73,22 +76,32 @@ public class AndroidAssetManager extends DesktopAssetManager {
* If URL == null then a default list of locators and loaders for android is set * If URL == null then a default list of locators and loaders for android is set
* @param configFile * @param configFile
*/ */
public AndroidAssetManager(URL configFile) { public AndroidAssetManager(URL configFile) {
System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
// Set Default Android config // Set Default Android config
registerLocator("", AndroidLocator.class); registerLocator("", AndroidLocator.class);
registerLocator("", ClasspathLocator.class); registerLocator("", ClasspathLocator.class);
registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg"); registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");
registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav"); if (JmeAndroidSystem.getAudioRendererType().equals(AppSettings.ANDROID_MEDIAPLAYER)) {
registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav");
} else if (JmeAndroidSystem.getAudioRendererType().equals(AppSettings.ANDROID_OPENAL_SOFT)) {
registerLoader(WAVLoader.class, "wav");
// TODO jogg is not in core, need to add some other way to get around compile errors, or not.
// registerLoader(com.jme3.audio.plugins.OGGLoader.class, "ogg");
registerLoaderSafe("com.jme3.audio.plugins.OGGLoader", "ogg");
} else {
throw new IllegalStateException("No Audio Renderer Type defined!");
}
registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m"); registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");
registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md"); registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");
registerLoader(com.jme3.material.plugins.ShaderNodeDefinitionLoader.class, "j3sn"); registerLoader(com.jme3.material.plugins.ShaderNodeDefinitionLoader.class, "j3sn");
registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");
registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");
registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");
// Less common loaders (especially on Android) // Less common loaders (especially on Android)
registerLoaderSafe("com.jme3.texture.plugins.DDSLoader", "dds"); registerLoaderSafe("com.jme3.texture.plugins.DDSLoader", "dds");
registerLoaderSafe("com.jme3.texture.plugins.PFMLoader", "pfm"); registerLoaderSafe("com.jme3.texture.plugins.PFMLoader", "pfm");
@ -100,9 +113,9 @@ public class AndroidAssetManager extends DesktopAssetManager {
registerLoaderSafe("com.jme3.scene.plugins.ogre.SkeletonLoader", "skeleton.xml"); registerLoaderSafe("com.jme3.scene.plugins.ogre.SkeletonLoader", "skeleton.xml");
registerLoaderSafe("com.jme3.scene.plugins.ogre.MaterialLoader", "material"); registerLoaderSafe("com.jme3.scene.plugins.ogre.MaterialLoader", "material");
registerLoaderSafe("com.jme3.scene.plugins.ogre.SceneLoader", "scene"); registerLoaderSafe("com.jme3.scene.plugins.ogre.SceneLoader", "scene");
logger.fine("AndroidAssetManager created."); logger.fine("AndroidAssetManager created.");
} }
} }

@ -1,523 +1,24 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.audio.android; package com.jme3.audio.android;
import android.app.Activity; import com.jme3.audio.AudioRenderer;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import com.jme3.asset.AssetKey;
import com.jme3.audio.*;
import com.jme3.audio.AudioSource.Status;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* This class is the android implementation for {@link AudioRenderer} * Android specific AudioRenderer interface that supports pausing and resuming
* audio files when the app is minimized or placed in the background
* *
* @author larynx * @author iwgeric
* @author plan_rich
*/ */
public class AndroidAudioRenderer implements AudioRenderer, public interface AndroidAudioRenderer extends AudioRenderer {
SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
private static final Logger logger = Logger.getLogger(AndroidAudioRenderer.class.getName());
private final static int MAX_NUM_CHANNELS = 16;
private final HashMap<AudioSource, MediaPlayer> musicPlaying = new HashMap<AudioSource, MediaPlayer>();
private SoundPool soundPool = null;
private final Vector3f listenerPosition = new Vector3f();
// For temp use
private final Vector3f distanceVector = new Vector3f();
private final AssetManager assetManager;
private HashMap<Integer, AudioSource> soundpoolStillLoading = new HashMap<Integer, AudioSource>();
private Listener listener;
private boolean audioDisabled = false;
private final AudioManager manager;
public AndroidAudioRenderer(Activity context) {
manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
assetManager = context.getAssets();
}
@Override
public void initialize() {
soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
0);
soundPool.setOnLoadCompleteListener(this);
}
@Override
public void updateSourceParam(AudioSource src, AudioParam param) {
if (audioDisabled) {
return;
}
if (src.getChannel() < 0) {
return;
}
switch (param) {
case Position:
if (!src.isPositional()) {
return;
}
Vector3f pos = src.getPosition();
break;
case Velocity:
if (!src.isPositional()) {
return;
}
Vector3f vel = src.getVelocity();
break;
case MaxDistance:
if (!src.isPositional()) {
return;
}
break;
case RefDistance:
if (!src.isPositional()) {
return;
}
break;
case ReverbFilter:
if (!src.isPositional() || !src.isReverbEnabled()) {
return;
}
break;
case ReverbEnabled:
if (!src.isPositional()) {
return;
}
if (src.isReverbEnabled()) {
updateSourceParam(src, AudioParam.ReverbFilter);
}
break;
case IsPositional:
break;
case Direction:
if (!src.isDirectional()) {
return;
}
Vector3f dir = src.getDirection();
break;
case InnerAngle:
if (!src.isDirectional()) {
return;
}
break;
case OuterAngle:
if (!src.isDirectional()) {
return;
}
break;
case IsDirectional:
if (src.isDirectional()) {
updateSourceParam(src, AudioParam.Direction);
updateSourceParam(src, AudioParam.InnerAngle);
updateSourceParam(src, AudioParam.OuterAngle);
} else {
}
break;
case DryFilter:
if (src.getDryFilter() != null) {
Filter f = src.getDryFilter();
if (f.isUpdateNeeded()) {
// updateFilter(f);
}
}
break;
case Looping:
if (src.isLooping()) {
}
break;
case Volume:
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.setVolume(src.getVolume(), src.getVolume());
} else {
soundPool.setVolume(src.getChannel(), src.getVolume(),
src.getVolume());
}
break;
case Pitch:
break;
}
}
@Override
public void updateListenerParam(Listener listener, ListenerParam param) {
if (audioDisabled) {
return;
}
switch (param) {
case Position:
listenerPosition.set(listener.getLocation());
break;
case Rotation:
Vector3f dir = listener.getDirection();
Vector3f up = listener.getUp();
break;
case Velocity:
Vector3f vel = listener.getVelocity();
break;
case Volume:
// alListenerf(AL_GAIN, listener.getVolume());
break;
}
}
@Override
public void update(float tpf) {
float distance;
float volume;
// Loop over all mediaplayers
for (AudioSource src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src);
// Calc the distance to the listener
distanceVector.set(listenerPosition);
distanceVector.subtractLocal(src.getPosition());
distance = FastMath.abs(distanceVector.length());
if (distance < src.getRefDistance()) {
distance = src.getRefDistance();
}
if (distance > src.getMaxDistance()) {
distance = src.getMaxDistance();
}
volume = src.getRefDistance() / distance;
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
// Left / Right channel get the same volume by now, only
// positional
mp.setVolume(volume, volume);
audioData.setCurrentVolume(volume);
}
}
}
public void setListener(Listener listener) {
if (audioDisabled) {
return;
}
if (this.listener != null) {
// previous listener no longer associated with current
// renderer
this.listener.setRenderer(null);
}
this.listener = listener;
this.listener.setRenderer(this);
}
@Override
public void cleanup() {
// Cleanup sound pool
if (soundPool != null) {
soundPool.release();
soundPool = null;
}
// Cleanup media player
for (AudioSource src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src);
{
mp.stop();
mp.release();
src.setStatus(Status.Stopped);
}
}
musicPlaying.clear();
}
@Override
public void onCompletion(MediaPlayer mp) {
if (mp.isPlaying()) {
mp.seekTo(0);
mp.stop();
}
// XXX: This has bad performance -> maybe change overall structure of
// mediaplayer in this audiorenderer?
for (AudioSource src : musicPlaying.keySet()) {
if (musicPlaying.get(src) == mp) {
src.setStatus(Status.Stopped);
break;
}
}
}
/** /**
* Plays using the {@link SoundPool} of Android. Due to hard limitation of * Pauses all Playing audio. To be used when the app is placed in the
* the SoundPool: After playing more instances of the sound you only have * background.
* the channel of the last played instance.
*
* It is not possible to get information about the state of the soundpool of
* a specific streamid, so removing is not possilbe -> noone knows when
* sound finished.
*/ */
public void playSourceInstance(AudioSource src) { public void pauseAll();
if (audioDisabled) {
return;
}
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (!(audioData.getAssetKey() instanceof AudioKey)) {
throw new IllegalArgumentException("Asset is not a AudioKey");
}
AudioKey assetKey = (AudioKey) audioData.getAssetKey();
try {
if (audioData.getId() < 0) { // found something to load
int soundId = soundPool.load(
assetManager.openFd(assetKey.getName()), 1);
audioData.setId(soundId);
}
int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
if (channel == 0) {
soundpoolStillLoading.put(audioData.getId(), src);
} else {
if (src.getStatus() != Status.Stopped) {
soundPool.stop(channel);
src.setStatus(Status.Stopped);
}
src.setChannel(channel); // receive a channel at the last
setSourceParams(src);
// playing at least
}
} catch (IOException e) {
logger.log(Level.SEVERE,
"Failed to load sound " + assetKey.getName(), e);
audioData.setId(-1);
}
}
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
AudioSource src = soundpoolStillLoading.remove(sampleId);
if (src == null) {
logger.warning("Something went terribly wrong! onLoadComplete"
+ " had sampleId which was not in the HashMap of loading items");
return;
}
AudioData audioData = src.getAudioData();
// load was successfull
if (status == 0) {
int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
src.setChannel(channelIndex);
setSourceParams(src);
}
}
public void playSource(AudioSource src) {
if (audioDisabled) {
return;
}
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
MediaPlayer mp = musicPlaying.get(src);
if (mp == null) {
mp = new MediaPlayer();
mp.setOnCompletionListener(this);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
try {
if (src.getStatus() == Status.Stopped) {
mp.reset();
AssetKey<?> key = audioData.getAssetKey();
AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
afd.getLength());
mp.prepare();
setSourceParams(src, mp);
src.setChannel(0);
src.setStatus(Status.Playing);
musicPlaying.put(src, mp);
mp.start();
} else {
mp.start();
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void setSourceParams(AudioSource src, MediaPlayer mp) {
mp.setLooping(src.isLooping());
mp.setVolume(src.getVolume(), src.getVolume());
//src.getDryFilter();
}
private void setSourceParams(AudioSource src) {
soundPool.setLoop(src.getChannel(), src.isLooping() ? -1 : 0);
soundPool.setVolume(src.getChannel(), src.getVolume(), src.getVolume());
}
/** /**
* Pause the current playing sounds. Both from the {@link SoundPool} and the * Resumes all Paused audio. To be used when the app is brought back to
* active {@link MediaPlayer}s * the foreground.
*/ */
public void pauseAll() { public void resumeAll();
if (soundPool != null) {
soundPool.autoPause();
for (MediaPlayer mp : musicPlaying.values()) {
if(mp.isPlaying()){
mp.pause();
}
}
}
}
/**
* Resume all paused sounds.
*/
public void resumeAll() {
if (soundPool != null) {
soundPool.autoResume();
for (MediaPlayer mp : musicPlaying.values()) {
mp.start(); //no resume -> api says call start to resume
}
}
}
public void pauseSource(AudioSource src) {
if (audioDisabled) {
return;
}
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.pause();
src.setStatus(Status.Paused);
} else {
int channel = src.getChannel();
if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
} // something useful :)
}
}
public void stopSource(AudioSource src) {
if (audioDisabled) {
return;
}
// can be stream or buffer -> so try to get mediaplayer
// if there is non try to stop soundpool
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.stop();
mp.reset();
src.setStatus(Status.Stopped);
} else {
int channel = src.getChannel();
if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
// something useful :)
}
}
}
@Override
public void deleteAudioData(AudioData ad) {
for (AudioSource src : musicPlaying.keySet()) {
if (src.getAudioData() == ad) {
MediaPlayer mp = musicPlaying.remove(src);
mp.stop();
mp.release();
src.setStatus(Status.Stopped);
src.setChannel(-1);
ad.setId(-1);
break;
}
}
if (ad.getId() > 0) {
soundPool.unload(ad.getId());
ad.setId(-1);
}
}
@Override
public void setEnvironment(Environment env) {
// not yet supported
}
@Override
public void deleteFilter(Filter filter) {
}
} }

@ -0,0 +1,523 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.audio.android;
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import com.jme3.asset.AssetKey;
import com.jme3.audio.*;
import com.jme3.audio.AudioSource.Status;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class is the android implementation for {@link AudioRenderer}
*
* @author larynx
* @author plan_rich
*/
public class AndroidMediaPlayerAudioRenderer implements AndroidAudioRenderer,
SoundPool.OnLoadCompleteListener, MediaPlayer.OnCompletionListener {
private static final Logger logger = Logger.getLogger(AndroidMediaPlayerAudioRenderer.class.getName());
private final static int MAX_NUM_CHANNELS = 16;
private final HashMap<AudioSource, MediaPlayer> musicPlaying = new HashMap<AudioSource, MediaPlayer>();
private SoundPool soundPool = null;
private final Vector3f listenerPosition = new Vector3f();
// For temp use
private final Vector3f distanceVector = new Vector3f();
private final AssetManager assetManager;
private HashMap<Integer, AudioSource> soundpoolStillLoading = new HashMap<Integer, AudioSource>();
private Listener listener;
private boolean audioDisabled = false;
private final AudioManager manager;
public AndroidMediaPlayerAudioRenderer(Activity context) {
manager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
context.setVolumeControlStream(AudioManager.STREAM_MUSIC);
assetManager = context.getAssets();
}
@Override
public void initialize() {
soundPool = new SoundPool(MAX_NUM_CHANNELS, AudioManager.STREAM_MUSIC,
0);
soundPool.setOnLoadCompleteListener(this);
}
@Override
public void updateSourceParam(AudioSource src, AudioParam param) {
if (audioDisabled) {
return;
}
if (src.getChannel() < 0) {
return;
}
switch (param) {
case Position:
if (!src.isPositional()) {
return;
}
Vector3f pos = src.getPosition();
break;
case Velocity:
if (!src.isPositional()) {
return;
}
Vector3f vel = src.getVelocity();
break;
case MaxDistance:
if (!src.isPositional()) {
return;
}
break;
case RefDistance:
if (!src.isPositional()) {
return;
}
break;
case ReverbFilter:
if (!src.isPositional() || !src.isReverbEnabled()) {
return;
}
break;
case ReverbEnabled:
if (!src.isPositional()) {
return;
}
if (src.isReverbEnabled()) {
updateSourceParam(src, AudioParam.ReverbFilter);
}
break;
case IsPositional:
break;
case Direction:
if (!src.isDirectional()) {
return;
}
Vector3f dir = src.getDirection();
break;
case InnerAngle:
if (!src.isDirectional()) {
return;
}
break;
case OuterAngle:
if (!src.isDirectional()) {
return;
}
break;
case IsDirectional:
if (src.isDirectional()) {
updateSourceParam(src, AudioParam.Direction);
updateSourceParam(src, AudioParam.InnerAngle);
updateSourceParam(src, AudioParam.OuterAngle);
} else {
}
break;
case DryFilter:
if (src.getDryFilter() != null) {
Filter f = src.getDryFilter();
if (f.isUpdateNeeded()) {
// updateFilter(f);
}
}
break;
case Looping:
if (src.isLooping()) {
}
break;
case Volume:
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.setVolume(src.getVolume(), src.getVolume());
} else {
soundPool.setVolume(src.getChannel(), src.getVolume(),
src.getVolume());
}
break;
case Pitch:
break;
}
}
@Override
public void updateListenerParam(Listener listener, ListenerParam param) {
if (audioDisabled) {
return;
}
switch (param) {
case Position:
listenerPosition.set(listener.getLocation());
break;
case Rotation:
Vector3f dir = listener.getDirection();
Vector3f up = listener.getUp();
break;
case Velocity:
Vector3f vel = listener.getVelocity();
break;
case Volume:
// alListenerf(AL_GAIN, listener.getVolume());
break;
}
}
@Override
public void update(float tpf) {
float distance;
float volume;
// Loop over all mediaplayers
for (AudioSource src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src);
// Calc the distance to the listener
distanceVector.set(listenerPosition);
distanceVector.subtractLocal(src.getPosition());
distance = FastMath.abs(distanceVector.length());
if (distance < src.getRefDistance()) {
distance = src.getRefDistance();
}
if (distance > src.getMaxDistance()) {
distance = src.getMaxDistance();
}
volume = src.getRefDistance() / distance;
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (FastMath.abs(audioData.getCurrentVolume() - volume) > FastMath.FLT_EPSILON) {
// Left / Right channel get the same volume by now, only
// positional
mp.setVolume(volume, volume);
audioData.setCurrentVolume(volume);
}
}
}
public void setListener(Listener listener) {
if (audioDisabled) {
return;
}
if (this.listener != null) {
// previous listener no longer associated with current
// renderer
this.listener.setRenderer(null);
}
this.listener = listener;
this.listener.setRenderer(this);
}
@Override
public void cleanup() {
// Cleanup sound pool
if (soundPool != null) {
soundPool.release();
soundPool = null;
}
// Cleanup media player
for (AudioSource src : musicPlaying.keySet()) {
MediaPlayer mp = musicPlaying.get(src);
{
mp.stop();
mp.release();
src.setStatus(Status.Stopped);
}
}
musicPlaying.clear();
}
@Override
public void onCompletion(MediaPlayer mp) {
if (mp.isPlaying()) {
mp.seekTo(0);
mp.stop();
}
// XXX: This has bad performance -> maybe change overall structure of
// mediaplayer in this audiorenderer?
for (AudioSource src : musicPlaying.keySet()) {
if (musicPlaying.get(src) == mp) {
src.setStatus(Status.Stopped);
break;
}
}
}
/**
* Plays using the {@link SoundPool} of Android. Due to hard limitation of
* the SoundPool: After playing more instances of the sound you only have
* the channel of the last played instance.
*
* It is not possible to get information about the state of the soundpool of
* a specific streamid, so removing is not possilbe -> noone knows when
* sound finished.
*/
public void playSourceInstance(AudioSource src) {
if (audioDisabled) {
return;
}
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
if (!(audioData.getAssetKey() instanceof AudioKey)) {
throw new IllegalArgumentException("Asset is not a AudioKey");
}
AudioKey assetKey = (AudioKey) audioData.getAssetKey();
try {
if (audioData.getId() < 0) { // found something to load
int soundId = soundPool.load(
assetManager.openFd(assetKey.getName()), 1);
audioData.setId(soundId);
}
int channel = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
if (channel == 0) {
soundpoolStillLoading.put(audioData.getId(), src);
} else {
if (src.getStatus() != Status.Stopped) {
soundPool.stop(channel);
src.setStatus(Status.Stopped);
}
src.setChannel(channel); // receive a channel at the last
setSourceParams(src);
// playing at least
}
} catch (IOException e) {
logger.log(Level.SEVERE,
"Failed to load sound " + assetKey.getName(), e);
audioData.setId(-1);
}
}
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
AudioSource src = soundpoolStillLoading.remove(sampleId);
if (src == null) {
logger.warning("Something went terribly wrong! onLoadComplete"
+ " had sampleId which was not in the HashMap of loading items");
return;
}
AudioData audioData = src.getAudioData();
// load was successfull
if (status == 0) {
int channelIndex;
channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
src.setChannel(channelIndex);
setSourceParams(src);
}
}
public void playSource(AudioSource src) {
if (audioDisabled) {
return;
}
AndroidAudioData audioData = (AndroidAudioData) src.getAudioData();
MediaPlayer mp = musicPlaying.get(src);
if (mp == null) {
mp = new MediaPlayer();
mp.setOnCompletionListener(this);
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
}
try {
if (src.getStatus() == Status.Stopped) {
mp.reset();
AssetKey<?> key = audioData.getAssetKey();
AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
afd.getLength());
mp.prepare();
setSourceParams(src, mp);
src.setChannel(0);
src.setStatus(Status.Playing);
musicPlaying.put(src, mp);
mp.start();
} else {
mp.start();
}
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void setSourceParams(AudioSource src, MediaPlayer mp) {
mp.setLooping(src.isLooping());
mp.setVolume(src.getVolume(), src.getVolume());
//src.getDryFilter();
}
private void setSourceParams(AudioSource src) {
soundPool.setLoop(src.getChannel(), src.isLooping() ? -1 : 0);
soundPool.setVolume(src.getChannel(), src.getVolume(), src.getVolume());
}
/**
* Pause the current playing sounds. Both from the {@link SoundPool} and the
* active {@link MediaPlayer}s
*/
public void pauseAll() {
if (soundPool != null) {
soundPool.autoPause();
for (MediaPlayer mp : musicPlaying.values()) {
if(mp.isPlaying()){
mp.pause();
}
}
}
}
/**
* Resume all paused sounds.
*/
public void resumeAll() {
if (soundPool != null) {
soundPool.autoResume();
for (MediaPlayer mp : musicPlaying.values()) {
mp.start(); //no resume -> api says call start to resume
}
}
}
public void pauseSource(AudioSource src) {
if (audioDisabled) {
return;
}
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.pause();
src.setStatus(Status.Paused);
} else {
int channel = src.getChannel();
if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
} // something useful :)
}
}
public void stopSource(AudioSource src) {
if (audioDisabled) {
return;
}
// can be stream or buffer -> so try to get mediaplayer
// if there is non try to stop soundpool
MediaPlayer mp = musicPlaying.get(src);
if (mp != null) {
mp.stop();
mp.reset();
src.setStatus(Status.Stopped);
} else {
int channel = src.getChannel();
if (channel != -1) {
soundPool.pause(channel); // is not very likley to make
// something useful :)
}
}
}
@Override
public void deleteAudioData(AudioData ad) {
for (AudioSource src : musicPlaying.keySet()) {
if (src.getAudioData() == ad) {
MediaPlayer mp = musicPlaying.remove(src);
mp.stop();
mp.release();
src.setStatus(Status.Stopped);
src.setChannel(-1);
ad.setId(-1);
break;
}
}
if (ad.getId() > 0) {
soundPool.unload(ad.getId());
ad.setId(-1);
}
}
@Override
public void setEnvironment(Environment env) {
// not yet supported
}
@Override
public void deleteFilter(Filter filter) {
}
}

@ -44,7 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
public class AndroidOpenALSoftAudioRenderer implements AudioRenderer, Runnable { public class AndroidOpenALSoftAudioRenderer implements AndroidAudioRenderer, Runnable {
private static final Logger logger = Logger.getLogger(AndroidOpenALSoftAudioRenderer.class.getName()); private static final Logger logger = Logger.getLogger(AndroidOpenALSoftAudioRenderer.class.getName());
private final NativeObjectManager objManager = new NativeObjectManager(); private final NativeObjectManager objManager = new NativeObjectManager();

@ -10,25 +10,25 @@ import com.jme3.asset.AndroidImageInfo;
import com.jme3.asset.AssetManager; import com.jme3.asset.AssetManager;
import com.jme3.audio.AudioRenderer; import com.jme3.audio.AudioRenderer;
import com.jme3.audio.android.AndroidAudioRenderer; import com.jme3.audio.android.AndroidAudioRenderer;
import com.jme3.audio.android.AndroidMediaPlayerAudioRenderer;
import com.jme3.audio.android.AndroidOpenALSoftAudioRenderer;
import com.jme3.system.*; import com.jme3.system.*;
import com.jme3.system.JmeContext.Type; import com.jme3.system.JmeContext.Type;
import com.jme3.texture.Image; import com.jme3.texture.Image;
import com.jme3.texture.image.DefaultImageRaster; import com.jme3.texture.image.DefaultImageRaster;
import com.jme3.texture.image.ImageRaster; import com.jme3.texture.image.ImageRaster;
import com.jme3.util.AndroidScreenshots; import com.jme3.util.AndroidScreenshots;
import com.jme3.util.JmeFormatter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.logging.Handler;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
public class JmeAndroidSystem extends JmeSystemDelegate { public class JmeAndroidSystem extends JmeSystemDelegate {
private static Activity activity; private static Activity activity;
private static String audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
static { static {
try { try {
@ -97,6 +97,16 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
@Override @Override
public JmeContext newContext(AppSettings settings, Type contextType) { public JmeContext newContext(AppSettings settings, Type contextType) {
if (settings.getAudioRenderer().equals(AppSettings.ANDROID_MEDIAPLAYER)) {
logger.log(Level.INFO, "newContext settings set to Android MediaPlayer / SoundPool");
audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
} else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_OPENAL_SOFT)) {
logger.log(Level.INFO, "newContext settings set to Android OpenAL Soft");
audioRendererType = AppSettings.ANDROID_OPENAL_SOFT;
} else {
logger.log(Level.INFO, "AudioRenderer not set. Defaulting to Android MediaPlayer / SoundPool");
audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
}
initialize(settings); initialize(settings);
JmeContext ctx = new OGLESContext(); JmeContext ctx = new OGLESContext();
ctx.setSettings(settings); ctx.setSettings(settings);
@ -105,7 +115,20 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
@Override @Override
public AudioRenderer newAudioRenderer(AppSettings settings) { public AudioRenderer newAudioRenderer(AppSettings settings) {
return new AndroidAudioRenderer(activity);
if (settings.getAudioRenderer().equals(AppSettings.ANDROID_MEDIAPLAYER)) {
logger.log(Level.INFO, "newAudioRenderer settings set to Android MediaPlayer / SoundPool");
audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
return new AndroidMediaPlayerAudioRenderer(activity);
} else if (settings.getAudioRenderer().equals(AppSettings.ANDROID_OPENAL_SOFT)) {
logger.log(Level.INFO, "newAudioRenderer settings set to Android OpenAL Soft");
audioRendererType = AppSettings.ANDROID_OPENAL_SOFT;
return new AndroidOpenALSoftAudioRenderer();
} else {
logger.log(Level.INFO, "AudioRenderer not set. Defaulting to Android MediaPlayer / SoundPool");
audioRendererType = AppSettings.ANDROID_MEDIAPLAYER;
return new AndroidMediaPlayerAudioRenderer(activity);
}
} }
@Override @Override
@ -198,4 +221,8 @@ public class JmeAndroidSystem extends JmeSystemDelegate {
public static Activity getActivity() { public static Activity getActivity() {
return activity; return activity;
} }
public static String getAudioRendererType() {
return audioRendererType;
}
} }

@ -42,69 +42,88 @@ import java.util.prefs.Preferences;
/** /**
* <code>AppSettings</code> provides a store of configuration * <code>AppSettings</code> provides a store of configuration
* to be used by the application. * to be used by the application.
* <p> * <p>
* By default only the {@link JmeContext context} uses the configuration, * By default only the {@link JmeContext context} uses the configuration,
* however the user may set and retrieve the settings as well. * however the user may set and retrieve the settings as well.
* The settings can be stored either in the Java preferences * The settings can be stored either in the Java preferences
* (using {@link #save(java.lang.String) } or * (using {@link #save(java.lang.String) } or
* a .properties file (using {@link #save(java.io.OutputStream) }. * a .properties file (using {@link #save(java.io.OutputStream) }.
* *
* @author Kirill Vainer * @author Kirill Vainer
*/ */
public final class AppSettings extends HashMap<String, Object> { public final class AppSettings extends HashMap<String, Object> {
private static final AppSettings defaults = new AppSettings(false); private static final AppSettings defaults = new AppSettings(false);
/** /**
* Use LWJGL as the display system and force using the OpenGL1.1 renderer. * Use LWJGL as the display system and force using the OpenGL1.1 renderer.
* *
* @see AppSettings#setRenderer(java.lang.String) * @see AppSettings#setRenderer(java.lang.String)
*/ */
public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1"; public static final String LWJGL_OPENGL1 = "LWJGL-OPENGL1";
/** /**
* Use LWJGL as the display system and force using the OpenGL2.0 renderer. * Use LWJGL as the display system and force using the OpenGL2.0 renderer.
* <p> * <p>
* If the underlying system does not support OpenGL2.0, then the context * If the underlying system does not support OpenGL2.0, then the context
* initialization will throw an exception. * initialization will throw an exception.
* *
* @see AppSettings#setRenderer(java.lang.String) * @see AppSettings#setRenderer(java.lang.String)
*/ */
public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2"; public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2";
/** /**
* Use LWJGL as the display system and force using the core OpenGL3.3 renderer. * Use LWJGL as the display system and force using the core OpenGL3.3 renderer.
* <p> * <p>
* If the underlying system does not support OpenGL3.2, then the context * If the underlying system does not support OpenGL3.2, then the context
* initialization will throw an exception. Note that currently jMonkeyEngine * initialization will throw an exception. Note that currently jMonkeyEngine
* does not have any shaders that support OpenGL3.2 therefore this * does not have any shaders that support OpenGL3.2 therefore this
* option is not useful. * option is not useful.
* <p> * <p>
* Note: OpenGL 3.2 is used to give 3.x support to Mac users. * Note: OpenGL 3.2 is used to give 3.x support to Mac users.
* *
* @see AppSettings#setRenderer(java.lang.String) * @see AppSettings#setRenderer(java.lang.String)
*/ */
public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3";
/** /**
* Use LWJGL as the display system and allow the context * Use LWJGL as the display system and allow the context
* to choose an appropriate renderer based on system capabilities. * to choose an appropriate renderer based on system capabilities.
* <p> * <p>
* If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will * If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will
* be used, otherwise, the OpenGL1.1 renderer is used. * be used, otherwise, the OpenGL1.1 renderer is used.
* *
* @see AppSettings#setRenderer(java.lang.String) * @see AppSettings#setRenderer(java.lang.String)
*/ */
public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any"; public static final String LWJGL_OPENGL_ANY = "LWJGL-OpenGL-Any";
/** /**
* Use the LWJGL OpenAL based renderer for audio capabilities. * Use the LWJGL OpenAL based renderer for audio capabilities.
* *
* @see AppSettings#setAudioRenderer(java.lang.String) * @see AppSettings#setAudioRenderer(java.lang.String)
*/ */
public static final String LWJGL_OPENAL = "LWJGL"; public static final String LWJGL_OPENAL = "LWJGL";
/**
* Use the Android MediaPlayer / SoundPool based renderer for Android audio capabilities.
* <p>
* NOTE: Supports Android 2.2+ platforms. This is the current default for
* Android platforms.
*
* @see AppSettings#setAudioRenderer(java.lang.String)
*/
public static final String ANDROID_MEDIAPLAYER = "MediaPlayer";
/**
* Use the OpenAL Soft based renderer for Android audio capabilities.
* <p>
* NOTE: Only to be used on Android 2.3+ platforms due to using OpenSL.
*
* @see AppSettings#setAudioRenderer(java.lang.String)
*/
public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT";
static { static {
defaults.put("Width", 640); defaults.put("Width", 640);
defaults.put("Height", 480); defaults.put("Height", 480);
@ -131,10 +150,10 @@ public final class AppSettings extends HashMap<String, Object> {
* Create a new instance of <code>AppSettings</code>. * Create a new instance of <code>AppSettings</code>.
* <p> * <p>
* If <code>loadDefaults</code> is true, then the default settings * If <code>loadDefaults</code> is true, then the default settings
* will be set on the AppSettings. * will be set on the AppSettings.
* Use false if you want to change some settings but you would like the * Use false if you want to change some settings but you would like the
* application to load settings from previous launches. * application to load settings from previous launches.
* *
* @param loadDefaults If default settings are to be loaded. * @param loadDefaults If default settings are to be loaded.
*/ */
public AppSettings(boolean loadDefaults) { public AppSettings(boolean loadDefaults) {
@ -145,11 +164,11 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Copies all settings from <code>other</code> to <code>this</code> * Copies all settings from <code>other</code> to <code>this</code>
* AppSettings. * AppSettings.
* <p> * <p>
* Any settings that are specified in other will overwrite settings * Any settings that are specified in other will overwrite settings
* set on this AppSettings. * set on this AppSettings.
* *
* @param other The AppSettings to copy the settings from * @param other The AppSettings to copy the settings from
*/ */
public void copyFrom(AppSettings other) { public void copyFrom(AppSettings other) {
@ -159,7 +178,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except
* doesn't overwrite settings that are already set. * doesn't overwrite settings that are already set.
* *
* @param other The AppSettings to merge the settings from * @param other The AppSettings to merge the settings from
*/ */
public void mergeFrom(AppSettings other) { public void mergeFrom(AppSettings other) {
@ -172,11 +191,11 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Loads the settings from the given properties input stream. * Loads the settings from the given properties input stream.
* *
* @param in The InputStream to load from * @param in The InputStream to load from
* @throws IOException If an IOException occurs * @throws IOException If an IOException occurs
* *
* @see #save(java.io.OutputStream) * @see #save(java.io.OutputStream)
*/ */
public void load(InputStream in) throws IOException { public void load(InputStream in) throws IOException {
Properties props = new Properties(); Properties props = new Properties();
@ -207,11 +226,11 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Saves all settings to the given properties output stream. * Saves all settings to the given properties output stream.
* *
* @param out The OutputStream to write to * @param out The OutputStream to write to
* @throws IOException If an IOException occurs * @throws IOException If an IOException occurs
* *
* @see #load(java.io.InputStream) * @see #load(java.io.InputStream)
*/ */
public void save(OutputStream out) throws IOException { public void save(OutputStream out) throws IOException {
Properties props = new Properties(); Properties props = new Properties();
@ -238,11 +257,11 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Loads settings previously saved in the Java preferences. * Loads settings previously saved in the Java preferences.
* *
* @param preferencesKey The preferencesKey previously used to save the settings. * @param preferencesKey The preferencesKey previously used to save the settings.
* @throws BackingStoreException If an exception occurs with the preferences * @throws BackingStoreException If an exception occurs with the preferences
* *
* @see #save(java.lang.String) * @see #save(java.lang.String)
*/ */
public void load(String preferencesKey) throws BackingStoreException { public void load(String preferencesKey) throws BackingStoreException {
Preferences prefs = Preferences.userRoot().node(preferencesKey); Preferences prefs = Preferences.userRoot().node(preferencesKey);
@ -289,10 +308,10 @@ public final class AppSettings extends HashMap<String, Object> {
* On the Windows operating system, the preferences are saved in the registry * On the Windows operating system, the preferences are saved in the registry
* at the following key:<br> * at the following key:<br>
* <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code> * <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code>
* *
* @param preferencesKey The preferences key to save at. Generally the * @param preferencesKey The preferences key to save at. Generally the
* application's unique name. * application's unique name.
* *
* @throws BackingStoreException If an exception occurs with the preferences * @throws BackingStoreException If an exception occurs with the preferences
*/ */
public void save(String preferencesKey) throws BackingStoreException { public void save(String preferencesKey) throws BackingStoreException {
@ -302,7 +321,7 @@ public final class AppSettings extends HashMap<String, Object> {
// purge any other parameters set in older versions of the app, so // purge any other parameters set in older versions of the app, so
// that they don't leak onto the AppSettings of newer versions. // that they don't leak onto the AppSettings of newer versions.
prefs.clear(); prefs.clear();
for (String key : keySet()) { for (String key : keySet()) {
Object val = get(key); Object val = get(key);
if (val instanceof Integer) { if (val instanceof Integer) {
@ -314,12 +333,12 @@ public final class AppSettings extends HashMap<String, Object> {
} else if (val instanceof Boolean) { } else if (val instanceof Boolean) {
prefs.putBoolean("B_" + key, (Boolean) val); prefs.putBoolean("B_" + key, (Boolean) val);
} }
// NOTE: Ignore any parameters of unsupported types instead // NOTE: Ignore any parameters of unsupported types instead
// of throwing exception. This is specifically for handling // of throwing exception. This is specifically for handling
// BufferedImage which is used in setIcons(), as you do not // BufferedImage which is used in setIcons(), as you do not
// want to export such data in the preferences. // want to export such data in the preferences.
} }
// Ensure the data is properly written into preferences before // Ensure the data is properly written into preferences before
// continuing. // continuing.
prefs.sync(); prefs.sync();
@ -366,7 +385,7 @@ public final class AppSettings extends HashMap<String, Object> {
return s; return s;
} }
/** /**
* Get a float from the settings. * Get a float from the settings.
* <p> * <p>
@ -401,51 +420,51 @@ public final class AppSettings extends HashMap<String, Object> {
public void putString(String key, String value) { public void putString(String key, String value) {
put(key, value); put(key, value);
} }
/** /**
* Set a float on the settings. * Set a float on the settings.
*/ */
public void putFloat(String key, float value) { public void putFloat(String key, float value) {
put(key, Float.valueOf(value)); put(key, Float.valueOf(value));
} }
/** /**
* Enable or disable mouse emulation on touchscreen based devices. * Enable or disable mouse emulation on touchscreen based devices.
* This will convert taps on the touchscreen or movement of finger * This will convert taps on the touchscreen or movement of finger
* over touchscreen (only the first) into the appropriate mouse events. * over touchscreen (only the first) into the appropriate mouse events.
* *
* @param emulateMouse If mouse emulation should be enabled. * @param emulateMouse If mouse emulation should be enabled.
*/ */
public void setEmulateMouse(boolean emulateMouse) { public void setEmulateMouse(boolean emulateMouse) {
putBoolean("TouchEmulateMouse", emulateMouse); putBoolean("TouchEmulateMouse", emulateMouse);
} }
/** /**
* Returns true if mouse emulation is enabled, false otherwise. * Returns true if mouse emulation is enabled, false otherwise.
* *
* @return Mouse emulation mode. * @return Mouse emulation mode.
*/ */
public boolean isEmulateMouse() { public boolean isEmulateMouse() {
return getBoolean("TouchEmulateMouse"); return getBoolean("TouchEmulateMouse");
} }
/** /**
* Specify if the X or Y (or both) axes should be flipped for emulated mouse. * Specify if the X or Y (or both) axes should be flipped for emulated mouse.
* *
* @param flipX Set to flip X axis * @param flipX Set to flip X axis
* @param flipY Set to flip Y axis * @param flipY Set to flip Y axis
* *
* @see #setEmulateMouse(boolean) * @see #setEmulateMouse(boolean)
*/ */
public void setEmulateMouseFlipAxis(boolean flipX, boolean flipY) { public void setEmulateMouseFlipAxis(boolean flipX, boolean flipY) {
putBoolean("TouchEmulateMouseFlipX", flipX); putBoolean("TouchEmulateMouseFlipX", flipX);
putBoolean("TouchEmulateMouseFlipY", flipY); putBoolean("TouchEmulateMouseFlipY", flipY);
} }
public boolean isEmulateMouseFlipX() { public boolean isEmulateMouseFlipX() {
return getBoolean("TouchEmulateMouseFlipX"); return getBoolean("TouchEmulateMouseFlipX");
} }
public boolean isEmulateMouseFlipY() { public boolean isEmulateMouseFlipY() {
return getBoolean("TouchEmulateMouseFlipY"); return getBoolean("TouchEmulateMouseFlipY");
} }
@ -484,7 +503,7 @@ public final class AppSettings extends HashMap<String, Object> {
* <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li> * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
* <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li> * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability</li>
* <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li> * <li>AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability</li>
* <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate * <li>AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate
* OpenGL version based on system capabilities</li> * OpenGL version based on system capabilities</li>
* <li>null - Disable graphics rendering</li> * <li>null - Disable graphics rendering</li>
* </ul> * </ul>
@ -496,7 +515,7 @@ public final class AppSettings extends HashMap<String, Object> {
} }
/** /**
* Set a custom graphics renderer to use. The class should implement * Set a custom graphics renderer to use. The class should implement
* the {@link JmeContext} interface. * the {@link JmeContext} interface.
* @param clazz The custom context class. * @param clazz The custom context class.
* (Default: not set) * (Default: not set)
@ -511,7 +530,7 @@ public final class AppSettings extends HashMap<String, Object> {
* <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li> * <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li>
* <li>null - Disable audio</li> * <li>null - Disable audio</li>
* </ul> * </ul>
* @param audioRenderer * @param audioRenderer
* (Default: LWJGL) * (Default: LWJGL)
*/ */
public void setAudioRenderer(String audioRenderer) { public void setAudioRenderer(String audioRenderer) {
@ -573,10 +592,10 @@ public final class AppSettings extends HashMap<String, Object> {
setMinHeight(height); setMinHeight(height);
} }
/** /**
* Set the frequency, also known as refresh rate, for the * Set the frequency, also known as refresh rate, for the
* rendering display. * rendering display.
* @param value The frequency * @param value The frequency
* (Default: 60) * (Default: 60)
@ -593,13 +612,13 @@ public final class AppSettings extends HashMap<String, Object> {
* 16 bits. On some platforms 24 bits might not be supported, in that case, * 16 bits. On some platforms 24 bits might not be supported, in that case,
* specify 16 bits.<p> * specify 16 bits.<p>
* (Default: 24) * (Default: 24)
* *
* @param value The depth bits * @param value The depth bits
*/ */
public void setDepthBits(int value){ public void setDepthBits(int value){
putInteger("DepthBits", value); putInteger("DepthBits", value);
} }
/** /**
* Set the number of stencil bits. * Set the number of stencil bits.
* <p> * <p>
@ -608,17 +627,17 @@ public final class AppSettings extends HashMap<String, Object> {
* the stencil buffer. * the stencil buffer.
* </p> * </p>
* (Default: 0) * (Default: 0)
* *
* @param value Number of stencil bits * @param value Number of stencil bits
*/ */
public void setStencilBits(int value){ public void setStencilBits(int value){
putInteger("StencilBits", value); putInteger("StencilBits", value);
} }
/** /**
* Set the bits per pixel for the display. Appropriate * Set the bits per pixel for the display. Appropriate
* values are 16 for RGB565 color format, or 24 for RGB8 color format. * values are 16 for RGB565 color format, or 24 for RGB8 color format.
* *
* @param value The bits per pixel to set * @param value The bits per pixel to set
* (Default: 24) * (Default: 24)
*/ */
@ -630,7 +649,7 @@ public final class AppSettings extends HashMap<String, Object> {
* Set the number of samples per pixel. A value of 1 indicates * Set the number of samples per pixel. A value of 1 indicates
* each pixel should be single-sampled, higher values indicate * each pixel should be single-sampled, higher values indicate
* a pixel should be multi-sampled. * a pixel should be multi-sampled.
* *
* @param value The number of samples * @param value The number of samples
* (Default: 1) * (Default: 1)
*/ */
@ -657,16 +676,16 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Set to true to enable vertical-synchronization, limiting and synchronizing * Set to true to enable vertical-synchronization, limiting and synchronizing
* every frame rendered to the monitor's refresh rate. * every frame rendered to the monitor's refresh rate.
* @param value * @param value
* (Default: false) * (Default: false)
*/ */
public void setVSync(boolean value) { public void setVSync(boolean value) {
putBoolean("VSync", value); putBoolean("VSync", value);
} }
/** /**
* Enable 3D stereo. * Enable 3D stereo.
* <p>This feature requires hardware support from the GPU driver. * <p>This feature requires hardware support from the GPU driver.
* @see <a href="http://en.wikipedia.org/wiki/Quad_buffering">http://en.wikipedia.org/wiki/Quad_buffering</a><br /> * @see <a href="http://en.wikipedia.org/wiki/Quad_buffering">http://en.wikipedia.org/wiki/Quad_buffering</a><br />
* Once enabled, filters or scene processors that handle 3D stereo rendering * Once enabled, filters or scene processors that handle 3D stereo rendering
* could use this feature to render using hardware 3D stereo.</p> * could use this feature to render using hardware 3D stereo.</p>
@ -693,16 +712,16 @@ public final class AppSettings extends HashMap<String, Object> {
public void setIcons(Object[] value) { public void setIcons(Object[] value) {
put("Icons", value); put("Icons", value);
} }
/** /**
* Sets the path of the settings dialog image to use. * Sets the path of the settings dialog image to use.
* <p> * <p>
* The image will be displayed in the settings dialog when the * The image will be displayed in the settings dialog when the
* application is started. * application is started.
* </p> * </p>
* (Default: /com/jme3/app/Monkey.png) * (Default: /com/jme3/app/Monkey.png)
* *
* @param path The path to the image in the classpath. * @param path The path to the image in the classpath.
*/ */
public void setSettingsDialogImage(String path) { public void setSettingsDialogImage(String path) {
putString("SettingsDialogImage", path); putString("SettingsDialogImage", path);
@ -710,7 +729,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the framerate. * Get the framerate.
* @see #setFrameRate(int) * @see #setFrameRate(int)
*/ */
public int getFrameRate() { public int getFrameRate() {
return getInteger("FrameRate"); return getInteger("FrameRate");
@ -718,7 +737,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the use input state. * Get the use input state.
* @see #setUseInput(boolean) * @see #setUseInput(boolean)
*/ */
public boolean useInput() { public boolean useInput() {
return getBoolean("UseInput"); return getBoolean("UseInput");
@ -726,7 +745,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the renderer * Get the renderer
* @see #setRenderer(java.lang.String) * @see #setRenderer(java.lang.String)
*/ */
public String getRenderer() { public String getRenderer() {
return getString("Renderer"); return getString("Renderer");
@ -734,7 +753,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the width * Get the width
* @see #setWidth(int) * @see #setWidth(int)
*/ */
public int getWidth() { public int getWidth() {
return getInteger("Width"); return getInteger("Width");
@ -742,7 +761,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the height * Get the height
* @see #setHeight(int) * @see #setHeight(int)
*/ */
public int getHeight() { public int getHeight() {
return getInteger("Height"); return getInteger("Height");
@ -750,7 +769,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the width * Get the width
* @see #setWidth(int) * @see #setWidth(int)
*/ */
public int getMinWidth() { public int getMinWidth() {
return getInteger("MinWidth"); return getInteger("MinWidth");
@ -758,7 +777,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the height * Get the height
* @see #setHeight(int) * @see #setHeight(int)
*/ */
public int getMinHeight() { public int getMinHeight() {
return getInteger("MinHeight"); return getInteger("MinHeight");
@ -766,7 +785,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the bits per pixel * Get the bits per pixel
* @see #setBitsPerPixel(int) * @see #setBitsPerPixel(int)
*/ */
public int getBitsPerPixel() { public int getBitsPerPixel() {
return getInteger("BitsPerPixel"); return getInteger("BitsPerPixel");
@ -774,7 +793,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the frequency * Get the frequency
* @see #setFrequency(int) * @see #setFrequency(int)
*/ */
public int getFrequency() { public int getFrequency() {
return getInteger("Frequency"); return getInteger("Frequency");
@ -790,7 +809,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the number of stencil bits * Get the number of stencil bits
* @see #setStencilBits(int) * @see #setStencilBits(int)
*/ */
public int getStencilBits() { public int getStencilBits() {
return getInteger("StencilBits"); return getInteger("StencilBits");
@ -798,7 +817,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the number of samples * Get the number of samples
* @see #setSamples(int) * @see #setSamples(int)
*/ */
public int getSamples() { public int getSamples() {
return getInteger("Samples"); return getInteger("Samples");
@ -806,7 +825,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the application title * Get the application title
* @see #setTitle(java.lang.String) * @see #setTitle(java.lang.String)
*/ */
public String getTitle() { public String getTitle() {
return getString("Title"); return getString("Title");
@ -814,7 +833,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the vsync state * Get the vsync state
* @see #setVSync(boolean) * @see #setVSync(boolean)
*/ */
public boolean isVSync() { public boolean isVSync() {
return getBoolean("VSync"); return getBoolean("VSync");
@ -822,7 +841,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the fullscreen state * Get the fullscreen state
* @see #setFullscreen(boolean) * @see #setFullscreen(boolean)
*/ */
public boolean isFullscreen() { public boolean isFullscreen() {
return getBoolean("Fullscreen"); return getBoolean("Fullscreen");
@ -830,7 +849,7 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the use joysticks state * Get the use joysticks state
* @see #setUseJoysticks(boolean) * @see #setUseJoysticks(boolean)
*/ */
public boolean useJoysticks() { public boolean useJoysticks() {
return !getBoolean("DisableJoysticks"); return !getBoolean("DisableJoysticks");
@ -838,31 +857,31 @@ public final class AppSettings extends HashMap<String, Object> {
/** /**
* Get the audio renderer * Get the audio renderer
* @see #setAudioRenderer(java.lang.String) * @see #setAudioRenderer(java.lang.String)
*/ */
public String getAudioRenderer() { public String getAudioRenderer() {
return getString("AudioRenderer"); return getString("AudioRenderer");
} }
/** /**
* Get the stereo 3D state * Get the stereo 3D state
* @see #setStereo3D(boolean) * @see #setStereo3D(boolean)
*/ */
public boolean useStereo3D(){ public boolean useStereo3D(){
return getBoolean("Stereo3D"); return getBoolean("Stereo3D");
} }
/** /**
* Get the icon array * Get the icon array
* @see #setIcons(java.lang.Object[]) * @see #setIcons(java.lang.Object[])
*/ */
public Object[] getIcons() { public Object[] getIcons() {
return (Object[]) get("Icons"); return (Object[]) get("Icons");
} }
/** /**
* Get the settings dialog image * Get the settings dialog image
* @see #setSettingsDialogImage(java.lang.String) * @see #setSettingsDialogImage(java.lang.String)
*/ */
public String getSettingsDialogImage() { public String getSettingsDialogImage() {
return getString("SettingsDialogImage"); return getString("SettingsDialogImage");

Loading…
Cancel
Save