动机
支付宝商家收款时,语音提示:支付宝收款xxx元,当时觉得这东西还挺有趣的,第一时间通知给商家,减少不必要的纠纷,节约时间成本,对商家对用户都挺好的。
我们产品先做了<我的钱包>,现在也希望在商家版有这样收款播报的功能,我觉得挺好的。
效果图
使用
- gradle引入
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.YzyCoding:PushVoiceBroadcast:1.0.2'
}
- 一行代码
VoicePlay.with(MainActivity.this).play(amount);
需求
- 固定播报文字,除了金额动态
- 收到多条推送,顺序播报
- 来电时,暂停播报,挂断后继续播报
- 正在播放音乐,暂停音乐,播放完成继续播放音乐
- 如果音量过小,调节音量
分析
当然是google一把,寻找新世界
- 系统类TextToSpeech,文字转语音,对中文支持很不给力,可以安装 “讯飞语记” TTS来满足
- 提前录制好"收款成功",“0”,“1”,“2”...简小音频拼成一句话播放
- 讯飞SDK在线文字转语音播放?
随后呢,我又下载了支付宝APK,反编译出来看看,下图得知,支付宝的做法就是提前录制好,然后根据金额拼接成一句话,可不是,毕竟播报的是固定的那么几个字,在线文字转音频,还是TTS肯定麻烦了,所以还是选择和支付宝一样的做法。
思路
- 金额转大写
- 文字转音频
- 顺序播放
实践
- 关于金额的工具类
public class MoneyUtils {
private static final char[] NUM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
private static final char[] CHINESE_UNIT = {'元', '拾', '佰', '仟', '万', '拾', '佰', '仟', '亿', '拾', '佰', '仟'};
/**
* 返回关于钱的中文式大写数字,支仅持到亿
*/
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("元", "");
}
}
- 文字转本地音频
private static List<String> genReadableMoney(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;
}
private static List<String> readDecimalPart(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;
}
private static List<String> readIntPart(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;
}
- 顺序播放
private void start(final List<String> voicePlay) {
synchronized (VoicePlay.this) {
MediaPlayer mMediaPlayer = new MediaPlayer();
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])));
mMediaPlayer.setDataSource(
assetFileDescription.getFileDescriptor(),
assetFileDescription.getStartOffset(),
assetFileDescription.getLength());
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(mediaPlayer -> mMediaPlayer.start());
mMediaPlayer.setOnCompletionListener(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();
}
}
}
容错处理
/**
* 提取字符串中的 数字 带小数点 ,没有就返回""
*
* @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;
}
@Test
public void testMoney() {
String money = StringUtils.getMoney("");
System.out.println("money == " + money);
String money1 = StringUtils.getMoney("收到影秀卡付款0.01元");
System.out.println("money1 == " + money1);
String money2 = StringUtils.getMoney("收到测试影秀卡付款1000.00元");
System.out.println("money1 == " + money2);
String money3 = StringUtils.getMoney("收到测试影秀卡付款1000元");
System.out.println("money2 == " + money3);
String money4 = StringUtils.getMoney("收到测试影秀卡付款999.99元");
System.out.println("money3 == " + money4);
String money5 = StringUtils.getMoney("999.99");
System.out.println("money4 == " + money5);
String money6 = StringUtils.getMoney("1");
System.out.println("money5 == " + money6);
}
Log:
money ==
money1 == 0.01
money1 == 1000.00
money2 == 1000
money3 == 999.99
money4 == 999.99
money5 == 1
开发中
来电未测试
总结
代码分为两部分
音频组合 VoiceTextTemplate
音频播放 VoicePlay
VoiceBuilder 建造者模式
同步采用 synchronized + notifyAll()
更多优化请联系@我
项目Demo
项目地址