Ver código fonte

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
iwg..om 12 anos atrás
pai
commit
e727928731

+ 16 - 10
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.
+     * <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
      * 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) {

+ 22 - 9
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<? 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
      * @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.");
     }
-    
+
 }

+ 11 - 510
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<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;
-                }
-            }
-
-    }
+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();
 }

+ 523 - 0
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<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) {
+    }
+}

+ 1 - 1
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();

+ 31 - 4
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;
+    }
 }

+ 115 - 96
engine/src/core/com/jme3/system/AppSettings.java

@@ -42,69 +42,88 @@ import java.util.prefs.Preferences;
 
 /**
  * <code>AppSettings</code> provides a store of configuration
- * to be used by the application. 
+ * to be used by the application.
  * <p>
  * 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<String, Object> {
 
     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.
      * <p>
      * 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.
      * <p>
      * 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.
      * <p>
      * 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.
      * <p>
      * 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.
+     * <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 {
         defaults.put("Width", 640);
         defaults.put("Height", 480);
@@ -131,10 +150,10 @@ public final class AppSettings extends HashMap<String, Object> {
      * Create a new instance of <code>AppSettings</code>.
      * <p>
      * 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
      * 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<String, Object> {
 
     /**
      * Copies all settings from <code>other</code> to <code>this</code>
-     * AppSettings. 
+     * AppSettings.
      * <p>
      * 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<String, Object> {
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
      * On the Windows operating system, the preferences are saved in the registry
      * at the following key:<br>
      * <code>HKEY_CURRENT_USER\Software\JavaSoft\Prefs\[preferencesKey]</code>
-     * 
-     * @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<String, Object> {
         // 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<String, Object> {
             } 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<String, Object> {
 
         return s;
     }
-    
+
     /**
      * Get a float from the settings.
      * <p>
@@ -401,51 +420,51 @@ public final class AppSettings extends HashMap<String, Object> {
     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<String, Object> {
      * <li>AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability</li>
      * <li>AppSettings.LWJGL_OPENGL2 - Force OpenGL2 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>
      * <li>null - Disable graphics rendering</li>
      * </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.
      * @param clazz The custom context class.
      * (Default: not set)
@@ -511,7 +530,7 @@ public final class AppSettings extends HashMap<String, Object> {
      * <li>AppSettings.LWJGL_OPENAL - Default for LWJGL</li>
      * <li>null - Disable audio</li>
      * </ul>
-     * @param audioRenderer 
+     * @param audioRenderer
      * (Default: LWJGL)
      */
     public void setAudioRenderer(String audioRenderer) {
@@ -573,10 +592,10 @@ public final class AppSettings extends HashMap<String, Object> {
         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<String, Object> {
      * 16 bits. On some platforms 24 bits might not be supported, in that case,
      * specify 16 bits.<p>
      * (Default: 24)
-     * 
+     *
      * @param value The depth bits
      */
     public void setDepthBits(int value){
         putInteger("DepthBits", value);
     }
-    
+
     /**
      * Set the number of stencil bits.
      * <p>
@@ -608,17 +627,17 @@ public final class AppSettings extends HashMap<String, Object> {
      * the stencil buffer.
      * </p>
      * (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<String, Object> {
      * 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<String, Object> {
     /**
      * 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.
-     * <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 />
      * Once enabled, filters or scene processors that handle 3D stereo rendering
      * 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) {
         put("Icons", value);
     }
-    
+
     /**
      * Sets the path of the settings dialog image to use.
      * <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.
      * </p>
      * (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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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<String, Object> {
 
     /**
      * 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");