Android音频播放 支付金额播报(SoundPool、MediaPlayer)

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

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

/**
 * @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;
    }
}

[原文地址]("Android音频播放-支付金额播报_一个不是很优秀的码农的博客-CSDN博客
")

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容