2
0
Эх сурвалжийг харах

* Applied audio fix patch (thanks prich)
- Pause audio when jME3 app is paused
- Add WAV to supported formats
- Add Android audio test
* Minor javadoc fixes in AndroidHarness
* Made jme3tools.android.Fixed class deprecated

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9152 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Sha..rd 13 жил өмнө
parent
commit
b89b2428af

+ 41 - 36
engine/src/android/com/jme3/app/AndroidHarness.java

@@ -8,15 +8,13 @@ import android.graphics.drawable.Drawable;
 import android.graphics.drawable.NinePatchDrawable;
 import android.opengl.GLSurfaceView;
 import android.os.Bundle;
-import android.view.Display;
-import android.view.Gravity;
-import android.view.View;
 import android.view.ViewGroup.LayoutParams;
-import android.view.Window;
-import android.view.WindowManager;
+import android.view.*;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
+import com.jme3.audio.AudioRenderer;
+import com.jme3.audio.android.AndroidAudioRenderer;
 import com.jme3.input.android.AndroidInput;
 import com.jme3.input.controls.TouchListener;
 import com.jme3.input.event.TouchEvent;
@@ -26,7 +24,6 @@ import com.jme3.system.android.AndroidConfigChooser.ConfigType;
 import com.jme3.system.android.JmeAndroidSystem;
 import com.jme3.system.android.OGLESContext;
 import com.jme3.util.JmeFormatter;
-import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.logging.Handler;
@@ -43,76 +40,63 @@ import java.util.logging.Logger;
 public class AndroidHarness extends Activity implements TouchListener, DialogInterface.OnClickListener {
 
     protected final static Logger logger = Logger.getLogger(AndroidHarness.class.getName());
-    
     /**
      * The application class to start
      */
     protected String appClass = "jme3test.android.Test";
-    
     /**
      * The jme3 application object
      */
     protected Application app = null;
-    
     /**
      * ConfigType.FASTEST is RGB565, GLSurfaceView default ConfigType.BEST is
      * RGBA8888 or better if supported by the hardware
      */
     protected ConfigType eglConfigType = ConfigType.FASTEST;
-    
     /**
      * If true all valid and not valid egl configs are logged
      */
     protected boolean eglConfigVerboseLogging = false;
-    
     /**
      * If true MouseEvents are generated from TouchEvents
      */
     protected boolean mouseEventsEnabled = true;
-    
     /**
      * Flip X axis
      */
     protected boolean mouseEventsInvertX = true;
-    
     /**
      * Flip Y axis
      */
     protected boolean mouseEventsInvertY = true;
-    
     /**
      * Title of the exit dialog, default is "Do you want to exit?"
      */
     protected String exitDialogTitle = "Do you want to exit?";
-    
     /**
      * Message of the exit dialog, default is "Use your home key to bring this
      * app into the background or exit to terminate it."
      */
     protected String exitDialogMessage = "Use your home key to bring this app into the background or exit to terminate it.";
     /**
-     * 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 remains visible if 
-     * screenShowTitle is true while screenFullScreen is false, 
-     * then the title bar is also displayed under the notification bar.
+     * 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
+     * remains visible if screenShowTitle is true while screenFullScreen is
+     * false, then the title bar is also displayed under the notification bar.
      */
     protected boolean screenFullScreen = true;
-    
     /**
      * if screenShowTitle is true while screenFullScreen is false, then the
      * title bar is also displayed under the notification bar
      */
     protected boolean screenShowTitle = true;
-    
     /**
      * Splash Screen picture Resource ID. If a Splash Screen is desired, set
      * splashPicID to the value of the Resource ID (i.e. R.drawable.picname). If
      * splashPicID = 0, then no splash screen will be displayed.
      */
     protected int splashPicID = 0;
-    
     /**
      * Set the screen orientation, default is SENSOR
      * ActivityInfo.SCREEN_ORIENTATION_* constants package
@@ -216,6 +200,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
         if (app != null) {
             app.restart();
         }
+
         logger.info("onRestart");
     }
 
@@ -231,6 +216,14 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
         if (view != null) {
             view.onResume();
         }
+
+        //resume the audio
+        AudioRenderer result = app.getAudioRenderer();
+        if (result instanceof AndroidAudioRenderer) {
+            AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
+            renderer.resumeAll();
+        }
+
         isGLThreadPaused = false;
         logger.info("onResume");
     }
@@ -241,6 +234,15 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
         if (view != null) {
             view.onPause();
         }
+
+        //pause the audio
+        AudioRenderer result = app.getAudioRenderer();
+        logger.info("pause: " + result.getClass().getSimpleName());
+        if (result instanceof AndroidAudioRenderer) {
+            AndroidAudioRenderer renderer = (AndroidAudioRenderer) result;
+            renderer.pauseAll();
+        }
+
         isGLThreadPaused = true;
         logger.info("onPause");
     }
@@ -248,6 +250,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
     @Override
     protected void onStop() {
         super.onStop();
+
         logger.info("onStop");
     }
 
@@ -256,6 +259,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
         if (app != null) {
             app.stop(!isGLThreadPaused);
         }
+
         logger.info("onDestroy");
         super.onDestroy();
     }
@@ -265,35 +269,35 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
     }
 
     /**
-     * Called when an error has occurred. 
-     * By default, will show an error message to the user 
-     * and print the exception/error to the log.
+     * Called when an error has occurred. By default, will show an error message
+     * to the user and print the exception/error to the log.
      */
     public void handleError(final String errorMsg, final Throwable t) {
         String stackTrace = "";
         String title = "Error";
-        
-        if (t != null){
+
+        if (t != null) {
             // Convert exception to string
             StringWriter sw = new StringWriter(100);
             t.printStackTrace(new PrintWriter(sw));
             stackTrace = sw.toString();
             title = t.toString();
         }
-        
-        final String finalTitle = title; 
-        final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception") 
-                                + "\n" + stackTrace;
+
+        final String finalTitle = title;
+        final String finalMsg = (errorMsg != null ? errorMsg : "Uncaught Exception")
+                + "\n" + stackTrace;
 
         logger.log(Level.SEVERE, finalMsg);
 
         runOnUiThread(new Runnable() {
+
             @Override
             public void run() {
                 AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)
-                                        .setTitle(finalTitle)
-                                        .setPositiveButton("Kill", AndroidHarness.this)
-                                        .setMessage(finalMsg).create();
+                         .setTitle(finalTitle)
+                         .setPositiveButton("Kill", AndroidHarness.this)
+                         .setMessage(finalMsg).create();
                 dialog.show();
             }
         });
@@ -324,6 +328,7 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt
             switch (evt.getType()) {
                 case KEY_UP:
                     runOnUiThread(new Runnable() {
+
                         @Override
                         public void run() {
                             AlertDialog dialog = new AlertDialog.Builder(AndroidHarness.this) // .setIcon(R.drawable.alert_dialog_icon)

+ 115 - 115
engine/src/android/com/jme3/asset/AndroidAssetManager.java

@@ -1,115 +1,115 @@
-/*
- * Copyright (c) 2009-2010 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.asset;
-
-import com.jme3.asset.plugins.AndroidLocator;
-import com.jme3.asset.plugins.ClasspathLocator;
-import com.jme3.audio.plugins.AndroidAudioLoader;
-import com.jme3.texture.Texture;
-import com.jme3.texture.plugins.AndroidImageLoader;
-import java.net.URL;
-import java.util.logging.Logger;
-
-/**
- * <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android
- *
- * @author larynx
- */
-public class AndroidAssetManager extends DesktopAssetManager {
-
-    private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName());
-
-    public AndroidAssetManager() {
-        this(null);
-    }
-
-    @Deprecated
-    public AndroidAssetManager(boolean loadDefaults) {
-        //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));
-        this(null);
-    }
-
-    /**
-     * AndroidAssetManager constructor
-     * If URL == null then a default list of locators and loaders for android is set
-     * @param configFile
-     */
-    public AndroidAssetManager(URL configFile) {
-        System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
-
-        // Set Default Android config        	       
-        this.registerLocator("", AndroidLocator.class);
-        this.registerLocator("", ClasspathLocator.class);
-        this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");
-        this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3");
-        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");
-        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");
-        this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");
-        this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds");
-        this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm");
-        this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr");
-        this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga");
-        this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");
-        this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj");
-        this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl");
-        this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml");
-        this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml");
-        this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material");
-        this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene");
-        this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");
-
-        logger.info("AndroidAssetManager created.");
-    }
-
-    /**
-     * Loads a texture. 
-     *
-     * @return
-     */
-    @Override
-    public Texture loadTexture(TextureKey key) {
-        Texture tex = (Texture) loadAsset(key);
-
-        // XXX: This will improve performance on some really
-        // low end GPUs (e.g. ones with OpenGL ES 1 support only)
-        // but otherwise won't help on the higher ones. 
-        // Strongly consider removing this.
-        tex.setMagFilter(Texture.MagFilter.Nearest);
-        tex.setAnisotropicFilter(0);
-        if (tex.getMinFilter().usesMipMapLevels()) {
-            tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap);
-        } else {
-            tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
-        }
-        return tex;
-    }
-}
+/*
+ * Copyright (c) 2009-2010 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.asset;
+
+import com.jme3.asset.plugins.AndroidLocator;
+import com.jme3.asset.plugins.ClasspathLocator;
+import com.jme3.audio.plugins.AndroidAudioLoader;
+import com.jme3.texture.Texture;
+import com.jme3.texture.plugins.AndroidImageLoader;
+import java.net.URL;
+import java.util.logging.Logger;
+
+/**
+ * <code>AndroidAssetManager</code> is an implementation of DesktopAssetManager for Android
+ *
+ * @author larynx
+ */
+public class AndroidAssetManager extends DesktopAssetManager {
+
+    private static final Logger logger = Logger.getLogger(AndroidAssetManager.class.getName());
+
+    public AndroidAssetManager() {
+        this(null);
+    }
+
+    @Deprecated
+    public AndroidAssetManager(boolean loadDefaults) {
+        //this(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"));
+        this(null);
+    }
+
+    /**
+     * AndroidAssetManager constructor
+     * If URL == null then a default list of locators and loaders for android is set
+     * @param configFile
+     */
+    public AndroidAssetManager(URL configFile) {
+        System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
+
+        // Set Default Android config        	       
+        this.registerLocator("", AndroidLocator.class);
+        this.registerLocator("", ClasspathLocator.class);
+        this.registerLoader(AndroidImageLoader.class, "jpg", "bmp", "gif", "png", "jpeg");
+        this.registerLoader(AndroidAudioLoader.class, "ogg", "mp3", "wav");
+        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3m");
+        this.registerLoader(com.jme3.material.plugins.J3MLoader.class, "j3md");
+        this.registerLoader(com.jme3.font.plugins.BitmapFontLoader.class, "fnt");
+        this.registerLoader(com.jme3.texture.plugins.DDSLoader.class, "dds");
+        this.registerLoader(com.jme3.texture.plugins.PFMLoader.class, "pfm");
+        this.registerLoader(com.jme3.texture.plugins.HDRLoader.class, "hdr");
+        this.registerLoader(com.jme3.texture.plugins.TGALoader.class, "tga");
+        this.registerLoader(com.jme3.export.binary.BinaryImporter.class, "j3o");
+        this.registerLoader(com.jme3.scene.plugins.OBJLoader.class, "obj");
+        this.registerLoader(com.jme3.scene.plugins.MTLLoader.class, "mtl");
+        this.registerLoader(com.jme3.scene.plugins.ogre.MeshLoader.class, "meshxml", "mesh.xml");
+        this.registerLoader(com.jme3.scene.plugins.ogre.SkeletonLoader.class, "skeletonxml", "skeleton.xml");
+        this.registerLoader(com.jme3.scene.plugins.ogre.MaterialLoader.class, "material");
+        this.registerLoader(com.jme3.scene.plugins.ogre.SceneLoader.class, "scene");
+        this.registerLoader(com.jme3.shader.plugins.GLSLLoader.class, "vert", "frag", "glsl", "glsllib");
+
+        logger.info("AndroidAssetManager created.");
+    }
+
+    /**
+     * Loads a texture. 
+     *
+     * @return
+     */
+    @Override
+    public Texture loadTexture(TextureKey key) {
+        Texture tex = (Texture) loadAsset(key);
+
+        // XXX: This will improve performance on some really
+        // low end GPUs (e.g. ones with OpenGL ES 1 support only)
+        // but otherwise won't help on the higher ones. 
+        // Strongly consider removing this.
+        tex.setMagFilter(Texture.MagFilter.Nearest);
+        tex.setAnisotropicFilter(0);
+        if (tex.getMinFilter().usesMipMapLevels()) {
+            tex.setMinFilter(Texture.MinFilter.NearestNearestMipMap);
+        } else {
+            tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
+        }
+        return tex;
+    }
+}

+ 499 - 478
engine/src/android/com/jme3/audio/android/AndroidAudioRenderer.java

@@ -1,478 +1,499 @@
-/*
- * Copyright (c) 2009-2010 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 android.util.Log;
-
-import com.jme3.asset.AssetKey;
-import com.jme3.audio.AudioNode.Status;
-import com.jme3.audio.*;
-import com.jme3.math.FastMath;
-import com.jme3.math.Vector3f;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-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 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<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
-	private SoundPool soundPool = null;
-
-	private final Vector3f listenerPosition = new Vector3f();
-	// For temp use
-	private final Vector3f distanceVector = new Vector3f();
-	private final Context context;
-	private final AssetManager assetManager;
-	private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
-	private Listener listener;
-	private boolean audioDisabled = false;
-
-	private final AudioManager manager;
-
-	public AndroidAudioRenderer(Activity context) {
-		this.context = 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(AudioNode src, AudioParam param) {
-		// logger.log(Level.INFO, "updateSourceParam " + param);
-
-		if (audioDisabled) {
-			return;
-		}
-
-		if (src.getChannel() < 0) {
-			return;
-		}
-
-		switch (param) {
-		case Position:
-			if (!src.isPositional()) {
-				return;
-			}
-
-			Vector3f pos = src.getWorldTranslation();
-			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:
-
-			soundPool.setVolume(src.getChannel(), src.getVolume(),
-					src.getVolume());
-
-			break;
-		case Pitch:
-
-			break;
-		}
-
-	}
-
-	@Override
-	public void updateListenerParam(Listener listener, ListenerParam param) {
-		// logger.log(Level.INFO, "updateListenerParam " + 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 (AudioNode src : musicPlaying.keySet()) {
-
-			MediaPlayer mp = musicPlaying.get(src);
-			{
-				// Calc the distance to the listener
-				distanceVector.set(listenerPosition);
-				distanceVector.subtractLocal(src.getLocalTranslation());
-				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 (AudioNode src : musicPlaying.keySet()) {
-			MediaPlayer mp = musicPlaying.get(src);
-			{
-				mp.stop();
-				mp.release();
-				src.setStatus(Status.Stopped);
-			}
-		}
-		musicPlaying.clear();
-	}
-
-	@Override
-	public void onCompletion(MediaPlayer mp) {
-		mp.seekTo(0);
-		mp.stop();
-		// XXX: This has bad performance -> maybe change overall structure of
-		// mediaplayer in this audiorenderer?
-		for (AudioNode 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(AudioNode 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 {
-				src.setChannel(channel); // receive a channel at the last
-											// 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) {
-		AudioNode 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();
-
-		if (status == 0) // load was successfull
-		{
-			int channelIndex;
-			channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
-			src.setChannel(channelIndex);
-		}
-	}
-
-	public void playSource(AudioNode 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 {
-			AssetKey<?> key = audioData.getAssetKey();
-
-			AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
-			mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
-					afd.getLength());
-			mp.prepare();
-			mp.setLooping(src.isLooping());
-			mp.start();
-			src.setChannel(0);
-			src.setStatus(Status.Playing);
-			musicPlaying.put(src, mp);
-
-		} catch (IllegalStateException e) {
-			e.printStackTrace();
-		} catch (Exception e) {
-			e.printStackTrace();
-		}
-	}
-
-	public void pauseSource(AudioNode 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(AudioNode 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();
-			src.setStatus(Status.Paused);
-		} 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 (AudioNode 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) {
-	}
-}
+/*
+ * Copyright (c) 2009-2010 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 android.util.Log;
+
+import com.jme3.asset.AssetKey;
+import com.jme3.audio.AudioNode.Status;
+import com.jme3.audio.*;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+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 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<AudioNode, MediaPlayer> musicPlaying = new HashMap<AudioNode, MediaPlayer>();
+	private SoundPool soundPool = null;
+
+	private final Vector3f listenerPosition = new Vector3f();
+	// For temp use
+	private final Vector3f distanceVector = new Vector3f();
+	private final Context context;
+	private final AssetManager assetManager;
+	private HashMap<Integer, AudioNode> soundpoolStillLoading = new HashMap<Integer, AudioNode>();
+	private Listener listener;
+	private boolean audioDisabled = false;
+
+	private final AudioManager manager;
+
+	public AndroidAudioRenderer(Activity context) {
+		this.context = 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(AudioNode src, AudioParam param) {
+		// logger.log(Level.INFO, "updateSourceParam " + param);
+
+		if (audioDisabled) {
+			return;
+		}
+
+		if (src.getChannel() < 0) {
+			return;
+		}
+
+		switch (param) {
+		case Position:
+			if (!src.isPositional()) {
+				return;
+			}
+
+			Vector3f pos = src.getWorldTranslation();
+			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:
+
+			soundPool.setVolume(src.getChannel(), src.getVolume(),
+					src.getVolume());
+
+			break;
+		case Pitch:
+
+			break;
+		}
+
+	}
+
+	@Override
+	public void updateListenerParam(Listener listener, ListenerParam param) {
+		// logger.log(Level.INFO, "updateListenerParam " + 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 (AudioNode src : musicPlaying.keySet()) {
+
+			MediaPlayer mp = musicPlaying.get(src);
+			{
+				// Calc the distance to the listener
+				distanceVector.set(listenerPosition);
+				distanceVector.subtractLocal(src.getLocalTranslation());
+				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 (AudioNode src : musicPlaying.keySet()) {
+			MediaPlayer mp = musicPlaying.get(src);
+			{
+				mp.stop();
+				mp.release();
+				src.setStatus(Status.Stopped);
+			}
+		}
+		musicPlaying.clear();
+	}
+
+	@Override
+	public void onCompletion(MediaPlayer mp) {
+		mp.seekTo(0);
+		mp.stop();
+		// XXX: This has bad performance -> maybe change overall structure of
+		// mediaplayer in this audiorenderer?
+		for (AudioNode 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(AudioNode 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 {
+				src.setChannel(channel); // receive a channel at the last
+											// 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) {
+		AudioNode 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();
+
+		if (status == 0) // load was successfull
+		{
+			int channelIndex;
+			channelIndex = soundPool.play(audioData.getId(), 1f, 1f, 1, 0, 1f);
+			src.setChannel(channelIndex);
+		}
+	}
+
+	public void playSource(AudioNode 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 {
+			AssetKey<?> key = audioData.getAssetKey();
+
+			AssetFileDescriptor afd = assetManager.openFd(key.getName()); // assetKey.getName()
+			mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+					afd.getLength());
+			mp.prepare();
+			mp.setLooping(src.isLooping());
+			mp.start();
+			src.setChannel(0);
+			src.setStatus(Status.Playing);
+			musicPlaying.put(src, mp);
+
+		} catch (IllegalStateException e) {
+			e.printStackTrace();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Pause the current playing sounds. Both from the {@link SoundPool} and the
+	 * active {@link MediaPlayer}s
+	 */
+	public void pauseAll() {
+		soundPool.autoPause();
+		for (MediaPlayer mp : musicPlaying.values()) {
+			mp.pause();
+		}
+	}
+	
+	/**
+	 * Resume all paused sounds.
+	 */
+	public void resumeAll() {
+		soundPool.autoResume();
+		for (MediaPlayer mp : musicPlaying.values()) {
+			mp.start(); //no resume -> api says call start to resume
+		}
+	}
+
+	public void pauseSource(AudioNode 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(AudioNode 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();
+			src.setStatus(Status.Paused);
+		} 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 (AudioNode 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) {
+	}
+}

+ 40 - 0
engine/src/android/jme3test/android/SimpleSoundTest.java

@@ -0,0 +1,40 @@
+package jme3test.android;
+
+import com.jme3.app.SimpleApplication;
+import com.jme3.audio.AudioNode;
+import com.jme3.input.MouseInput;
+import com.jme3.input.controls.InputListener;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.math.Vector3f;
+
+public class SimpleSoundTest extends SimpleApplication implements InputListener {
+
+    private AudioNode gun;
+    private AudioNode nature;
+
+    @Override
+    public void simpleInitApp() {
+        gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav");
+        gun.setPositional(true);
+        gun.setLocalTranslation(new Vector3f(0, 0, 0));
+        gun.setMaxDistance(100);
+        gun.setRefDistance(5);
+
+        nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true);
+        nature.setVolume(3);
+        nature.setLooping(true);
+        nature.play();
+
+        inputManager.addMapping("click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
+        inputManager.addListener(this, "click");
+
+        rootNode.attachChild(gun);
+        rootNode.attachChild(nature);
+    }
+
+    public void onAction(String name, boolean isPressed, float tpf) {
+        if (name.equals("click") && isPressed) {
+            gun.playInstance();
+        }
+    }
+}

+ 4 - 0
engine/src/android/jme3tools/android/Fixed.java

@@ -13,7 +13,11 @@ import java.util.Random;
  *
  *	@version 1.0
  *	@author CW
+ * 
+ * @deprecated Most devices with OpenGL ES 2.0 have an FPU. Please use
+ * floats instead of this class for decimal math.
  */
+@Deprecated
 public final class Fixed {
 
     /**