From e727928731d99ff6258da15127f9c1997f510077 Mon Sep 17 00:00:00 2001 From: "iwg..om" Date: Wed, 15 May 2013 21:44:21 +0000 Subject: [PATCH] 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 --- .../android/com/jme3/app/AndroidHarness.java | 26 +- .../com/jme3/asset/AndroidAssetManager.java | 31 +- .../audio/android/AndroidAudioRenderer.java | 521 +---------------- .../AndroidMediaPlayerAudioRenderer.java | 523 ++++++++++++++++++ .../AndroidOpenALSoftAudioRenderer.java | 2 +- .../jme3/system/android/JmeAndroidSystem.java | 35 +- .../src/core/com/jme3/system/AppSettings.java | 211 +++---- 7 files changed, 719 insertions(+), 630 deletions(-) create mode 100644 engine/src/android/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java diff --git a/engine/src/android/com/jme3/app/AndroidHarness.java b/engine/src/android/com/jme3/app/AndroidHarness.java index a7a116bb3..e0cf3ad96 100644 --- a/engine/src/android/com/jme3/app/AndroidHarness.java +++ b/engine/src/android/com/jme3/app/AndroidHarness.java @@ -15,7 +15,6 @@ import android.widget.ImageView; import android.widget.TextView; import com.jme3.audio.AudioRenderer; import com.jme3.audio.android.AndroidAudioRenderer; -import com.jme3.audio.android.AndroidOpenALSoftAudioRenderer; import com.jme3.input.JoyInput; import com.jme3.input.TouchInput; 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. */ protected int antiAliasingSamples = 0; + + /** + * Sets the type of Audio Renderer to be used. + *

+ * Android MediaPlayer / SoundPool is the default and can be used on all + * supported Android platform versions (2.2+)
+ * OpenAL Soft uses an OpenSL backend and is only supported on Android + * versions 2.3+. + *

+ * 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 * 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 * 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 * 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.setResolution(disp.getWidth(), disp.getHeight()); settings.put(AndroidConfigChooser.SETTINGS_CONFIG_TYPE, eglConfigType); + settings.setAudioRenderer(audioRendererType); // Create application instance @@ -487,10 +501,6 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; renderer.resumeAll(); } - if (result instanceof AndroidOpenALSoftAudioRenderer) { - AndroidOpenALSoftAudioRenderer renderer = (AndroidOpenALSoftAudioRenderer) result; - renderer.resumeAll(); - } } //resume the sensors (aka joysticks) if (app.getContext() != null) { @@ -530,10 +540,6 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt AndroidAudioRenderer renderer = (AndroidAudioRenderer) result; renderer.pauseAll(); } - if (result instanceof AndroidOpenALSoftAudioRenderer) { - AndroidOpenALSoftAudioRenderer renderer = (AndroidOpenALSoftAudioRenderer) result; - renderer.pauseAll(); - } } //pause the sensors (aka joysticks) if (app.getContext() != null) { diff --git a/engine/src/android/com/jme3/asset/AndroidAssetManager.java b/engine/src/android/com/jme3/asset/AndroidAssetManager.java index 6b6e782b5..5d58dd421 100644 --- a/engine/src/android/com/jme3/asset/AndroidAssetManager.java +++ b/engine/src/android/com/jme3/asset/AndroidAssetManager.java @@ -33,8 +33,11 @@ package com.jme3.asset; import com.jme3.asset.plugins.AndroidLocator; import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.audio.android.AndroidAudioRenderer; 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 java.net.URL; 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(null); } - + private void registerLoaderSafe(String loaderClass, String ... extensions) { try { Class loader = (Class) 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 * @param configFile */ - public AndroidAssetManager(URL configFile) { + public AndroidAssetManager(URL configFile) { System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver"); - // Set Default Android config + // Set Default Android config registerLocator("", AndroidLocator.class); registerLocator("", ClasspathLocator.class); - + 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, "j3md"); registerLoader(com.jme3.material.plugins.ShaderNodeDefinitionLoader.class, "j3sn"); registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib"); registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o"); registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt"); - + // Less common loaders (especially on Android) registerLoaderSafe("com.jme3.texture.plugins.DDSLoader", "dds"); 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.MaterialLoader", "material"); registerLoaderSafe("com.jme3.scene.plugins.ogre.SceneLoader", "scene"); - + logger.fine("AndroidAssetManager created."); } - + } diff --git a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java index ae7e309d5..0cde16aa5 100644 --- a/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java +++ b/engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java @@ -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; -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; +import com.jme3.audio.AudioRenderer; /** - * 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 plan_rich + * @author iwgeric */ -public class AndroidAudioRenderer implements 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 musicPlaying = new HashMap(); - 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 soundpoolStillLoading = new HashMap(); - 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; - } - } - - } +public interface AndroidAudioRenderer extends AudioRenderer { /** - * 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. + * Pauses all Playing audio. To be used when the app is placed in the + * background. */ - 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()); - } + public void pauseAll(); /** - * Pause the current playing sounds. Both from the {@link SoundPool} and the - * active {@link MediaPlayer}s + * Resumes all Paused audio. To be used when the app is brought back to + * the foreground. */ - 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) { - } + public void resumeAll(); } diff --git a/engine/src/android/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java new file mode 100644 index 000000000..df1bea3b6 --- /dev/null +++ b/engine/src/android/com/jme3/audio/android/AndroidMediaPlayerAudioRenderer.java @@ -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 musicPlaying = new HashMap(); + 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 soundpoolStillLoading = new HashMap(); + 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) { + } +} diff --git a/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java b/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java index 6b880a7e9..1a0672033 100644 --- a/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java +++ b/engine/src/android/com/jme3/audio/android/AndroidOpenALSoftAudioRenderer.java @@ -44,7 +44,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; 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 final NativeObjectManager objManager = new NativeObjectManager(); diff --git a/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java index 40318040b..ab4f8665c 100644 --- a/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java +++ b/engine/src/android/com/jme3/system/android/JmeAndroidSystem.java @@ -10,25 +10,25 @@ import com.jme3.asset.AndroidImageInfo; import com.jme3.asset.AssetManager; import com.jme3.audio.AudioRenderer; 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.JmeContext.Type; import com.jme3.texture.Image; import com.jme3.texture.image.DefaultImageRaster; import com.jme3.texture.image.ImageRaster; import com.jme3.util.AndroidScreenshots; -import com.jme3.util.JmeFormatter; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.nio.ByteBuffer; -import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.Logger; public class JmeAndroidSystem extends JmeSystemDelegate { private static Activity activity; + private static String audioRendererType = AppSettings.ANDROID_MEDIAPLAYER; static { try { @@ -97,6 +97,16 @@ public class JmeAndroidSystem extends JmeSystemDelegate { @Override 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); JmeContext ctx = new OGLESContext(); ctx.setSettings(settings); @@ -105,7 +115,20 @@ public class JmeAndroidSystem extends JmeSystemDelegate { @Override 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 @@ -198,4 +221,8 @@ public class JmeAndroidSystem extends JmeSystemDelegate { public static Activity getActivity() { return activity; } + + public static String getAudioRendererType() { + return audioRendererType; + } } diff --git a/engine/src/core/com/jme3/system/AppSettings.java b/engine/src/core/com/jme3/system/AppSettings.java index 5699e749e..81ca315ee 100644 --- a/engine/src/core/com/jme3/system/AppSettings.java +++ b/engine/src/core/com/jme3/system/AppSettings.java @@ -42,69 +42,88 @@ import java.util.prefs.Preferences; /** * AppSettings provides a store of configuration - * to be used by the application. + * to be used by the application. *

* By default only the {@link JmeContext context} uses the configuration, - * however the user may set and retrieve the settings as well. - * The settings can be stored either in the Java preferences + * however the user may set and retrieve the settings as well. + * The settings can be stored either in the Java preferences * (using {@link #save(java.lang.String) } or * a .properties file (using {@link #save(java.io.OutputStream) }. - * + * * @author Kirill Vainer */ public final class AppSettings extends HashMap { private static final AppSettings defaults = new AppSettings(false); - + /** * 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"; - + /** * Use LWJGL as the display system and force using the OpenGL2.0 renderer. *

* If the underlying system does not support OpenGL2.0, then the context * 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"; - + /** * Use LWJGL as the display system and force using the core OpenGL3.3 renderer. *

* If the underlying system does not support OpenGL3.2, then the context * 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. *

* 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"; - + /** - * 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. *

* If the GPU supports OpenGL2 or later, then the OpenGL2.0 renderer will * 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"; - + /** * 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"; + /** + * Use the Android MediaPlayer / SoundPool based renderer for Android audio capabilities. + *

+ * 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. + *

+ * 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 { defaults.put("Width", 640); defaults.put("Height", 480); @@ -131,10 +150,10 @@ public final class AppSettings extends HashMap { * Create a new instance of AppSettings. *

* If loadDefaults 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 * application to load settings from previous launches. - * + * * @param loadDefaults If default settings are to be loaded. */ public AppSettings(boolean loadDefaults) { @@ -145,11 +164,11 @@ public final class AppSettings extends HashMap { /** * Copies all settings from other to this - * AppSettings. + * AppSettings. *

* Any settings that are specified in other will overwrite settings * set on this AppSettings. - * + * * @param other The AppSettings to copy the settings from */ public void copyFrom(AppSettings other) { @@ -159,7 +178,7 @@ public final class AppSettings extends HashMap { /** * Same as {@link #copyFrom(com.jme3.system.AppSettings) }, except * doesn't overwrite settings that are already set. - * + * * @param other The AppSettings to merge the settings from */ public void mergeFrom(AppSettings other) { @@ -172,11 +191,11 @@ public final class AppSettings extends HashMap { /** * Loads the settings from the given properties input stream. - * + * * @param in The InputStream to load from * @throws IOException If an IOException occurs - * - * @see #save(java.io.OutputStream) + * + * @see #save(java.io.OutputStream) */ public void load(InputStream in) throws IOException { Properties props = new Properties(); @@ -207,11 +226,11 @@ public final class AppSettings extends HashMap { /** * Saves all settings to the given properties output stream. - * + * * @param out The OutputStream to write to * @throws IOException If an IOException occurs - * - * @see #load(java.io.InputStream) + * + * @see #load(java.io.InputStream) */ public void save(OutputStream out) throws IOException { Properties props = new Properties(); @@ -238,11 +257,11 @@ public final class AppSettings extends HashMap { /** * Loads settings previously saved in the Java preferences. - * + * * @param preferencesKey The preferencesKey previously used to save the settings. * @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 { Preferences prefs = Preferences.userRoot().node(preferencesKey); @@ -289,10 +308,10 @@ public final class AppSettings extends HashMap { * On the Windows operating system, the preferences are saved in the registry * at the following key:
* HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey] - * - * @param preferencesKey The preferences key to save at. Generally the - * application's unique name. - * + * + * @param preferencesKey The preferences key to save at. Generally the + * application's unique name. + * * @throws BackingStoreException If an exception occurs with the preferences */ public void save(String preferencesKey) throws BackingStoreException { @@ -302,7 +321,7 @@ public final class AppSettings extends HashMap { // purge any other parameters set in older versions of the app, so // that they don't leak onto the AppSettings of newer versions. prefs.clear(); - + for (String key : keySet()) { Object val = get(key); if (val instanceof Integer) { @@ -314,12 +333,12 @@ public final class AppSettings extends HashMap { } else if (val instanceof Boolean) { prefs.putBoolean("B_" + key, (Boolean) val); } - // NOTE: Ignore any parameters of unsupported types instead - // of throwing exception. This is specifically for handling + // NOTE: Ignore any parameters of unsupported types instead + // of throwing exception. This is specifically for handling // BufferedImage which is used in setIcons(), as you do not // want to export such data in the preferences. } - + // Ensure the data is properly written into preferences before // continuing. prefs.sync(); @@ -366,7 +385,7 @@ public final class AppSettings extends HashMap { return s; } - + /** * Get a float from the settings. *

@@ -401,51 +420,51 @@ public final class AppSettings extends HashMap { public void putString(String key, String value) { put(key, value); } - + /** * Set a float on the settings. */ public void putFloat(String key, float value) { put(key, Float.valueOf(value)); } - + /** * Enable or disable mouse emulation on touchscreen based devices. * This will convert taps on the touchscreen or movement of finger * over touchscreen (only the first) into the appropriate mouse events. - * + * * @param emulateMouse If mouse emulation should be enabled. */ public void setEmulateMouse(boolean emulateMouse) { putBoolean("TouchEmulateMouse", emulateMouse); } - + /** * Returns true if mouse emulation is enabled, false otherwise. - * + * * @return Mouse emulation mode. */ public boolean isEmulateMouse() { return getBoolean("TouchEmulateMouse"); } - + /** * Specify if the X or Y (or both) axes should be flipped for emulated mouse. - * + * * @param flipX Set to flip X axis * @param flipY Set to flip Y axis - * - * @see #setEmulateMouse(boolean) + * + * @see #setEmulateMouse(boolean) */ public void setEmulateMouseFlipAxis(boolean flipX, boolean flipY) { putBoolean("TouchEmulateMouseFlipX", flipX); putBoolean("TouchEmulateMouseFlipY", flipY); } - + public boolean isEmulateMouseFlipX() { return getBoolean("TouchEmulateMouseFlipX"); } - + public boolean isEmulateMouseFlipY() { return getBoolean("TouchEmulateMouseFlipY"); } @@ -484,7 +503,7 @@ public final class AppSettings extends HashMap { *

  • AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability
  • *
  • AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability
  • *
  • AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability
  • - *
  • AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate + *
  • AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate * OpenGL version based on system capabilities
  • *
  • null - Disable graphics rendering
  • * @@ -496,7 +515,7 @@ public final class AppSettings extends HashMap { } /** - * 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. * @param clazz The custom context class. * (Default: not set) @@ -511,7 +530,7 @@ public final class AppSettings extends HashMap { *
  • AppSettings.LWJGL_OPENAL - Default for LWJGL
  • *
  • null - Disable audio
  • * - * @param audioRenderer + * @param audioRenderer * (Default: LWJGL) */ public void setAudioRenderer(String audioRenderer) { @@ -573,10 +592,10 @@ public final class AppSettings extends HashMap { setMinHeight(height); } - - + + /** - * Set the frequency, also known as refresh rate, for the + * Set the frequency, also known as refresh rate, for the * rendering display. * @param value The frequency * (Default: 60) @@ -593,13 +612,13 @@ public final class AppSettings extends HashMap { * 16 bits. On some platforms 24 bits might not be supported, in that case, * specify 16 bits.

    * (Default: 24) - * + * * @param value The depth bits */ public void setDepthBits(int value){ putInteger("DepthBits", value); } - + /** * Set the number of stencil bits. *

    @@ -608,17 +627,17 @@ public final class AppSettings extends HashMap { * the stencil buffer. *

    * (Default: 0) - * + * * @param value Number of stencil bits */ public void setStencilBits(int value){ putInteger("StencilBits", value); } - + /** * Set the bits per pixel for the display. Appropriate * values are 16 for RGB565 color format, or 24 for RGB8 color format. - * + * * @param value The bits per pixel to set * (Default: 24) */ @@ -630,7 +649,7 @@ public final class AppSettings extends HashMap { * Set the number of samples per pixel. A value of 1 indicates * each pixel should be single-sampled, higher values indicate * a pixel should be multi-sampled. - * + * * @param value The number of samples * (Default: 1) */ @@ -657,16 +676,16 @@ public final class AppSettings extends HashMap { /** * Set to true to enable vertical-synchronization, limiting and synchronizing * every frame rendered to the monitor's refresh rate. - * @param value + * @param value * (Default: false) */ public void setVSync(boolean value) { putBoolean("VSync", value); } - + /** * Enable 3D stereo. - *

    This feature requires hardware support from the GPU driver. + *

    This feature requires hardware support from the GPU driver. * @see http://en.wikipedia.org/wiki/Quad_buffering
    * Once enabled, filters or scene processors that handle 3D stereo rendering * could use this feature to render using hardware 3D stereo.

    @@ -693,16 +712,16 @@ public final class AppSettings extends HashMap { public void setIcons(Object[] value) { put("Icons", value); } - + /** * Sets the path of the settings dialog image to use. *

    - * 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. *

    * (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) { putString("SettingsDialogImage", path); @@ -710,7 +729,7 @@ public final class AppSettings extends HashMap { /** * Get the framerate. - * @see #setFrameRate(int) + * @see #setFrameRate(int) */ public int getFrameRate() { return getInteger("FrameRate"); @@ -718,7 +737,7 @@ public final class AppSettings extends HashMap { /** * Get the use input state. - * @see #setUseInput(boolean) + * @see #setUseInput(boolean) */ public boolean useInput() { return getBoolean("UseInput"); @@ -726,7 +745,7 @@ public final class AppSettings extends HashMap { /** * Get the renderer - * @see #setRenderer(java.lang.String) + * @see #setRenderer(java.lang.String) */ public String getRenderer() { return getString("Renderer"); @@ -734,7 +753,7 @@ public final class AppSettings extends HashMap { /** * Get the width - * @see #setWidth(int) + * @see #setWidth(int) */ public int getWidth() { return getInteger("Width"); @@ -742,7 +761,7 @@ public final class AppSettings extends HashMap { /** * Get the height - * @see #setHeight(int) + * @see #setHeight(int) */ public int getHeight() { return getInteger("Height"); @@ -750,7 +769,7 @@ public final class AppSettings extends HashMap { /** * Get the width - * @see #setWidth(int) + * @see #setWidth(int) */ public int getMinWidth() { return getInteger("MinWidth"); @@ -758,7 +777,7 @@ public final class AppSettings extends HashMap { /** * Get the height - * @see #setHeight(int) + * @see #setHeight(int) */ public int getMinHeight() { return getInteger("MinHeight"); @@ -766,7 +785,7 @@ public final class AppSettings extends HashMap { /** * Get the bits per pixel - * @see #setBitsPerPixel(int) + * @see #setBitsPerPixel(int) */ public int getBitsPerPixel() { return getInteger("BitsPerPixel"); @@ -774,7 +793,7 @@ public final class AppSettings extends HashMap { /** * Get the frequency - * @see #setFrequency(int) + * @see #setFrequency(int) */ public int getFrequency() { return getInteger("Frequency"); @@ -790,7 +809,7 @@ public final class AppSettings extends HashMap { /** * Get the number of stencil bits - * @see #setStencilBits(int) + * @see #setStencilBits(int) */ public int getStencilBits() { return getInteger("StencilBits"); @@ -798,7 +817,7 @@ public final class AppSettings extends HashMap { /** * Get the number of samples - * @see #setSamples(int) + * @see #setSamples(int) */ public int getSamples() { return getInteger("Samples"); @@ -806,7 +825,7 @@ public final class AppSettings extends HashMap { /** * Get the application title - * @see #setTitle(java.lang.String) + * @see #setTitle(java.lang.String) */ public String getTitle() { return getString("Title"); @@ -814,7 +833,7 @@ public final class AppSettings extends HashMap { /** * Get the vsync state - * @see #setVSync(boolean) + * @see #setVSync(boolean) */ public boolean isVSync() { return getBoolean("VSync"); @@ -822,7 +841,7 @@ public final class AppSettings extends HashMap { /** * Get the fullscreen state - * @see #setFullscreen(boolean) + * @see #setFullscreen(boolean) */ public boolean isFullscreen() { return getBoolean("Fullscreen"); @@ -830,7 +849,7 @@ public final class AppSettings extends HashMap { /** * Get the use joysticks state - * @see #setUseJoysticks(boolean) + * @see #setUseJoysticks(boolean) */ public boolean useJoysticks() { return !getBoolean("DisableJoysticks"); @@ -838,31 +857,31 @@ public final class AppSettings extends HashMap { /** * Get the audio renderer - * @see #setAudioRenderer(java.lang.String) + * @see #setAudioRenderer(java.lang.String) */ public String getAudioRenderer() { return getString("AudioRenderer"); } - + /** * Get the stereo 3D state - * @see #setStereo3D(boolean) + * @see #setStereo3D(boolean) */ public boolean useStereo3D(){ - return getBoolean("Stereo3D"); + return getBoolean("Stereo3D"); } /** * Get the icon array - * @see #setIcons(java.lang.Object[]) + * @see #setIcons(java.lang.Object[]) */ public Object[] getIcons() { return (Object[]) get("Icons"); } - + /** * Get the settings dialog image - * @see #setSettingsDialogImage(java.lang.String) + * @see #setSettingsDialogImage(java.lang.String) */ public String getSettingsDialogImage() { return getString("SettingsDialogImage");