[转]实现 Android 支付宝商家收款语音播报
1. 1动机
支付宝商家收款时,语音提示:支付宝收款xxx元,当时觉得这东西还挺有趣的,第一时间通知给商家,减少不必要的纠纷,节约时间成本,对商家对用户都挺好的。
我们产品先做了<我的钱包>,现在也希望在商家版有这样收款播报的功能,我觉得挺好的。
2.
效果图
使用
gradle引入
1 2 3 4 5 6 7 8 9 10 11 |
allprojects { repositories { <span class="">...</span> maven { url <span class="">'https://jitpack.io'</span> } } } dependencies { implementation <span class="">'com.github.YzyCoding:PushVoiceBroadcast:1.0.2'</span> } |
一行代码
1 2 3 4 |
<span class="">VoicePlay</span> <span class="">.with</span>(<span class="">MainActivity</span><span class="">.this</span>) <span class="">.play</span>(<span class="">amount</span>); |
3. 需求
- 固定播报文字,除了金额动态
- 收到多条推送,顺序播报
- 来电时,暂停播报,挂断后继续播报
- 正在播放音乐,暂停音乐,播放完成继续播放音乐
- 如果音量过小,调节音量
4. 2分析
当然是google一把,寻找新世界
- 系统类TextToSpeech,文字转语音,对中文支持很不给力,可以安装 “讯飞语记” TTS来满足
- 提前录制好”收款成功”,“0”,“1”,“2”…简小音频拼成一句话播放
- 讯飞SDK在线文字转语音播放?
随后呢,我又下载了支付宝APK,反编译出来看看,下图得知,支付宝的做法就是提前录制好,然后根据金额拼接成一句话,可不是,毕竟播报的是固定的那么几个字,在线文字转音频,还是TTS肯定麻烦了,所以还是选择和支付宝一样的做法。
思路
- 金额转大写
- 文字转音频
- 顺序播放
5.
6. 3实践
1. 关于金额的工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<span class="">public</span> <span class="">class MoneyUtils </span>{ <span class="">private</span> <span class="">static</span> <span class="">final</span> <span class="">char</span>[] NUM = {<span class="">'0'</span>, <span class="">'1'</span>, <span class="">'2'</span>, <span class="">'3'</span>, <span class="">'4'</span>, <span class="">'5'</span>, <span class="">'6'</span>, <span class="">'7'</span>, <span class="">'8'</span>, <span class="">'9'</span>}; <span class="">private</span> <span class="">static</span> <span class="">final</span> <span class="">char</span>[] CHINESE_UNIT = {<span class="">'元'</span>, <span class="">'拾'</span>, <span class="">'佰'</span>, <span class="">'仟'</span>, <span class="">'万'</span>, <span class="">'拾'</span>, <span class="">'佰'</span>, <span class="">'仟'</span>, <span class="">'亿'</span>, <span class="">'拾'</span>, <span class="">'佰'</span>, <span class="">'仟'</span>}; <span class="">/** * 返回关于钱的中文式大写数字,支仅持到亿 */</span> <span class="">public static String readInt(int moneyNum) </span>{ String res = <span class="">""</span>; <span class="">int</span> i = <span class="">0</span>; <span class="">if</span> (moneyNum == <span class="">0</span>) { <span class="">return</span> <span class="">"0"</span>; } <span class="">if</span> (moneyNum == <span class="">10</span>) { <span class="">return</span> <span class="">"拾"</span>; } <span class="">if</span> (moneyNum > <span class="">10</span> && moneyNum < <span class="">20</span>) { <span class="">return</span> <span class="">"拾"</span> + moneyNum % <span class="">10</span>; } <span class="">while</span> (moneyNum > <span class="">0</span>) { res = CHINESE_UNIT[i++] + res; res = NUM[moneyNum % <span class="">10</span>] + res; moneyNum /= <span class="">10</span>; } <span class="">return</span> res.replaceAll(<span class="">"0[拾佰仟]"</span>, <span class="">"0"</span>) .replaceAll(<span class="">"0+亿"</span>, <span class="">"亿"</span>) .replaceAll(<span class="">"0+万"</span>, <span class="">"万"</span>) .replaceAll(<span class="">"0+元"</span>, <span class="">"元"</span>) .replaceAll(<span class="">"0+"</span>, <span class="">"0"</span>) .replace(<span class="">"元"</span>, <span class="">""</span>); } } |
2. 文字转本地音频
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
private <span class="">static</span> <span class="">List</span><<span class="">String</span>> genReadableMoney(<span class="">String</span> numString) { <span class="">List</span><<span class="">String</span>> result = <span class="">new</span> ArrayList<>(); <span class="">if</span> (!TextUtils.isEmpty(numString)) { <span class="">if</span> (numString.contains(VoiceConstants.DOT_POINT)) { <span class="">String</span> integerPart = numString.split(<span class="">"\\."</span>)[<span class="">0</span>]; <span class="">String</span> decimalPart = numString.split(<span class="">"\\."</span>)[<span class="">1</span>]; <span class="">List</span><<span class="">String</span>> intList = readIntPart(integerPart); <span class="">List</span><<span class="">String</span>> decimalList = readDecimalPart(decimalPart); result.addAll(intList); <span class="">if</span> (!decimalList.isEmpty()) { result.add(VoiceConstants.DOT); result.addAll(decimalList); } } <span class="">else</span> { result.addAll(readIntPart(numString)); } } <span class="">return</span> result; } private <span class="">static</span> <span class="">List</span><<span class="">String</span>> readDecimalPart(<span class="">String</span> decimalPart) { <span class="">List</span><<span class="">String</span>> result = <span class="">new</span> ArrayList<>(); <span class="">if</span> (!<span class="">"00"</span>.equals(decimalPart)) { char[] chars = decimalPart.toCharArray(); <span class="">for</span> (char ch : chars) { result.add(<span class="">String</span>.valueOf(ch)); } } <span class="">return</span> result; } private <span class="">static</span> <span class="">List</span><<span class="">String</span>> readIntPart(<span class="">String</span> integerPart) { <span class="">List</span><<span class="">String</span>> result = <span class="">new</span> ArrayList<>(); <span class="">String</span> intString = MoneyUtils.readInt(Integer.parseInt(integerPart)); <span class="">int</span> len = intString.length(); <span class="">for</span> (<span class="">int</span> i = <span class="">0</span>; i < len; i++) { char current = intString.charAt(i); <span class="">if</span> (current == <span class="">'拾'</span>) { result.add(VoiceConstants.TEN); } <span class="">else</span> <span class="">if</span> (current == <span class="">'佰'</span>) { result.add(VoiceConstants.HUNDRED); } <span class="">else</span> <span class="">if</span> (current == <span class="">'仟'</span>) { result.add(VoiceConstants.THOUSAND); } <span class="">else</span> <span class="">if</span> (current == <span class="">'万'</span>) { result.add(VoiceConstants.TEN_THOUSAND); } <span class="">else</span> <span class="">if</span> (current == <span class="">'亿'</span>) { result.add(VoiceConstants.TEN_MILLION); } <span class="">else</span> { result.add(<span class="">String</span>.valueOf(current)); } } <span class="">return</span> result; } |
3. 顺序播放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
private <span class="">void</span> start(<span class="">final</span> <span class="">List</span><<span class="">String</span>> voicePlay) { synchronized (VoicePlay.<span class="">this</span>) { MediaPlayer mMediaPlayer = <span class="">new</span> MediaPlayer(); <span class="">final</span> CountDownLatch mCountDownLatch = <span class="">new</span> CountDownLatch(<span class="">1</span>); AssetFileDescriptor assetFileDescription = <span class="">null</span>; <span class="">final</span> <span class="">int</span>[] counter = {<span class="">0</span>}; assetFileDescription = FileUtils.getAssetFileDescription(mContext, <span class="">String</span>.format(VoiceConstants.FILE_PATH, voicePlay.<span class="">get</span>(counter[<span class="">0</span>]))); mMediaPlayer.setDataSource( assetFileDescription.getFileDescriptor(), assetFileDescription.getStartOffset(), assetFileDescription.getLength()); mMediaPlayer.prepareAsync(); mMediaPlayer.setOnPreparedListener(mediaPlayer -> mMediaPlayer.start()); mMediaPlayer.setOnCompletionListener(mediaPlayer -> { mediaPlayer.reset(); counter[<span class="">0</span>]++; <span class="">if</span> (counter[<span class="">0</span>] < voicePlay.size()) { <span class="">try</span> { AssetFileDescriptor fileDescription2 = FileUtils.getAssetFileDescription(mContext, <span class="">String</span>.format(VoiceConstants.FILE_PATH, voicePlay.<span class="">get</span>(counter[<span class="">0</span>]))); mediaPlayer.setDataSource( fileDescription2.getFileDescriptor(), fileDescription2.getStartOffset(), fileDescription2.getLength()); mediaPlayer.prepare(); } <span class="">catch</span> (IOException e) { e.printStackTrace(); mCountDownLatch.countDown(); } } <span class="">else</span> { mediaPlayer.release(); mCountDownLatch.countDown(); } }); mCountDownLatch.<span class="">await</span>(); notifyAll(); } } |
来电未测试
7.
8. 总结
代码分为两部分
- 音频组合 VoiceTextTemplate
- 音频播放 VoicePlay
VoiceBuilder 建造者模式
同步采用 synchronized + notifyAll()
更多优化请联系@我
项目Demo
https://github.com/YzyCoding/PushVoiceBroadcast