/** * Copyright 2008 - 2015 The Loon Game Engine Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. * * @project loon * @author cping * @email:javachenpeng@yahoo.com * @version 0.5 */ package loon.android; import java.io.FileDescriptor; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import android.content.res.AssetFileDescriptor; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnErrorListener; import android.media.SoundPool; import android.util.Log; import loon.LGame; import loon.LSystem; import loon.SoundImpl; import loon.event.Updateable; import loon.media.SoundOpenAlBuffer; import loon.media.SoundOpenAlEnv; import loon.media.SoundOpenAlSource; import loon.utils.StringUtils; public class AndroidAudio { protected static <I> void dispatchLoaded(final SoundImpl<I> sound, final I impl) { Updateable update = new Updateable() { @Override public void action(Object a) { sound.onLoaded(impl); } }; LSystem.unload(update); } protected static <I> void dispatchLoadError(final SoundImpl<I> sound, final Throwable error) { Updateable update = new Updateable() { @Override public void action(Object a) { sound.onLoadError(error); } }; LSystem.unload(update); } interface Resolver<I> { void resolve(AndroidSound<I> sound); } private final HashSet<AndroidSound<?>> playing = new HashSet<AndroidSound<?>>(); private final HashMap<String, OpenALSound> loadingOpenAlSounds = new HashMap<String, OpenALSound>(); private final HashMap<Integer, PooledSound> loadingSounds = new HashMap<Integer, PooledSound>(); private final SoundPool pool; final static boolean notSupport() { return (AndroidGame.isDevice("GT-S5830B") || AndroidGame.isDevice("GT-I9100")); } private class OpenALSound extends SoundImpl<String> { private SoundOpenAlEnv env; private String path; private SoundOpenAlSource source; private SoundOpenAlBuffer buffer; private boolean _complete, _loop; public OpenALSound(final String path) { this.path = path; this.env = SoundOpenAlEnv.getInstance(); if (SoundOpenAlEnv.isSupportNative()) { Updateable loading = new Updateable() { @Override public void action(Object a) { try { OpenALSound.this.buffer = env.addBuffer(path); OpenALSound.this.source = env.addSource(buffer); _complete = true; dispatchLoaded(OpenALSound.this, path); } catch (IOException e) { _complete = false; dispatchLoadError(OpenALSound.this, e); } } }; LSystem.load(loading); } } private boolean check() { return buffer != null && source != null && _complete; } @Override public String toString() { return path; } @Override protected boolean playingImpl() { return false; } @Override protected boolean playImpl() { if (check()) { source.play(_loop); return true; } return false; } protected boolean prepareImpl() { if (check()) { source.setPosition(0, 0, 0); } return true; } @Override protected void stopImpl() { if (check()) { source.stop(); } } @Override protected void setLoopingImpl(boolean looping) { _loop = looping; } @Override protected void setVolumeImpl(float volume) { if (check()) { source.setPitch(volume); source.setGain(volume); } } @Override protected void releaseImpl() { if (check()) { source.stop(); source.release(); buffer.release(); } } }; private class PooledSound extends SoundImpl<Integer> { public final int soundId; private int streamId; public PooledSound(int soundId) { this.soundId = soundId; } @Override public String toString() { return "pooled:" + soundId; } @Override protected boolean playingImpl() { return false; } @Override protected boolean playImpl() { if (notSupport()) { return false; } streamId = pool.play(soundId, volume, volume, 1, looping ? -1 : 0, 1); return (streamId != 0); } protected boolean prepareImpl() { pool.play(soundId, 0, 0, 0, 0, 1); return true; } @Override protected void stopImpl() { if (notSupport()) { return; } if (streamId != 0) { pool.stop(streamId); streamId = 0; } } @Override protected void setLoopingImpl(boolean looping) { if (notSupport()) { return; } if (streamId != 0) { pool.setLoop(streamId, looping ? -1 : 0); } } @Override protected void setVolumeImpl(float volume) { if (notSupport()) { return; } if (streamId != 0) { pool.setVolume(streamId, volume, volume); } } @Override protected void releaseImpl() { if (notSupport()) { return; } pool.unload(soundId); } }; public AndroidAudio() { this.pool = new SoundPool(8, AudioManager.STREAM_MUSIC, 0); // 以标准pool监听器监听数据 this.pool .setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() { public void onLoadComplete(SoundPool soundPool, int soundId, int status) { PooledSound sound = loadingSounds.remove(soundId); if (sound == null) { Log.e("AndroidAudio","load _complete for unknown sound [id=" + soundId + "]"); } else if (status == 0) { dispatchLoaded(sound, soundId); } else { dispatchLoadError(sound, new Exception( "Sound load failed [errcode=" + status + "]")); } } }); } public SoundImpl<?> createSound(AssetFileDescriptor fd) { PooledSound sound = new PooledSound(pool.load(fd, 1)); loadingSounds.put(sound.soundId, sound); return sound; } public SoundImpl<?> createSound(FileDescriptor fd, long offset, long length) { PooledSound sound = new PooledSound(pool.load(fd, offset, length, 1)); loadingSounds.put(sound.soundId, sound); return sound; } private static AssetFileDescriptor openFd(String fileName) throws IOException { if (LSystem.base().type() == LGame.Type.ANDROID) { if (fileName.toLowerCase().startsWith("assets/")) { fileName = StringUtils.replaceIgnoreCase(fileName, "assets/", ""); } if (fileName.startsWith("/") || fileName.startsWith("\\")) { fileName = fileName.substring(1, fileName.length()); } } return Loon.self.getAssets().openFd(fileName); } public SoundImpl<?> createSound(final String path) { if ("wav".equalsIgnoreCase(LSystem.getExtension(path)) && SoundOpenAlEnv.isSupportNative()) { OpenALSound sound = new OpenALSound(path); loadingOpenAlSounds.put(sound.path, sound); return sound; } try { return createSound(openFd(path)); } catch (IOException ioe) { PooledSound sound = new PooledSound(0); sound.onLoadError(ioe); return sound; } } public SoundImpl<?> createMusic(final String path) { return new AndroidBigClip(this, new Resolver<MediaPlayer>() { @Override public void resolve(final AndroidSound<MediaPlayer> sound) { final MediaPlayer mp = new MediaPlayer(); LSystem.load(new Updateable() { @Override public void action(Object o) { try { AssetFileDescriptor fd = openFd(path); mp.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); fd.close(); mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(final MediaPlayer mp) { dispatchLoaded(sound, mp); } }); mp.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { String errmsg = "MediaPlayer prepare failure [what=" + what + ", x=" + extra + "]"; dispatchLoadError(sound, new Exception( errmsg)); return false; } }); mp.prepareAsync(); } catch (Exception e) { dispatchLoadError(sound, e); } } }); } }); } public void onPause() { for (PooledSound p : loadingSounds.values()) { pool.pause(p.soundId); } for (AndroidSound<?> sound : playing) { sound.onPause(); } for (OpenALSound al : loadingOpenAlSounds.values()) { al.stop(); } } public void onResume() { pool.autoResume(); HashSet<AndroidSound<?>> wasPlaying = new HashSet<AndroidSound<?>>( playing); playing.clear(); if (!wasPlaying.isEmpty()) { Log.e("AndroidAudio","Resuming " + wasPlaying.size() + " playing sounds."); } for (AndroidSound<?> sound : wasPlaying) { sound.onResume(); } for (OpenALSound al : loadingOpenAlSounds.values()) { al.play(); } } public void onDestroy() { for (OpenALSound al : loadingOpenAlSounds.values()) { al.release(); } for (AndroidSound<?> sound : playing) { sound.release(); } loadingOpenAlSounds.clear(); playing.clear(); pool.release(); } void onPlaying(AndroidSound<?> sound) { playing.add(sound); } void onStopped(AndroidSound<?> sound) { playing.remove(sound); } }