demo地址
demo采用了Android音频播放的两种方式
SoundPool 和 MediaPlayer
两者区别是 SoundPool需要优先初始化加载 将音频加载到内存中 播放时从内存中获取音频文件 不加载无法播放
MediaPlayer不需要初始化加载 随时都可以进行播放
由此可见 SoundPool 播放会比MediaPlayer 更快一些
SoundUtil 对SoundPool的封装 由于SoundPool是提前加载、缓冲在进行播放 所以播放组合音频时会同时播放
为了解决这一问题 我们可以对相应的音频进行延迟 详情可见SoundUtil代码
MediaPlayUtil 对MediaPlayer的封装 由于某些极短音频无法播放(几毫秒状态) 这是建议使用SoundPool方法播放
对于音频播放是异步的 否则会阻塞主线程 导致程序卡顿 所以使用了线程池 ----> ExecutorService
MediaPlayer的基本使用:
(1) 创建MediaPlayer实例
可以使用直接new的方式:
MediaPlayer mp = new MediaPlayer();
也可以使用create的方式,如:
//这时就不用调用setDataSource了
MediaPlayer mp = MediaPlayer.create(this, R.raw.test);
(2)设置播放源
MediaPlayer要播放的文件主要包括3个来源:
a. 用户在应用中事先自带的resource资源
例如:MediaPlayer.create(this, R.raw.test);
b. 存储在SD卡或其他文件路径下的媒体文件或存放在assets目录下
例如:mp.setDataSource("/sdcard/test.mp3");
c. 网络上的媒体文件
例如:mp.setDataSource(" http://www.citynorth.cn/music/confucius.mp3");
MediaPlayer的setDataSource一共四个方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
(3)控制播放器的几个方法:
serDataSource() 设置要播放的音频文件的位置
prepare() 在开始播放之前调用这个方法完成准备工作 同步---->create方法创建的,那么第一次启动播放前不需要再调用prepare()了,因为create方法里已经调用过了。
prepareAsync() 在开始播放之前调用这个方法完成准备工作 异步
start() 开始或继续播放音频
pause() 暂停播放音频
reset() 将MediaPlayer对象重置到刚刚创建的状态 播放器从Error状态中恢复过来,重新会到Idle状态
seekTo() 从指定的位置开始播放音频 可以让播放器从指定的位置开始播放,需要注意的是该方法是个异步方法,也就是说该方法返回时并不意味着定位完成,尤其是播放的网络文件,真正定位完成时会触发OnSeekComplete.onSeekComplete()监听
stop() 停止播放音频,调用这个方法后的MediaPlayer对象无法在播放音频
release() 释放掉与MediaPlayer对象相关的资源 一旦确定不再使用播放器时应当尽早调用它释放资源
isPlaying() 判断当前MediaPlayer是否正在播放音频
getDuration() 获取载入的音频文件时长
setLooping(boolean looping):设置是否循环播放。
getCurrentPosition():获取当前流媒体的播放的位置,单位是毫秒
isLooping():判断是否循环播放
setAudioStreamType(int streamtype):设置播放流媒体类型
setNextMediaPlayer(MediaPlayer next):设置当前流媒体播放完毕,下一个播放的MediaPlayer
(4) 设置不同的监听器 监听不同的播放状态
setOnCompletionListener(MediaPlayer.OnCompletionListener listener)
setOnErrorListener(MediaPlayer.OnErrorListener listener)等
设置播放器时需要考虑到播放器可能出现的情况设置好监听和处理逻辑,以保持播放器的健壮性和稳定性
SoundPool简单使用:
(1)创建SoundPool实例
SoundPool mSoundPool = new SoundPool.Builder()
.setMaxStreams(16)//同时播放流的最大数量,当播放的流的数目大于此值,则会选择性停止优先级较低的流
.build();
构造器如下:
SoundPool(int maxStreams, int streamType, int srcQuality)
参数maxStreams:指定支持多少个声音;
参数streamType:指定声音类型:
参数srcQuality:指定声音品质。
(2)加载音频 load()
SoundPool提供了如下4个load方法:
//从 resld 所对应的资源加载声音。
int load(Context context, int resld, int priority)
//加载 fd 所对应的文件的offset开始、长度为length的声音。
int load(FileDescriptor fd, long offset, long length, int priority)
//从afd 所对应的文件中加载声音。
int load(AssetFileDescriptor afd, int priority)
//从path 对应的文件去加载声音。
int load(String path, int priority)
(3)播放 play()
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
参数soundID:指定播放哪个声音;
参数leftVolume、rightVolume:指定左、右的音量:
参数priority:指定播放声音的优先级,数值越大,优先级越高;
参数loop:指定是否循环,0:不循环,-1:循环,其他值表示要重复播放的次数;
参数rate:指定播放的比率,数值可从0.5到2, 1为正常比率。
(4)释放资源 release()
注意* SoundPool 必须、必须、必须 先加载load在播放play*****
* @author renquan
* SoundPool 方式播放
public class SoundUtil {
private ExecutorService mExecutorService;
private static SoundUtil soundUtil;
private final SoundPool soundPool;
private String[] voice = {"1", "9", "dot"
, "hundred", "hundred_million", "success", "ten", "ten_thousand", "thousand", "yuan"};
private static HashMap<String, Integer> soundMap = new HashMap<>();
private SoundUtil(Context context) {
this.mExecutorService = Executors.newCachedThreadPool();
//构建对象
SoundPool.Builder spb = new SoundPool.Builder();
spb.setMaxStreams(100);
soundPool = spb.build(); //创建SoundPool对象
public static SoundUtil getInstance() {
if (soundUtil == null) {
init();
return soundUtil;
public static SoundUtil init() {
if (soundUtil == null) {
synchronized (SoundPool.class) {
if (soundUtil == null) {
soundUtil = new SoundUtil(MyApp.context);
try {
for (int i = 0; i < soundUtil.voice.length; i++) {
String soundName = soundUtil.voice[i];
int soundId = soundUtil.soundPool.load(MyApp.context.getAssets().openFd(String.format(VoiceConstants.FILE_PATH, soundName)), 1);
soundMap.put(soundName, soundId);
} catch (IOException e) {
e.printStackTrace();
return soundUtil;
//数字播报
public void playNum(final String soundName) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
synchronized (SoundUtil.class) {
try {
if (null == soundName || soundName.isEmpty()) {
return;
Integer soundId = soundMap.get(soundName);
if (null != soundId) {
soundPool.play(soundId, 1, 1, 1, 0, 1);
if (soundName.length() == 1 || soundName.equals("dot")) {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
//组合播报
public void play(final List<String> voicePlayer) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
if (voicePlayer.size() <= 0) {
return;
start(voicePlayer);
public void playMoney(final String start, final String money) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
VoiceBuilder voiceBuilder = new VoiceBuilder.Builder()
.start(start)
.money(money)
.unit(VoiceConstants.YUAN)
.checkNum(false)
.builder();
List<String> voicePlay = VoiceTextTemplate.genVoiceList(voiceBuilder);
start(voicePlay);
private void start(final List<String> voicePlayer) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < voicePlayer.size(); i++) {
String soundName = voicePlayer.get(i);
if (null == soundName || soundName.isEmpty()) {
continue;
Integer soundId = soundMap.get(soundName);
if (null != soundId) {
soundPool.play(soundId, 1, 1, 1, 0, 1);
try {
if (soundName.equals("success")) {
Thread.sleep(800);
} else {
Thread.sleep(350);
} catch (InterruptedException e) {
e.printStackTrace();
* @author renquan
* @describe 音频播放
* MediaPlayer 方式播放音频
* @ideas
public class MediaPlayUtil {
private ExecutorService mExecutorService;
private Context mContext;
private MediaPlayUtil(Context context) {
this.mContext = context;
this.mExecutorService = Executors.newCachedThreadPool();
@Nullable
private volatile static MediaPlayUtil mVoicePlay = null;
* @return
@Nullable
public static MediaPlayUtil with(Context context) {
if (mVoicePlay == null) {
synchronized (MediaPlayUtil.class) {
if (mVoicePlay == null) {
mVoicePlay = new MediaPlayUtil(context);
return mVoicePlay;
* 默认收款成功样式
* @param money
public void play(String money) {
play(money, false);
* 设置播报数字
* @param money
* @param checkNum
public void play(String money, boolean checkNum) {
VoiceBuilder voiceBuilder = new VoiceBuilder.Builder()
.start(VoiceConstants.SUCCESS)
.money(money)
.unit(VoiceConstants.YUAN)
.checkNum(checkNum)
.builder();
executeStart(voiceBuilder);
public void checkMoney(String money, boolean checkNum) {
VoiceBuilder voiceBuilder = new VoiceBuilder.Builder()
.start(VoiceConstants.PLEASEPAY)
.money(money)
.unit(VoiceConstants.YUAN)
.checkNum(checkNum)
.builder();
executeStart(voiceBuilder);
* 接收自定义
* @param voiceBuilder
public void play(@NonNull VoiceBuilder voiceBuilder) {
executeStart(voiceBuilder);
* 开启线程
* @param builder
private void executeStart(@NonNull VoiceBuilder builder) {
final List<String> voicePlay = VoiceTextTemplate.genVoiceList(builder);
if (voicePlay == null || voicePlay.isEmpty()) {
return;
mExecutorService.execute(new Runnable() {
@Override
public void run() {
start(voicePlay);
* 开始播报
* @param voicePlay
private void start(@NonNull final List<String> voicePlay) {
synchronized (MediaPlayUtil.this) {
final CountDownLatch mCountDownLatch = new CountDownLatch(1);
AssetFileDescriptor assetFileDescription = null;
try {
final int[] counter = {0};
assetFileDescription = FileUtils.getAssetFileDescription(mContext,
String.format(VoiceConstants.FILE_PATH, voicePlay.get(counter[0])));
final MediaPlayer mMediaPlayer = new MediaPlayer();
mMediaPlayer.setDataSource(
assetFileDescription.getFileDescriptor(),
assetFileDescription.getStartOffset(),
assetFileDescription.getLength());
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.reset();
counter[0]++;
if (counter[0] < voicePlay.size()) {
try {
AssetFileDescriptor fileDescription2 = FileUtils.getAssetFileDescription(mContext,
String.format(VoiceConstants.FILE_PATH, voicePlay.get(counter[0])));
mediaPlayer.setDataSource(
fileDescription2.getFileDescriptor(),
fileDescription2.getStartOffset(),
fileDescription2.getLength());
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
mCountDownLatch.countDown();
} else {
mediaPlayer.release();
mCountDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
mCountDownLatch.countDown();
} finally {
if (assetFileDescription != null) {
try {
assetFileDescription.close();
} catch (IOException e) {
e.printStackTrace();
try {
mCountDownLatch.await();
notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
* @author renquan
* @date on 2020-12-09 15:25
* @describe 音频组合
* @ideas
public class VoiceTextTemplate {
* 音频组合
* @param voiceBean
* @return
@NonNull
public static List<String> genVoiceList(@NonNull VoiceBuilder voiceBean) {
List<String> result = new ArrayList<>();
String start = voiceBean.getStart();
String money = voiceBean.getMoney();
String unit = voiceBean.getUnit();
boolean checkNum = voiceBean.isCheckNum();
if (!TextUtils.isEmpty(start)) {
result.add(start);
if (!TextUtils.isEmpty(money)) {
if (checkNum) {
result.addAll(createReadableNumList(money));
} else {
result.addAll(genReadableMoney(money));
if (!TextUtils.isEmpty(unit)) {
result.add(unit);
return result;
* 全转成 中文 RMB
* @param numString
* @return
@NonNull
private static List<String> genReadableMoney(@NonNull String numString) {
List<String> result = new ArrayList<>();
if (!TextUtils.isEmpty(numString)) {
if (numString.contains(VoiceConstants.DOT_POINT)) {
String integerPart = numString.split("\\.")[0];
String decimalPart = numString.split("\\.")[1];
List<String> intList = readIntPart(integerPart);
List<String> decimalList = readDecimalPart(decimalPart);
result.addAll(intList);
if (!decimalList.isEmpty()) {
result.add(VoiceConstants.DOT);
result.addAll(decimalList);
} else {
result.addAll(readIntPart(numString));
return result;
@NonNull
private static List<String> readDecimalPart(@NonNull String decimalPart) {
List<String> result = new ArrayList<>();
if (!"00".equals(decimalPart)) {
char[] chars = decimalPart.toCharArray();
for (char ch : chars) {
result.add(String.valueOf(ch));
return result;
* 全转成数字
* @param numString
* @return
@NonNull
private static List<String> createReadableNumList(@NonNull String numString) {
List<String> result = new ArrayList<>();
if (!TextUtils.isEmpty(numString)) {
int len = numString.length();
for (int i = 0; i < len; i++) {
if ('.' == numString.charAt(i)) {
result.add(VoiceConstants.DOT);
} else {
result.add(String.valueOf(numString.charAt(i)));
return result;
* 返回数字对应的音频
* @param integerPart
* @return
@NonNull
private static List<String> readIntPart(@NonNull String integerPart) {
List<String> result = new ArrayList<>();
String intString = MoneyUtils.readInt(Integer.parseInt(integerPart));
int len = intString.length();
for (int i = 0; i < len; i++) {
char current = intString.charAt(i);
if (current == '拾') {
result.add(VoiceConstants.TEN);
} else if (current == '佰') {
result.add(VoiceConstants.HUNDRED);
} else if (current == '仟') {
result.add(VoiceConstants.THOUSAND);
} else if (current == '万') {
result.add(VoiceConstants.TEN_THOUSAND);
} else if (current == '亿') {
result.add(VoiceConstants.TEN_MILLION);
} else {
result.add(String.valueOf(current));
return result;
* @author renquan
* @date on 2020-12-09 17:54
* @describe 关于金钱的工具类
* @ideas
public class MoneyUtils {
private static final char[] NUM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
private static final char[] CHINESE_UNIT = {'元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟'};
* 返回关于钱的中文式大写数字,支仅持到亿
@NonNull
public static String readInt(int moneyNum) {
String res = "";
int i = 0;
if (moneyNum == 0) {
return "0";
if (moneyNum == 10) {
return "拾";
if (moneyNum > 10 && moneyNum < 20) {
return "拾" + moneyNum % 10;
while (moneyNum > 0) {
res = CHINESE_UNIT[i++] + res;
res = NUM[moneyNum % 10] + res;
moneyNum /= 10;
return res.replaceAll("0[拾佰仟]", "0")
.replaceAll("0+亿", "亿")
.replaceAll("0+万", "万")
.replaceAll("0+元", "元")
.replaceAll("0+", "0")
.replace("元", "");
* @author renquan
* @date on 2020-12-10 09:17
* @describe 字符相关的工具类
* @ideas
public class StringUtils {
* 提取字符串中的 数字 带小数点 ,没有就返回""
* @param money
* @return
public static String getMoney(String money) {
Pattern pattern = Pattern.compile("(\\d+\\.\\d+)");
Matcher m = pattern.matcher(money);
if (m.find()) {
money = m.group(1) == null ? "" : m.group(1);
} else {
pattern = Pattern.compile("(\\d+)");
m = pattern.matcher(money);
if (m.find()) {
money = m.group(1) == null ? "" : m.group(1);
} else {
money = "";
return money;