Android支付宝商家收款语音播报(无SDK)

YzyCoding发布

动机

支付宝商家收款时,语音提示:支付宝收款xxx元,当时觉得这东西还挺有趣的,第一时间通知给商家,减少不必要的纠纷,节约时间成本,对商家对用户都挺好的。
我们产品先做了,现在也希望在商家版有这样收款播报的功能,我觉得挺好的。

效果图

jianshu_0038.png

使用

  1. gradle引入
    allprojects {
        repositories {
        ...
            maven { url 'https://jitpack.io' }
        }
    }

    dependencies {
        implementation 'com.github.YzyCoding:PushVoiceBroadcast:1.0.2'
    }
  1. 一行代码
   VoicePlay.with(MainActivity.this).play(amount);

需求

  • 固定播报文字,除了金额动态
  • 收到多条推送,顺序播报
  • 来电时,暂停播报,挂断后继续播报
  • 正在播放音乐,暂停音乐,播放完成继续播放音乐
  • 如果音量过小,调节音量

分析

当然是google一把,寻找新世界
1. 系统类TextToSpeech,文字转语音,对中文支持很不给力,可以安装 “讯飞语记” TTS来满足
2. 提前录制好”收款成功”,“0”,“1”,“2”…简小音频拼成一句话播放
3. 讯飞SDK在线文字转语音播放?

随后呢,我又下载了支付宝APK,反编译出来看看,下图得知,支付宝的做法就是提前录制好,然后根据金额拼接成一句话,可不是,毕竟播报的是固定的那么几个字,在线文字转音频,还是TTS肯定麻烦了,所以还是选择和支付宝一样的做法。

jianshu_0036.png

jianshu_0037.png

思路

  • 金额转大写
  • 文字转音频
  • 顺序播放

实践

  1. 关于金额的工具类
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("元", "");
    }
}
  1. 文字转本地音频
    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;
    }
  1. 顺序播放
 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
项目地址

分类: Android

YzyCoding

Read, write and share

1 条评论

小松哥_iOSer · 2019年2月26日 下午5:52

很好用的工具

发表评论

电子邮件地址不会被公开。 必填项已用*标注