查看原文
其他

创意实现 Android 支付宝商家收款语音播报

鱼__鱼 鸿洋 2019-04-05

本文作者


作者:鱼__鱼

链接:

https://www.jianshu.com/p/62e6382c610b

本文由作者授权发布。


1动机


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


效果图


使用


gradle引入


allprojects {
        repositories {
        ...
            maven { url 'https://jitpack.io' }
        }
    }

    dependencies {
        implementation 'com.github.YzyCoding:PushVoiceBroadcast:1.0.2'
    }


一行代码


VoicePlay
.with(MainActivity.this)
.play(amount);


需求


  • 固定播报文字,除了金额动态

  • 收到多条推送,顺序播报

  • 来电时,暂停播报,挂断后继续播报

  • 正在播放音乐,暂停音乐,播放完成继续播放音乐

  • 如果音量过小,调节音量


2分析


当然是google一把,寻找新世界


  1. 系统类TextToSpeech,文字转语音,对中文支持很不给力,可以安装 “讯飞语记” TTS来满足

  2. 提前录制好"收款成功",“0”,“1”,“2”...简小音频拼成一句话播放

  3. 讯飞SDK在线文字转语音播放?


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




思路


  • 金额转大写

  • 文字转音频

  • 顺序播放


3实践


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("元""");
    }
}


2. 文字转本地音频


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;
}


3. 顺序播放


private void start(final List<String> voicePlay) {
    synchronized (VoicePlay.this) {
        MediaPlayer mMediaPlayer = new MediaPlayer();
        final CountDownLatch mCountDownLatch = new CountDownLatch(1);
        AssetFileDescriptor assetFileDescription = null;

        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();
            }
        });
        mCountDownLatch.await();
        notifyAll();
    }
}


来电未测试


总结


代码分为两部分


  • 音频组合 VoiceTextTemplate

  • 音频播放 VoicePlay


VoiceBuilder 建造者模式
同步采用 synchronized + notifyAll()


更多优化请联系@我

项目Demo
https://github.com/YzyCoding/PushVoiceBroadcast



最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!



推荐阅读

Android O 适配详细指南

Android 技能图谱学习路线


扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存