Android仿微信录音功能
程序员文章站
2023-11-09 16:39:22
提要:需求是开发类似微信发语音的功能,没有语音转文字。网上看了一些代码,不能拿来直接用,部分代码逻辑有问题,所以想把自己的代码贴出来,仅供参考。
功能:
a、设置最大录音时长和录音...
提要:需求是开发类似微信发语音的功能,没有语音转文字。网上看了一些代码,不能拿来直接用,部分代码逻辑有问题,所以想把自己的代码贴出来,仅供参考。
功能:
a、设置最大录音时长和录音倒计时(为了方便测试,最大时长设置为15秒,开始倒计时设置为7秒)
b、在录音之前检查录音和存储权限
源码:
1、录音对话框管理类dialogmanager:
/** * 功能:录音对话框管理类 */ public class dialogmanager { private alertdialog.builder builder; private alertdialog dialog; private imageview micon; private imageview mvoice; private textview mlabel; private context context; /** * 构造方法 * * @param context activity级别的context */ public dialogmanager(context context) { this.context = context; } /** * 显示录音的对话框 */ public void showrecordingdialog() { builder = new alertdialog.builder(context, r.style.audiorecorderdialogstyle); layoutinflater inflater = layoutinflater.from(context); view view = inflater.inflate(r.layout.audio_recorder_dialog, null); micon = view.findviewbyid(r.id.iv_dialog_icon); mvoice = view.findviewbyid(r.id.iv_dialog_voice); mlabel = view.findviewbyid(r.id.tv_dialog_label); builder.setview(view); dialog = builder.create(); dialog.show(); dialog.setcanceledontouchoutside(false); } /** * 正在播放时的状态 */ public void recording() { if (dialog != null && dialog.isshowing()) { //显示状态 micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); micon.setimageresource(r.drawable.ic_audio_recorder); mvoice.setimageresource(r.drawable.ic_audio_v1); mlabel.settext(r.string.audio_record_dialog_up_to_cancel); } } /** * 显示想取消的对话框 */ public void wanttocancel() { if (dialog != null && dialog.isshowing()) { //显示状态 micon.setvisibility(view.visible); mvoice.setvisibility(view.gone); mlabel.setvisibility(view.visible); micon.setimageresource(r.drawable.ic_audio_cancel); mlabel.settext(r.string.audio_record_dialog_release_to_cancel); } } /** * 显示时间过短的对话框 */ public void tooshort() { if (dialog != null && dialog.isshowing()) { //显示状态 micon.setvisibility(view.visible); mvoice.setvisibility(view.gone); mlabel.setvisibility(view.visible); mlabel.settext(r.string.audio_record_dialog_too_short); } } // 显示取消的对话框 public void dismissdialog() { if (dialog != null && dialog.isshowing()) { //显示状态 dialog.dismiss(); dialog = null; } } /** * 显示更新音量级别的对话框 * * @param level 1-7 */ public void updatevoicelevel(int level) { if (dialog != null && dialog.isshowing()) { //显示状态 micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); int resid = context.getresources().getidentifier("ic_audio_v" + level, "drawable", context.getpackagename()); mvoice.setimageresource(resid); } } public void updatetime(int time) { if (dialog != null && dialog.isshowing()) { //显示状态 micon.setvisibility(view.visible); mvoice.setvisibility(view.visible); mlabel.setvisibility(view.visible); mlabel.settext(time + "s"); } } }
2、录音管理类audiomanager
/** * 功能:录音管理类 */ public class audiomanager { private mediarecorder mmediarecorder; private string mdir; private string mcurrentfilepath; private static audiomanager minstance; private boolean isprepared; private audiomanager(string dir) { this.mdir = dir; } //单例模式:在这里实例化audiomanager并传入录音文件地址 public static audiomanager getinstance(string dir) { if (minstance == null) { synchronized (audiomanager.class) { if (minstance == null) { minstance = new audiomanager(dir); } } } return minstance; } /** * 回调准备完毕 */ public interface audiostatelistener { void wellprepared(); } public audiostatelistener mlistener; /** * 回调方法 */ public void setonaudiostatelistener(audiostatelistener listener) { mlistener = listener; } /** * 准备 */ public void prepareaudio() { try { isprepared = false; file dir = fileutils.createnewfile(mdir); string filename = generatefilename(); file file = new file(dir, filename); mcurrentfilepath = file.getabsolutepath(); logger.t("audiomanager").i("audio file name :" + mcurrentfilepath); mmediarecorder = new mediarecorder(); //设置输出文件 mmediarecorder.setoutputfile(mcurrentfilepath); //设置mediarecorder的音频源为麦克风 mmediarecorder.setaudiosource(mediarecorder.audiosource.mic); //设置音频格式 mmediarecorder.setoutputformat(mediarecorder.outputformat.mpeg_4); //设置音频的格式为aac mmediarecorder.setaudioencoder(mediarecorder.audioencoder.aac); //准备录音 mmediarecorder.prepare(); //开始 mmediarecorder.start(); //准备结束 isprepared = true; if (mlistener != null) { mlistener.wellprepared(); } } catch (exception e) { e.printstacktrace(); } } /** * 随机生成文件的名称 */ private string generatefilename() { return uuid.randomuuid().tostring() + ".m4a"; } public int getvoicelevel(int maxlevel) { if (isprepared) { try { //获得最大的振幅getmaxamplitude() 1-32767 return maxlevel * mmediarecorder.getmaxamplitude() / 32768 + 1; } catch (exception e) { } } return 1; } /** * 释放资源 */ public void release() { if (mmediarecorder != null) { mmediarecorder.stop(); mmediarecorder.release(); mmediarecorder = null; } } public void cancel() { release(); if (mcurrentfilepath != null) { file file = new file(mcurrentfilepath); fileutils.deletefile(file); mcurrentfilepath = null; } } public string getcurrentfilepath() { return mcurrentfilepath; } }
3、自定义录音按钮audiorecorderbutton
/** * 功能:录音按钮 */ public class audiorecorderbutton extends appcompatbutton { private context mcontext; //取消录音y轴位移 private static final int distance_y_cancel = 80; //录音最大时长限制 private static final int audio_recorder_max_time = 15; //录音倒计时时间 private static final int audio_recorder_count_down = 7; //状态 private static final int state_normal = 1;// 默认的状态 private static final int state_recording = 2;// 正在录音 private static final int state_want_to_cancel = 3;// 希望取消 //当前的状态 private int mcurrentstate = state_normal; //已经开始录音 private boolean isrecording = false; //是否触发onlongclick private boolean mready; private dialogmanager mdialogmanager; private audiomanager maudiomanager; private android.media.audiomanager audiomanager; public audiorecorderbutton(context context) { this(context, null); } public audiorecorderbutton(context context, attributeset attrs) { super(context, attrs); this.mcontext = context; mdialogmanager = new dialogmanager(context); audiomanager = (android.media.audiomanager) context.getsystemservice(context.audio_service); string dir = sdutils.getcustomfolder("audios");//创建文件夹 maudiomanager = audiomanager.getinstance(dir); maudiomanager.setonaudiostatelistener(new audiomanager.audiostatelistener() { @override public void wellprepared() { mhandler.sendemptymessage(msg_audio_prepared); } }); //按钮长按 准备录音 包括start setonlongclicklistener(new onlongclicklistener() { @override public boolean onlongclick(view v) { //先判断有没有录音和存储权限,有则开始录音,没有就申请权限 int hasaudiopermission = contextcompat.checkselfpermission(mcontext, manifest.permission.record_audio); int hasstoragepermission = contextcompat.checkselfpermission(mcontext, manifest.permission.write_external_storage); if (hasaudiopermission == packagemanager.permission_granted && hasstoragepermission == packagemanager.permission_granted) { mready = true; maudiomanager.prepareaudio(); } else { rxpermissions permissions = new rxpermissions((fragmentactivity) mcontext); disposable disposable = permissions.request(manifest.permission.record_audio, manifest.permission.write_external_storage) .subscribe(new consumer<boolean>() { @override public void accept(boolean granted) { if (!granted) { toastutils.showshort("发送语音功能需要赋予录音和存储权限"); } } }); } return true; } }); } private static final int msg_audio_prepared = 0x110; private static final int msg_voice_changed = 0x111; private static final int msg_dialog_dismiss = 0x112; private static final int msg_time_out = 0x113; private static final int update_time = 0x114; private boolean mthreadflag = false; //录音时长 private float mtime; //获取音量大小的runnable private runnable mgetvoicelevelrunnable = new runnable() { @override public void run() { while (isrecording) { try { thread.sleep(100); mtime += 0.1f; mhandler.sendemptymessage(msg_voice_changed); if (mtime >= audio_recorder_max_time) {//如果时间超过60秒,自动结束录音 while (!mthreadflag) {//记录已经结束了录音,不需要再次结束,以免出现问题 mdialogmanager.dismissdialog(); maudiomanager.release(); if (audiofinishrecorderlistener != null) { //先回调,再reset,不然回调中的时间是0 audiofinishrecorderlistener.onfinish(mtime, maudiomanager.getcurrentfilepath()); mhandler.sendemptymessage(msg_time_out); } mthreadflag = !mthreadflag; } isrecording = false; } else if (mtime >= audio_recorder_count_down) { mhandler.sendemptymessage(update_time); } } catch (interruptedexception e) { e.printstacktrace(); } } } }; private handler mhandler = new handler(new handler.callback() { @override public boolean handlemessage(message msg) { switch (msg.what) { case msg_audio_prepared: mdialogmanager.showrecordingdialog(); isrecording = true; new thread(mgetvoicelevelrunnable).start(); break; case msg_voice_changed: mdialogmanager.updatevoicelevel(maudiomanager.getvoicelevel(7)); break; case msg_dialog_dismiss: mdialogmanager.dismissdialog(); break; case msg_time_out: reset(); break; case update_time: int countdown = (int) (audio_recorder_max_time - mtime); mdialogmanager.updatetime(countdown); break; } return true; } }); /** * 录音完成后的回调 */ public interface audiofinishrecorderlistener { /** * @param seconds 时长 * @param filepath 文件 */ void onfinish(float seconds, string filepath); } private audiofinishrecorderlistener audiofinishrecorderlistener; public void setaudiofinishrecorderlistener(audiofinishrecorderlistener listener) { audiofinishrecorderlistener = listener; } android.media.audiomanager.onaudiofocuschangelistener onaudiofocuschangelistener = new android.media.audiomanager.onaudiofocuschangelistener() { @override public void onaudiofocuschange(int focuschange) { if (focuschange == android.media.audiomanager.audiofocus_loss) { audiomanager.abandonaudiofocus(onaudiofocuschangelistener); } } }; public void myrequestaudiofocus() { audiomanager.requestaudiofocus(onaudiofocuschangelistener, android.media.audiomanager.stream_music, android.media.audiomanager.audiofocus_gain_transient); } @override public boolean ontouchevent(motionevent event) { logger.t("audiomanager").i("x :" + event.getx() + "-y:" + event.gety()); switch (event.getaction()) { case motionevent.action_down: mthreadflag = false; isrecording = true; changestate(state_recording); myrequestaudiofocus(); break; case motionevent.action_move: if (isrecording) { //根据想x,y的坐标,判断是否想要取消 if (event.gety() < 0 && math.abs(event.gety()) > distance_y_cancel) { changestate(state_want_to_cancel); } else { changestate(state_recording); } } break; case motionevent.action_up: //如果longclick 没触发 if (!mready) { reset(); return super.ontouchevent(event); } //触发了onlongclick 没准备好,但是已经prepared已经start //所以消除文件夹 if (!isrecording || mtime < 1.0f) { mdialogmanager.tooshort(); maudiomanager.cancel(); mhandler.sendemptymessagedelayed(msg_dialog_dismiss, 1000); } else if (mcurrentstate == state_recording) {//正常录制结束 mdialogmanager.dismissdialog(); maudiomanager.release(); if (audiofinishrecorderlistener != null) { audiofinishrecorderlistener.onfinish(mtime, maudiomanager.getcurrentfilepath()); } } else if (mcurrentstate == state_want_to_cancel) { mdialogmanager.dismissdialog(); maudiomanager.cancel(); } reset(); audiomanager.abandonaudiofocus(onaudiofocuschangelistener); break; } return super.ontouchevent(event); } /** * 恢复状态 标志位 */ private void reset() { isrecording = false; mtime = 0; mready = false; changestate(state_normal); } /** * 改变状态 */ private void changestate(int state) { if (mcurrentstate != state) { mcurrentstate = state; switch (state) { case state_normal: settext(r.string.audio_record_button_normal); break; case state_recording: if (isrecording) { mdialogmanager.recording(); } settext(r.string.audio_record_button_recording); break; case state_want_to_cancel: mdialogmanager.wanttocancel(); settext(r.string.audio_record_button_cancel); break; } } } }
4、dialogstyle
<!--app base theme--> <style name="appthemeparent" parent="theme.appcompat.light.noactionbar"> <!--不显示状态栏:22之前--> <item name="android:windownotitle">true</item> <item name="android:windowanimationstyle">@style/activityanimtheme</item><!--activity动画--> <item name="actionoverflowmenustyle">@style/menustyle</item><!--toolbar菜单样式--> </style> <!--dialog式的activity--> <style name="activitydialogstyle" parent="appthemeparent"> <item name="android:windowbackground">@android:color/transparent</item> <!-- 浮于activity之上 --> <item name="android:windowisfloating">true</item> <!-- 边框 --> <item name="android:windowframe">@null</item> <!-- dialog以外的区域模糊效果 --> <item name="android:backgrounddimenabled">true</item> <!-- 半透明 --> <item name="android:windowistranslucent">true</item> <!-- dialog进入及退出动画 --> <item name="android:windowanimationstyle">@style/activitydialoganimation</item> </style> <!--audio recorder dialog--> <style name="audiorecorderdialogstyle" parent="activitydialogstyle"> <!-- dialog以外的区域模糊效果 --> <item name="android:backgrounddimenabled">false</item> </style> <!-- dialog动画:渐入渐出--> <style name="activitydialoganimation" parent="@android:style/animation.dialog"> <item name="android:windowenteranimation">@anim/fade_in</item> <item name="android:windowexitanimation">@anim/fade_out</item> </style>
5、dialoglayout
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/audio_recorder_dialog_bg" android:gravity="center" android:orientation="vertical" android:padding="20dp"> <linearlayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <imageview android:id="@+id/iv_dialog_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_audio_recorder" /> <imageview android:id="@+id/iv_dialog_voice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_audio_v1" /> </linearlayout> <textview android:id="@+id/tv_dialog_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margintop="15dp" android:text="@string/audio_record_dialog_up_to_cancel" android:textcolor="@color/white" android:textsize="15dp" /> </linearlayout>
6、用到的字符串
<!--audiorecord--> <string name="audio_record_button_normal">按住 说话</string> <string name="audio_record_button_recording">松开 结束</string> <string name="audio_record_button_cancel">松开手指 取消发送</string> <string name="audio_record_dialog_up_to_cancel">手指上划,取消发送</string> <string name="audio_record_dialog_release_to_cancel">松开手指,取消发送</string> <string name="audio_record_dialog_too_short">录音时间过短</string>
7、使用:按钮的样式不需要写在自定义button中,方便使用
<com.kidney.base_library.view.audiorecorder.audiorecorderbutton android:id="@+id/btn_audio_recorder" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/audio_record_button_normal" /> audiorecorderbutton audiorecorderbutton = findviewbyid(r.id.btn_audio_recorder); audiorecorderbutton.setaudiofinishrecorderlistener(new audiorecorderbutton.audiofinishrecorderlistener() { @override public void onfinish(float seconds, string filepath) { logger.i(seconds + "秒:" + filepath); } });
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。