本文链接:https://blog.csdn.net/qq_40785165/article/details/109658968

大家好,我是小黑,一个还没秃头的程序员~~~

这是我第一次写文章,也是希望将我以后的学习经历分享给大家,希望大家喜欢!

简单的事你重复做,你就是专家;重复的事你认真做,你就是赢家。

之前在一个聊天室项目中实现了发送图片之后,我又想着实现一个发送语音的功能,包括录音、计时、播放、耳机与外放切换,先看一下效果图

可以看到发送语音的功能是由点击语音功能模块后弹出的对话框来实现的,点击开始按钮会开始录音,点击完成释放资源并上传录音到服务器,最终刷新录音列表,列表行点击事件会进行录音播放并监听耳机的连接广播。

*注:以下代码中的颜色与尺寸均为项目中定义好的,可替换成自己想要的值。

本次功能开发需要添加的权限如下:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

(一)先定义一个对话框的样式dialog_microphone,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:background="@drawable/frame_grey_white_edge"android:gravity="center"android:orientation="vertical"android:paddingLeft="@dimen/b20"android:paddingTop="@dimen/b50"android:paddingRight="@dimen/b20"android:paddingBottom="@dimen/b50">
​<ImageViewandroid:layout_width="@dimen/b120"android:layout_height="@dimen/b120"android:src="@mipmap/icon_microphone" />
​<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/b50"android:text="点击外部区域,取消发送" />
​<LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="@dimen/b20">
​<com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButtonandroid:id="@+id/btn_start"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:padding="@dimen/b10"android:text="开始"android:textColor="@color/color_white"android:textSize="@dimen/b28"app:qmui_backgroundColor="@color/color_orange_main"app:qmui_borderColor="@color/color_white"app:qmui_radius="@dimen/b10" />
​<com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButtonandroid:id="@+id/btn_ok"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:padding="@dimen/b10"android:text="完成"android:textColor="@color/color_white"android:textSize="@dimen/b28"app:qmui_backgroundColor="@color/color_orange_main"app:qmui_borderColor="@color/color_white"app:qmui_radius="@dimen/b10" />
​</LinearLayout>
</LinearLayout>

代码中QMUIRoundButton是QMUI框架的按钮控件,感兴趣的小伙伴可以自行百度了解一下,或者换成普通的按钮控件即可,frame_grey_white_edge代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"><solid android:color="@color/color_gray_e8" /><corners android:radius="@dimen/b20" /><stroke android:color="@color/color_white" />
</shape>

(二)我们可以使用MediaRecorder的Api进行录音,我将所用到的Api都整理到类--MediaHelper中去,代码如下:

public class MediaHelper {private MediaRecorder mMediaRecorder;private String mPath;//文件夹private String mFilePath;//文件
​private static MediaHelper mInstance;
​private MediaHelper(String path) {mPath = path;}
​/*** 准备播放后的回调* 这个时候文件夹里已经有文件生成了* 如果不去释放资源将会一直进行录音*/public interface MediaStateListener {void preparedDone();}
​public MediaStateListener mMediaStateListener;
​public void setMediaStateListener(MediaStateListener mediaStateListener) {mMediaStateListener = mediaStateListener;}
​
​/*** 单例模式获取 MediaHelper* 双检锁/双重校验锁** @param path* @return*/public static MediaHelper getInstance(String path) {if (mInstance == null) {synchronized (MediaHelper.class) {if (mInstance == null) {mInstance = new MediaHelper(path);}}}
​return mInstance;}
​/*** 准备录音*/public void prepare() {
​try {File fileDir = new File(mPath);boolean b = !fileDir.exists();if (b) {fileDir.mkdirs();}
​String fileName = System.currentTimeMillis() + ".amr"; // 文件名字File file = new File(fileDir, fileName);  // 文件路径
​mMediaRecorder = new MediaRecorder();mFilePath = file.getAbsolutePath();//设置保存文件的路径if (Build.VERSION.SDK_INT < 26) {//若api低于26,调用setOutputFile(String path)mMediaRecorder.setOutputFile(file.getAbsolutePath());} else {//若API高于26 使用setOutputFile(File path)mMediaRecorder.setOutputFile(new File(file.getAbsolutePath()));}mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);    // 设置MediaRecorder的音频源为麦克风mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);    // 设置音频的格式mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);    // 设置音频的编码为AMR_NB
​mMediaRecorder.prepare();mMediaRecorder.start();
​if (mMediaStateListener != null) {mMediaStateListener.preparedDone();}} catch (IllegalStateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}
​}
​/*** 释放资源*/public void release() {mMediaRecorder.stop();mMediaRecorder.release();mMediaRecorder = null;}
​/*** 取消*/public void cancel() {release();//删除相应的录音if (mFilePath != null) {File file = new File(mFilePath);if (file.exists()) {file.delete();}mFilePath = null;}}//获取生成的文件路径public String getFilePath() {return mFilePath;}
}

(三) 对话框中通过点击相应按钮调用上述类中的相应的方法,我这里有个对话框的类叫MicrophoneDialog,代码如下:

public class MicrophoneDialog extends BaseDialog implements MediaHelper.MediaStateListener {public static final int EXTRA_START = 1;//开始录制public static final int EXTRA_UPDATE_TIME = 2;//更新时长private AudioListener mAudioListener;private long mTime;//时长private String mPath = Constants.APK_PATH;private boolean authDismiss;//是否是自动关闭的,自动关闭的不触发关闭监听
​private MediaHelper mMediaHelper;private boolean isRecording;private String TAG = "MicrophoneDialog";
​public void setAudioListener(AudioListener audioListener) {mAudioListener = audioListener;}
​@Overridepublic void preparedDone() {mHandler.sendEmptyMessage(EXTRA_START);}
​public interface AudioListener {void finish(long time, String filePath);
​void cancel();}
​public void dismiss(boolean authDismiss) {this.authDismiss = authDismiss;dismiss();}
​public MicrophoneDialog(Context context) {super(context);}
​@Overridepublic int getViewId() {return R.layout.dialog_microphone;}
​@Overridepublic void initBasic(Bundle savedInstanceState) {mMediaHelper = MediaHelper.getInstance(mPath);
​setOnDismissListener(new OnDismissListener() {@Overridepublic void onDismiss(DialogInterface dialog) {if (!authDismiss) {mMediaHelper.release();isRecording = false;mTime = 0;if (mAudioListener != null) {mAudioListener.cancel();}}}});mMediaHelper.setMediaStateListener(this);}
​@OnClick({R.id.btn_ok, R.id.btn_start})public void onViewClicked(View view) {switch (view.getId()) {case R.id.btn_start:QToast.showToast("开始录音");mMediaHelper.prepare();break;case R.id.btn_ok:mMediaHelper.release();//要上传音频前释放资源,否则没办法上传音频QToast.showToast("结束录音");isRecording = false;Log.e(TAG, "onViewClicked: " + mTime + "," + mMediaHelper.getFilePath());if (mAudioListener != null) {mAudioListener.finish(mTime / 1000, mMediaHelper.getFilePath());mTime = 0;}break;}}
​/*** 这里使用Handle是为了在子线程中更新ui* 尽管我现在子线程只用来计时,没有更新ui* 但是万一以后会更新ui呢*/@SuppressLint("HandlerLeak")private Handler mHandler = new Handler() {
​public void handleMessage(android.os.Message msg) {switch (msg.what) {case EXTRA_START:isRecording = true;//开始计时postDelayed(mRunnable, 1000);break;case EXTRA_UPDATE_TIME:postDelayed(mRunnable, 1000);break;
​}}};/*** 开启个子线程计算时长*/private Runnable mRunnable = new Runnable() {@Overridepublic void run() {if (isRecording) {mTime += 1000;mHandler.sendEmptyMessage(EXTRA_UPDATE_TIME);//TODO 通知修改时长显示}}};

上述代码中练习了Handle的使用,本来是为了在子线程中更新ui的,但是还是偷了懒,延时计时使用Thread.sleep也是可以的,用到了Butterknife框架进行控件声明以及点击事件声明,BaseDialog是封装好的基类,小伙伴们可将initBasic()方法中的代码移至onCreate()中去即可,需要注意的是上传文件之前需要先释放资源,否则会影响上传文件接口的调用,对话框做完之后就是在activity或者fragment中去显示对话框即可,录音完成之后调用相应的后台接口进行文件上传即可,随后返回录音地址,使用recyclerview开发录音列表,通过行点击进行录音播放,播放实现的效果有:

1.点击相同子项播放与重置播放,点击不同子项需要释放上一个资源重置下一个资源
2.监听有线耳机与蓝牙耳机的拔插,修改播放参数

(四)播放的代码如下:

//变量声明以及定义
private MediaPlayer mMediaPlayer;
private HeadSetReceiver mHeadSetReceiver;//广播监听
private AudioManager mAudioManager = null;
private int index;//当前点击的是不是本身
......
registerHeadsetReceiver();
mAudioManager = (AudioManager) mActivity.getSystemService(Context.AUDIO_SERVICE);//切换耳机等播放模式
mAudioManager.setMode(AudioManager.MODE_NORMAL);//普通模式
mMediaPlayer = new MediaPlayer();//播放
......
//点击事件,先判断是否是音频类型的消息if (item.getMessageType() == 3) {if (!mMediaPlayer.isPlaying()) {//没在播放就播放play(item);} else {//同样的音频在播放就释放并重置,如果是新的音频就接着播放新的mMediaPlayer.reset();mMediaPlayer.stop();mMediaPlayer.release();mMediaPlayer = new MediaPlayer();if (position != index) {play(item);//播放}}}index = position;......//播放录音的方法private void play(MessageBean item) {try {mMediaPlayer = new MediaPlayer();mMediaPlayer.setDataSource(HttpHelper.picDomain + item.getMessage_content());//域名+文件路径mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}

(五)耳机(有线耳机&蓝牙耳机)拔插的监听以及注册监听代码如下:

 class HeadSetReceiver extends BroadcastReceiver {
​@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {BluetoothAdapter defaultAdapter = BluetoothAdapter.getDefaultAdapter();//记得加上蓝牙权限if (BluetoothHeadset.STATE_AUDIO_DISCONNECTED == defaultAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET)) {QToast.showToast("耳机未连接");mAudioManager.setSpeakerphoneOn(true);} else {QToast.showToast("耳机已连接");mAudioManager.setSpeakerphoneOn(false);}} else if (intent.hasExtra("state")) {if (intent.getIntExtra("state", 0) == 0) {QToast.showToast("耳机未连接");mAudioManager.setSpeakerphoneOn(true);} else {QToast.showToast("耳机已连接");mAudioManager.setSpeakerphoneOn(false);
​}}}}private void registerHeadsetReceiver() {mHeadSetReceiver = new HeadSetReceiver();IntentFilter intentFilter = new IntentFilter();intentFilter.addAction("android.intent.action.HEADSET_PLUG");registerReceiver(mHeadSetReceiver, intentFilter);IntentFilter bluetoothFilter = new IntentFilter(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);registerReceiver(mHeadSetReceiver, bluetoothFilter);}

(六)页面销毁时要释放资源(onDestroy)

if (mMediaPlayer != null) {mMediaPlayer.stop();mMediaPlayer.reset();mMediaPlayer.release();mMediaPlayer = null;}unregisterReceiver(mHeadSetReceiver);

到此为止,语音发送以及点击播放的功能就实现了,效果就是开头的两张静态图,因为没有什么花里胡哨的界面交互效果,所以就不放上gif效果了,感兴趣的小伙伴可以自己动手试一试,本项目的前后台均为本人完成,有疑惑的可以扫描下方二维码添加我微信,欢迎大家来与我交流Android前后台技术,大家共同进步!也欢迎大家订阅我的微信公众号(也是刚搞起来的),我会继续分享一些有趣的学习经历,最后,祝大家万事如意,身体健康,谢谢大家的支持与阅读!

Android实现语音发送播放功能以及示例代码相关推荐

  1. android播放mp3功能,Android Studio实现简单音乐播放功能的示例代码

    项目要求 基于Broadcast,BroadcastReceiver等与广播相关的知识实现简单的音乐播放功能,包括音乐的播放.暂停.切换.进度选择.音量调整. 设计效果 (进度条时间刷新功能还没有实现 ...

  2. android tun0 流量统计,Android应用流量统计——NetworkStatsManager使用(示例代码)

    在没有Root的情况下,Android应用流量统计在6.0之前一直没有太好的办法,官方虽然提供了TrafficStats,但其主要功能是设备启动以来流量的统计信息,和时间信息无法很好的配合.最近再看T ...

  3. 第三方登录android代码,Android Learning:微信第三方登录(示例代码)

    这两天,解决了微信第三方授权登录的问题,作为一个新手,想想也是一把辛酸泪.我想着,就把我的遇到的坑给大家分享一下,避免新手遇到我这样的问题能够顺利避开. 步骤一 微信开发者平台 我开始的解决思路是,去 ...

  4. 转:Android判断当前网络是否可用--示例代码

    在Android平台上开发基于网络的应用,必然需要去判断当前的网络连接情况.下面的代码,作为例子,详细说明了对于当前网络情况的判断. 先看一个自己定义的应用类. 源码copy to clipboard ...

  5. android相对布局代码,Android基础_3 Activity相对布局(示例代码)

    相对布局要比前面讲的线性布局和表格布局要灵活一些,所以平常用得也是比较多的.相对布局控件的位置是与其周围控件的位置相关的,从名字可以看出来,这些位置都是相对的,确定出了其中一个控件的位置就可以确定另一 ...

  6. android 实现 效果代码,Android实现雷达View效果的示例代码

    样式效果 还是先来看效果: 这是一个仿雷达扫描的效果,是之前在做地图sdk接入时就想实现的效果,但之前由于赶着毕业设计,就没有亲手去实现,不过现在自己撸一个发现还是挺简单的. 这里主要分享一下我的做法 ...

  7. android mysql代码_LitePal——Android数据库框架完整使用手册(示例代码)

    LitePal for Android LitePal是一个开源的Android库,使开发人员使用SQLite数据库非常简单.您无需编写任何SQL语句就可以完成大部分数据库操作,包括创建或升级表,增. ...

  8. cocos2d android 音乐,cocos2d-之音乐背景播放(示例代码)

    1.先加入头文件 #include using namespace CocosDenshion;//为了方便以下的函数使用,使用命名空间 2.在init()函数里面加入代码: //(有些新手)在运行以 ...

  9. Android运行ListView的代码,Android ListView组件详解及示例代码

    Android 列表组件 ListView 列表组件是开发中经常用到组件,使用该组件在使用时需要为它提供适配器,由适配器提供来确定显示样式和显示数据. 下面看一个例子: 新建一个项目Lesson8_L ...

最新文章

  1. mpython掌控板作品_mPython掌控板Easy-IoT物联
  2. Maven(七) maven 常用命令
  3. 测试如何开始像用户那样思考(译)
  4. Linux进程 管理,Linux进程查看与管理以及作业控制
  5. 算法总结之欧拉函数中国剩余定理
  6. java swing如何设置jtextarea对齐方式_【爵士钢琴】一次搞懂爵士经典Swing节奏!
  7. oracle在进行跨库访问时,采用dblink实现
  8. 管理分支:git branch
  9. 免费的文字转语音朗读 -API接口
  10. win7获取计算机管理员权限,Win7获取管理员权限的方法
  11. 深度linux双显卡死机,Deepin配置IntelNvidia双显卡
  12. Could not transfer artifact org.springframework.boot:spring-boot-starter-parent:pom:2.3.0.RELEAS错误解决
  13. 假AI?如何辨识 AI 界的snake oil
  14. 京东云服务器搭建mysql+jdk+tomact
  15. 工作用哪个邮箱好用?好用的办公邮箱让你放假无烦恼
  16. 微操作、微命令、微指令、微程序、微周期、微地址
  17. 桥接模式: Bridge
  18. python批量删除图片和空文件夹
  19. 国产32位单片机使用-APT32F102x
  20. 数据库设计之冗余字段

热门文章

  1. 使用WebBrowser控件实现打印 去掉 页眉和页脚
  2. 如何卸载360安全客户端
  3. Preparing wheel metadata ... error
  4. leaflet地图资源整理
  5. hp服务器装系统键盘没反应,u盘装系统f12没反应怎么回事|u盘装系统按f12没反应怎么解决...
  6. STM8L101x驱动ADS1256
  7. 绝地求生发生错误服务器维护,绝地求生更新时发生错误无法连接服务器解决办法最新版...
  8. 数据结构学习——RBT(红黑树)以及实现Map和Set
  9. 化合物分子 ogb、dgl生成图网络及GNN模型训练;pgl图框架
  10. 论文阅读——Deep 3D Portrait from a Single Image(CVPR2020)