Android SoundPool 钢琴弹奏(停止播放做 FadeOut)

编程入门 行业动态 更新时间:2024-10-10 18:20:07

Android SoundPool <a href=https://www.elefans.com/category/jswz/34/1740843.html style=钢琴弹奏(停止播放做 FadeOut)"/>

Android SoundPool 钢琴弹奏(停止播放做 FadeOut)

Android SoundPool 主要用于快速播放多个短音频,开发文档链接。应用场景如:钢琴弹奏 APP,可用 SoundPool 播放每个琴键的音频资源。

先看应用场景效果:

为使用方便,封装 SoundPool 如下:

package com.alan.audioio.audio;import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import com.alan.audioio.audiomon.APPContext;
import com.alan.audioio.audiomon.AudioConstants;
import com.alan.audioio.audio.exception.AudioException;
import com.alan.audioio.utils.ALog;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** Author: AlanWang4523.* Date: 2020/10/17 14:57.* Mail: alanwang4523@gmail*/
public class AndroidSoundPool {private static final String TAG = AndroidSoundPool.class.getSimpleName();private static final int MSG_FADE_OUT = 1001;private static final int FADE_DURATION = 30;private static final int FADE_INTERVAL_TIME = 6;private static final float FADE_INTERVAL_VOLUME = (1.0f / (1.0f * FADE_DURATION / FADE_INTERVAL_TIME));private SoundPool mSoundPool;private int mMaxStreamCount;private ArrayList<Integer> mSoundIdList;private ArrayList<Integer> mPlayingIdList;private CountDownLatch mCountDownLatch;private Handler mHandler;private HandlerThread mHandlerThread;private float mCurPlayVolume = 1.0f;/*** 构造函数*/public AndroidSoundPool(int maxStreamCount) {mMaxStreamCount = maxStreamCount;mSoundIdList = new ArrayList<>();mPlayingIdList = new ArrayList<>();mSoundPool = createSoundPool(mMaxStreamCount);mSoundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {@Overridepublic void onLoadComplete(SoundPool soundPool, int sampleId, int status) {ALog.d("onLoadComplete()--->>sampleId = " + sampleId + ", status = " + status);if (mCountDownLatch != null) {mCountDownLatch.countDown();}}});mHandlerThread = new HandlerThread(TAG);mHandlerThread.start();mHandler = new InternalHandler(mHandlerThread.getLooper(), this);}/*** 创建 SoundPool* @param maxStream 同时播放的最大流数量* @return SoundPool*/private SoundPool createSoundPool(int maxStream) {if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {SoundPool.Builder builder = new SoundPool.Builder();builder.setMaxStreams(maxStream);AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();attributesBuilder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);attributesBuilder.setFlags(256);attributesBuilder.setUsage(AudioAttributes.USAGE_MEDIA);attributesBuilder.setLegacyStreamType(3);builder.setAudioAttributes(attributesBuilder.build());return builder.build();} else {return new SoundPool(maxStream, AudioManager.STREAM_MUSIC, 0);}}/*** 加载资源列表* @param audioPathList 要加载的音频资源列表* @return soundIDList* @throws AudioException 加载失败会抛出 AudioException*/public List<Integer> load(List<String> audioPathList) throws AudioException {mSoundIdList.clear();ArrayList<Integer> soundIDList = new ArrayList<>();if (audioPathList == null) {return soundIDList;}mCountDownLatch = new CountDownLatch(audioPathList.size());for (String audioPath : audioPathList) {int soundID = this.load(audioPath);soundIDList.add(soundID);}try {mCountDownLatch.await(audioPathList.size() * 2, TimeUnit.SECONDS);} catch (InterruptedException e) {// do nothing}return soundIDList;}/*** 加载资源文件* @param audioPath 音频资源路径,支持协议如下:*        assets://piano/A.m4a*        exfile:///sdcard/Alan/Audio/piano/A.m4a*        /sdcard/Alan/Audio/piano/A.m4a** @return soundID,可以用于播放或 unload* @throws AudioException 加载失败抛出 AudioException*/private int load(String audioPath) throws AudioException {int soundID;String realPath;if (AudioConstants.isAssetsPath(audioPath)) {// assets 文件realPath = audioPath.replace(AudioConstants.HOST_ASSETS, "");try {AssetFileDescriptor assetFileDescriptor = APPContext.getAssetManager().openFd(realPath);soundID = mSoundPool.load(assetFileDescriptor, 0);} catch (IOException e) {throw new AudioException("Load asset file failed.", e);}} else if (AudioConstants.isExFilePath(audioPath)) {// 外部存储文件realPath = audioPath.replace(AudioConstants.HOST_EXFILE, "");soundID = mSoundPool.load(realPath, 0);} else {// 其他绝对路径不带前缀的文件realPath = audioPath;soundID = mSoundPool.load(realPath, 0);}mSoundIdList.add(soundID);return soundID;}/*** 播放某个资源* @param soundID soundID,由 {@link #load(String)} 返回*/public void play(int soundID) {mCurPlayVolume = 1.0f;int playingId = mSoundPool.play(soundID,1.0f, 1.0f, 0, 0, 1.0f);synchronized (AndroidSoundPool.this) {if ((playingId != 0) && !mPlayingIdList.contains(playingId)) {mPlayingIdList.add(playingId);}if (mPlayingIdList.size() > mMaxStreamCount) {mPlayingIdList.remove(0);}}}/*** 停止播放,停止时会做 fade out*/public void stopPlay() {mHandler.removeMessages(MSG_FADE_OUT);mHandler.sendEmptyMessage(MSG_FADE_OUT);try {Thread.sleep(FADE_DURATION + FADE_INTERVAL_TIME);} catch (InterruptedException e) {e.printStackTrace();}}/*** 卸载某个资源* @param soundID soundID,由 {@link #load(String)} 返回*/public void unload(int soundID) {mSoundPool.unload(soundID);int idIndex = -1;for (int i = 0; i < mSoundIdList.size(); i++) {if (soundID == mSoundIdList.get(i)) {idIndex = i;}}if (idIndex >= 0) {mSoundIdList.size();mSoundIdList.remove(idIndex);}}/*** 卸载所有资源*/public void unloadAll() {for (Integer soundID : mSoundIdList) {mSoundPool.unload(soundID);}mSoundIdList.clear();}/*** 释放资源*/public void release() {unloadAll();mSoundPool.release();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {mHandlerThread.quitSafely();} else {mHandlerThread.quit();}}private void handleFadeOut() {mCurPlayVolume -= FADE_INTERVAL_VOLUME;setVolume(mCurPlayVolume);if (mCurPlayVolume > 0) {mHandler.sendEmptyMessageDelayed(MSG_FADE_OUT, FADE_INTERVAL_TIME);} else {synchronized (AndroidSoundPool.this) {for (Integer playingId : mPlayingIdList) {mSoundPool.stop(playingId);}mPlayingIdList.clear();}}}private void setVolume(float volume) {ALog.d("setVolume()----->>>" + volume + ", PlayingIdList = " + mPlayingIdList.toString());if (volume > 1.0f) {volume = 1.0f;} else if (volume < 0.01f) {volume = 0.0f;}synchronized (AndroidSoundPool.this) {try {for (Integer playingId : mPlayingIdList) {mSoundPool.setVolume(playingId, volume, volume);}} catch (Exception e) {e.printStackTrace();}}}private static class InternalHandler extends Handler {private WeakReference<AndroidSoundPool> weakRefSoundPool;public InternalHandler(Looper looper, AndroidSoundPool androidSoundPool) {super(looper);weakRefSoundPool = new WeakReference<>(androidSoundPool);}@Overridepublic void handleMessage(Message msg) {AndroidSoundPool androidSoundPool = weakRefSoundPool.get();if (androidSoundPool == null) {return;}if (msg.what == MSG_FADE_OUT) {androidSoundPool.handleFadeOut();}}}
}

调用逻辑如下:

package com.alan.audioio.app;import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.alan.audioio.R;
import com.alan.audioio.app.ui.PianoKeyItemView;
import com.alan.audioio.audio.AndroidSoundPool;
import com.alan.audioio.audiomon.APPContext;
import com.alan.audioio.audio.exception.AudioException;
import com.alan.audioio.utils.ALog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;/*** Author: AlanWang4523.* Date: 2020/10/17 18:38.* Mail: alanwang4523@gmail*/
public class TestSoundPoolActivity  extends AppCompatActivity implements View.OnClickListener {public static void launchMe(Context context) {context.startActivity(new Intent(context, TestSoundPoolActivity.class));}private static final String MUSIC_PIANO_DIR = "assets://piano/";private static final String[] KEY_NAMES = {"A", "B", "C", "D", "E",};private static final String FILE_SUFFIX = ".m4a";private static final int PIANO_KEYS_COUNT = 5;private static final int MAX_SOUND_COUNT = 5;private int[] btnPianoKeysIdArr;// 按钮idprivate PianoKeyItemView[] pianoKeyItemViewArr;private HashMap<Integer, Integer> btnIdIndexMap = new HashMap<>(PIANO_KEYS_COUNT);private HashMap<Integer, Integer> btnIdAndSoundIdMap = new HashMap<>(PIANO_KEYS_COUNT);private TextView btnStopPlay;private ProgressDialog mProgressDialog;private AndroidSoundPool mAndroidSoundPool;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_sound_pool);btnStopPlay = findViewById(R.id.btn_stop);btnStopPlay.setOnClickListener(this);APPContext.getInstance().setContext(this);btnPianoKeysIdArr = new int[PIANO_KEYS_COUNT];btnPianoKeysIdArr[0] = R.id.btn_key_A;btnPianoKeysIdArr[1] = R.id.btn_key_B;btnPianoKeysIdArr[2] = R.id.btn_key_C;btnPianoKeysIdArr[3] = R.id.btn_key_D;btnPianoKeysIdArr[4] = R.id.btn_key_E;pianoKeyItemViewArr = new PianoKeyItemView[PIANO_KEYS_COUNT];for (int i = 0; i < pianoKeyItemViewArr.length; i++) {btnIdIndexMap.put(btnPianoKeysIdArr[i], i);pianoKeyItemViewArr[i] = findViewById(btnPianoKeysIdArr[i]);pianoKeyItemViewArr[i].setOnClickListener(this);}loadMusicInstrument();}private void loadMusicInstrument() {ALog.e("loadMusicInstrument--------------->>");if (mProgressDialog == null) {mProgressDialog = new ProgressDialog(this);mProgressDialog.setMessage("正在加载乐器...");}mProgressDialog.show();if (mAndroidSoundPool != null) {mAndroidSoundPool.release();}mAndroidSoundPool = new AndroidSoundPool(MAX_SOUND_COUNT);final ArrayList<String> audioFileList = new ArrayList<>();for (int i = 0; i < pianoKeyItemViewArr.length; i++) {audioFileList.add(getAudioPath(i));}AsyncTask.execute(new Runnable() {@Overridepublic void run() {try {List<Integer> soundIdList = mAndroidSoundPool.load(audioFileList);for (int i = 0; i < soundIdList.size(); i++) {ALog.e("loadAudioAsync:: i = " + i + ", soundId = " + soundIdList.get(i)+ ", keyPath = " + getAudioPath(i));btnIdAndSoundIdMap.put(btnPianoKeysIdArr[i], soundIdList.get(i));}TestSoundPoolActivity.this.runOnUiThread(new Runnable() {@Overridepublic void run() {mProgressDialog.dismiss();}});} catch (AudioException e) {e.printStackTrace();}}});}private String getAudioPath(int i) {return MUSIC_PIANO_DIR + KEY_NAMES[i] + FILE_SUFFIX;}@Overrideprotected void onDestroy() {if (mProgressDialog != null && mProgressDialog.isShowing()) {mProgressDialog.dismiss();}if (mAndroidSoundPool != null) {mAndroidSoundPool.stopPlay();mAndroidSoundPool.release();}super.onDestroy();}@Overridepublic void onClick(View view) {if (view.getId() == R.id.btn_stop) {if (mAndroidSoundPool != null) {mAndroidSoundPool.stopPlay();}} else {int index = btnIdIndexMap.get(view.getId());if (index >= 0) {PianoKeyItemView keyItemView = pianoKeyItemViewArr[index];int soundId = btnIdAndSoundIdMap.get(view.getId());ALog.e("PlayPiano--->> " + keyItemView.getKeyName() + ", soundId = " + soundId);if (mAndroidSoundPool != null) {mAndroidSoundPool.play(soundId);}}}}
}

完整 Demo

更多推荐

Android SoundPool 钢琴弹奏(停止播放做 FadeOut)

本文发布于:2024-03-13 17:22:52,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1734464.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:钢琴   Android   SoundPool   FadeOut

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!