仿微信语音消息的录制和播放

一、简述

效果:

实现功能:

  1. 长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音;
  2. 监听手指动作,规定区域。录音状态下手指划出规定区域取消录音,删除生成的录音文件;
  3. 监听手指动作。当手指抬起时,判断是否开始录音,录音时长是否过短,符合条件则提示录音时长过短;正常结束时通过回调返回该次录音的文件路径和时长。
    4.点击录音列表的item时,播放动画,播放对应的音频文件。

主要用到4个核心类:

  1. 自定义录音按钮(AudioRecordButton);
  2. 弹框管理类(DialogManager);
  3. 录音管理类(AudioManager)。

1.AudioRecordButton状态:

  • 1.STATE_NORMAL:普通状态
  • 2.STATE_RECORDING:录音中
  • 3.STATE_CANCEL:取消录音

2.DialogManager状态:

  • 1.RECORDING:录音中
  • 2.WANT_TO_CANCEL:取消录音
  • 3.TOO_SHORT:录音时间太短

3.AudioManager:

  • 1.prepare():准备状态
  • 2.cancel():取消录音
  • 3.release():正常结束录音
  • 4.getVoiceLevel():获取音量

核心逻辑:

自定义Button,重写onTouchEvent()方法。

伪代码:

class AudioRecorderButton{onTouchEvent(){DOWN:changeButtonState(STATE_RECORDING);| DialogManager.showDialog(RECORDING)触发LongClick事件(AudioManager.prepare() --> end prepared -->  |                                                     );| getVoiceLevel();//开启一个线程,更新Dialog上的音量等级 MOVE:if(wantCancel(x,y)){DialogManager.showDialog(WANT_TO_CANCEL);更新DialogchangeButtonState(STATE_WANT_TO_CANCEL);更新Button状态}else{DialogManager.showDialog(WANT_TO_CANCEL);changeButtonState(STATE_RECORDING);}UP:if(wantCancel == curState){//当前状态是想取消状态AudioManager.cancel();}if(STATE_RECORDING = curState){if(tooShort){//判断录制时长,如果录制时间过短DialogManager.showDialog(TOO_SHORT);}AudioManager.release();callbackActivity(url,time);//(当前录音文件路径,时长)}}}

二、MediaManager封装

简述:使用MediaPlayer播放录制好的音频文件,要注意MediaPlayer资源的释放。更多关于MediaPlayer的知识参考【MediaPlayer】的使用。点击此处跳转

代码:

import android.media.*;import java.io.IOException;/*** 播放管理类*/
public class MediaManager {private static MediaPlayer mMediaPlayer;private static boolean isPause;public static void playSound(String filePath, MediaPlayer.OnCompletionListener onCompletionListener) {if (mMediaPlayer == null) {mMediaPlayer = new MediaPlayer();mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {mMediaPlayer.reset();return false;}});} else {mMediaPlayer.reset();}try {mMediaPlayer.setAudioStreamType(android.media.AudioManager.STREAM_MUSIC);mMediaPlayer.setOnCompletionListener(onCompletionListener);mMediaPlayer.setDataSource(filePath);mMediaPlayer.prepare();mMediaPlayer.start();} catch (IOException e) {e.printStackTrace();}}public static void pause(){if(mMediaPlayer != null && mMediaPlayer.isPlaying()){mMediaPlayer.pause();isPause = true;}}public static void resume(){if(mMediaPlayer != null && isPause){mMediaPlayer.start();isPause = false;}}public static void release(){if(mMediaPlayer != null){mMediaPlayer.release();mMediaPlayer = null;}}
}

三、DialogManager封装

封装了6个方法:

1. showRecordingDialog():用来设置Diaog布局,拿到控件的引用,显示Dialog。
2. recording():更改Dialog状态为录音中状态。
3. wantToCancel():更改Dialog状态为想要取消状态。
4. tooShort():更改Dialog状态为录音时长过短状态。
5. dismissDialog():移除Dialog。
6. updateVoiceLevel():用来更新音量图片。

代码:

import android.app.Dialog;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;import com.tiddlerliu.wxrecorder.R;/*** Dialog管理类*/
public class DialogManager {private Dialog mDialog;private ImageView mIcon;private ImageView mVoice;private TextView mLabel;private Context mContext;public DialogManager(Context context) {mContext = context;}/*** 显示Dialog*/public void showRecordingDialog(){//将布局应用于DialogmDialog = new Dialog(mContext, R.style.Theme_AudioDialog);LayoutInflater inflater = LayoutInflater.from(mContext);View view = inflater.inflate(R.layout.dialog_recorder,null);mDialog.setContentView(view);//成员控件赋值mIcon = (ImageView) mDialog.findViewById(R.id.recorder_dialog_icon);mVoice = (ImageView) mDialog.findViewById(R.id.recorder_dialog_voice);mLabel = (TextView) mDialog.findViewById(R.id.recorder_dialog_label);mDialog.show();}public void recording(){if(mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.VISIBLE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.mipmap.recorder);mLabel.setText("手指上滑,取消发送");}}public void wantToCancel(){if(mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.mipmap.cancel);mLabel.setText("松开手指,取消发送");}}public void tooShort(){if(mDialog != null && mDialog.isShowing()){mIcon.setVisibility(View.VISIBLE);mVoice.setVisibility(View.GONE);mLabel.setVisibility(View.VISIBLE);mIcon.setImageResource(R.mipmap.voice_to_short);mLabel.setText("录音时间过短");}}public void dismissDialog(){if(mDialog != null && mDialog.isShowing()){mDialog.dismiss();mDialog = null;}}/*** 通过level更新音量资源图片* @param level*/public void updateVoiceLevel(int level){if(mDialog != null && mDialog.isShowing()){int resId = mContext.getResources().getIdentifier("v"+level,"mipmap",mContext.getPackageName());mVoice.setImageResource(resId);}}
}

四、AudioManager封装

4.1 添加必要权限

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

4.2 代码

import android.media.MediaRecorder;import java.io.File;
import java.io.IOException;
import java.util.UUID;/*** 录音管理类*/
public class AudioManager {private String mDir;//文件夹名称private MediaRecorder mMediaRecorder;private String mCurrentFilePath;//文件储存路径private static AudioManager mInstance;//表明MediaRecorder是否进入prepare状态(状态为true才能调用stop和release方法)private boolean isPrepared;public AudioManager(String dir) {mDir = dir;}public String getCurrentFilePath() {return mCurrentFilePath;}/*** 准备完毕接口*/public interface  AudioStateListener{void wellPrepared();}public AudioStateListener mListener;public void setOnAudioStateListener(AudioStateListener listener){mListener = listener;}/*** 单例* @return AudioManager*/public static AudioManager getInstance(String dir){if (mInstance == null){synchronized (AudioManager.class){if(mInstance == null){mInstance = new AudioManager(dir);}}}return mInstance;}/*** 准备*/public void prepareAudio() {try {isPrepared = false;File dir = new File(mDir);//创建文件夹if (!dir.exists()) {dir.mkdirs();}String fileName = generateFileName();//随机生成文件名File file = new File(dir, fileName);//创建文件mCurrentFilePath = file.getAbsolutePath();mMediaRecorder = new MediaRecorder();mMediaRecorder.setOutputFile(file.getAbsolutePath());//设置输出文件mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置麦克风为音频源mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB);//设置音频格式mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);//设置音频编码mMediaRecorder.prepare();mMediaRecorder.start();//准备结束isPrepared = true;if (mListener != null){mListener.wellPrepared();}} catch (IOException e) {e.printStackTrace();}}/*** 随机生成文件的名称* @return*/private String generateFileName() {return UUID.randomUUID().toString()+".amr";}/*** 获取音量等级*/public int getVoiceLevel(int maxLevel) {if (isPrepared) {try {//mMediaRecorder.getMaxAmplitude()  范围:1-32767return maxLevel * mMediaRecorder.getMaxAmplitude() / 32768 + 1;//最大值 * [0,1)+ 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);file.delete();mCurrentFilePath = null;}}}

五、AudioRecordButton封装

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;import com.tiddlerliu.wxrecorder.R;/*** 自定义Button*/
@SuppressLint("AppCompatCustomView")
public class AudioRecordButton extends Button implements AudioManager.AudioStateListener{private static final int STATE_NORMAL = 1;//默认状态private static final int STATE_RECORDING = 2;//录音状态private static final int STATE_WANT_CANCEL = 3;//想取消状态private static final int DISTANCE_Y_CANCEL = 50;//定义上滑取消距离private int mCurState = STATE_NORMAL;//记录当前状态private boolean isRecording = false;//是否在录音状态private DialogManager mDialogManager;private AudioManager mAudioManager;private float mTime;//记录录音时长private boolean mReady;//是否触发OnLongClick事件private boolean isComplete = true;//是否已经完成public AudioRecordButton(Context context) {this(context,null);}public AudioRecordButton(Context context, AttributeSet attrs) {super(context, attrs);mDialogManager = new DialogManager(getContext());String dir = Environment.getExternalStorageDirectory()+"/TiddlerLiu/recorder/audios";//最好判断SD卡是否存在可读mAudioManager = AudioManager.getInstance(dir);mAudioManager.setOnAudioStateListener(this);setOnLongClickListener(new OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {mReady = true;mAudioManager.prepareAudio();return false;}});}/*** 录音完成后的回调*/public interface AudioFinishRecorderListener {void onFinish(float seconds,String filePath);}private AudioFinishRecorderListener mAudioFinishRecorderListener;public void setAudioFinishRecorderListener(AudioFinishRecorderListener listener){mAudioFinishRecorderListener = listener;}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_AUDIO_COMPLETE = 0x113;//达到最大时长,自动完成/*** 获取音量大小*/private Runnable mGetVoiceLevelRunnable = new Runnable() {@Overridepublic void run() {while (isRecording){try {Thread.sleep(100);mTime += 0.1f;if(mTime >= 60f){//60s自动触发完成录制mHandler.sendEmptyMessage(MSG_AUDIO_COMPLETE);}mHandler.sendEmptyMessage(MSG_VOICE_CHANGED);} catch (InterruptedException e) {e.printStackTrace();}}}};private Handler mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {switch (msg.what){case MSG_AUDIO_PREPARED://显示应该在audio end prepared以后mDialogManager.showRecordingDialog();isRecording = true;isComplete = false;new Thread(mGetVoiceLevelRunnable).start();break;case MSG_VOICE_CHANGED:mDialogManager.updateVoiceLevel(mAudioManager.getVoiceLevel(7));break;case MSG_DIALOG_DISMISS:mDialogManager.dismissDialog();break;case MSG_AUDIO_COMPLETE:complete();reset();break;default:break;}}};@Overridepublic void wellPrepared() {mHandler.sendEmptyMessage(MSG_AUDIO_PREPARED);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int action = event.getAction();int x = (int) event.getX();int y = (int) event.getY();switch (action){case MotionEvent.ACTION_DOWN:changeState(STATE_RECORDING);break;case MotionEvent.ACTION_MOVE:if(isRecording){//根据(x,y)坐标,判断是否想要取消if (wantToCancel(x,y)){changeState(STATE_WANT_CANCEL);}else{changeState(STATE_RECORDING);}}break;case MotionEvent.ACTION_UP:if(!isComplete){//没有执行超时自动完成逻辑if (!mReady) {//还未触发OnLongClick事件reset();return super.onTouchEvent(event);}if (!isRecording || mTime < 0.6f) {//还未开始录音  或者  录制时长过短mDialogManager.tooShort();mAudioManager.cancel();mHandler.sendEmptyMessageDelayed(MSG_DIALOG_DISMISS, 1300);//1.3秒后关闭对话框} else if (mCurState == STATE_RECORDING) {//正常录制结束complete();} else if (mCurState == STATE_WANT_CANCEL) {//想要取消状态mDialogManager.dismissDialog();mAudioManager.cancel();}reset();}break;}return super.onTouchEvent(event);}/*** 正常录制结束*/private void complete() {mDialogManager.dismissDialog();mAudioManager.release();if(mAudioFinishRecorderListener != null  && !isComplete){mAudioFinishRecorderListener.onFinish(mTime,mAudioManager.getCurrentFilePath());}}/*** 恢复状态和标志位*/private void reset() {isRecording = false;mReady = false;mTime = 0;isComplete = true;changeState(STATE_NORMAL);}/*** 根据(x,y)坐标,判断是否想要取消* @param x* @param y* @return*/private boolean wantToCancel(int x, int y) {if(x < 0 || x > getWidth()){//手指移出button范围return true;}if(y < - DISTANCE_Y_CANCEL || y > getHeight() + DISTANCE_Y_CANCEL){//手指移出Y轴设定范围return true;}return false;}/*** 改变状态* @param state*/private void changeState(int state) {if(mCurState != state){mCurState = state;switch (state){case STATE_NORMAL:setBackgroundResource(R.drawable.btn_recorder_normal);setText(R.string.str_recorder_normal);break;case STATE_RECORDING:setBackgroundResource(R.drawable.btn_recorder_recording);setText(R.string.str_recorder_recording);if(isRecording){mDialogManager.recording();}break;case STATE_WANT_CANCEL:setBackgroundResource(R.drawable.btn_recorder_recording);setText(R.string.str_recorder_want_cancel);mDialogManager.wantToCancel();break;default:break;}}}}

六、 主界面实现

6.1 adapter

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.ArrayAdapter;
import android.widget.TextView;import com.tiddlerliu.wxrecorder.R;
import com.tiddlerliu.wxrecorder.model.Recorder;import java.util.List;public class RecorderAdapter extends ArrayAdapter<Recorder>{private int mMinItemWidth;private int mMaxItemWidth;private LayoutInflater mInflater;public RecorderAdapter(@NonNull Context context, List<Recorder> datas) {super(context, -1 ,datas);mInflater = LayoutInflater.from(context);//获取屏幕参数WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics outMetrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(outMetrics);//设置最小宽度和最大宽度mMinItemWidth = (int) (outMetrics.widthPixels * 0.16f);mMaxItemWidth = (int) (outMetrics.widthPixels * 0.64f);}@NonNull@Overridepublic View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {ViewHolder holder = null;if(convertView == null){convertView = mInflater.inflate(R.layout.item_recorder,parent,false);holder = new ViewHolder();holder.seconds = (TextView) convertView.findViewById(R.id.item_recorder_time);holder.length = convertView.findViewById(R.id.item_recorder_length);convertView.setTag(holder);}else {holder = (ViewHolder) convertView.getTag();}//设置时长holder.seconds.setText(Math.round(getItem(position).getTime())+ "\"");//根据时长按比例设置时长ViewGroup.LayoutParams lp = holder.length.getLayoutParams();lp.width = (int) (mMinItemWidth + (mMaxItemWidth/60f * getItem(position).getTime()));return convertView;}private class ViewHolder{TextView seconds;View length;}
}

6.2 activity

import android.graphics.drawable.AnimationDrawable;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;import com.tiddlerliu.wxrecorder.CustomView.AudioRecordButton;
import com.tiddlerliu.wxrecorder.CustomView.MediaManager;
import com.tiddlerliu.wxrecorder.adapter.RecorderAdapter;
import com.tiddlerliu.wxrecorder.model.Recorder;import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity {private ListView mListView;private AudioRecordButton mAudioRecordButton;private ArrayAdapter<Recorder> mAdapter ;private List<Recorder> mDatas = new ArrayList<>();private View mAnimView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mListView = (ListView) findViewById(R.id.recorder_list);mAudioRecordButton = (AudioRecordButton) findViewById(R.id.recorder_button);mAudioRecordButton.setAudioFinishRecorderListener(new AudioRecordButton.AudioFinishRecorderListener() {@Overridepublic void onFinish(float seconds, String filePath) {Recorder recorder = new Recorder(seconds,filePath);mDatas.add(recorder);mAdapter.notifyDataSetChanged();mListView.setSelection(mDatas.size()-1);}});mAdapter = new RecorderAdapter(this,mDatas);mListView.setAdapter(mAdapter);mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {if(mAnimView != null){mAnimView.setBackgroundResource(R.mipmap.adj);mAnimView = null;}//播放动画mAnimView = view.findViewById(R.id.item_recorder_anim);mAnimView.setBackgroundResource(R.drawable.play_ainm);AnimationDrawable anim = (AnimationDrawable) mAnimView.getBackground();anim.start();//播放音频MediaManager.playSound(mDatas.get(position).getFilePath(), new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mAnimView.setBackgroundResource(R.mipmap.adj);}});}});}@Overrideprotected void onPause() {super.onPause();MediaManager.pause();}@Overrideprotected void onResume() {super.onResume();MediaManager.resume();}@Overrideprotected void onDestroy() {super.onDestroy();MediaManager.release();}
}

仿微信语音消息的录制和播放相关推荐

  1. android放微信@功能,Android仿微信语音消息的录制和播放功能

    一.简述 效果: 实现功能: 长按Button时改变Button显示文字,弹出Dialog(动态更新音量),动态生成录音文件,开始录音: 监听手指动作,规定区域.录音状态下手指划出规定区域取消录音,删 ...

  2. android 融云 + 科大讯飞 实现仿微信语音消息转换为文字(附DEMO源码)

    融云SDK 使用很方便,简单配置就可以搭建即时通讯功能,配合科大讯飞的语音识别, 即可实现微信中语音消息转换为文字的功能 融云sdk的基本使用就不细说了, 网上很多资料 使用融云sdk自带的聊天会话界 ...

  3. Androidpcm格式音频编解码及播放,socket文件传输通讯,soundTouch变声框架 合成的一款仿微信语音按住说话的demo

    pcm格式音频编解码及播放,socket文件传输通讯,soundTouch变声框架 合成的一款仿微信语音按住播放的demo 效果:   -------------- 代码: MainActivity ...

  4. php仿微信语音条,html5的audio实现高仿微信语音播放效果

    前言 之前做过一个微信的项目,专家回复可以录音,然后储存成mp3格式,前台可以获取mp3,客户可以在线试听mp3录音效果,今天就简单分享一下这个效果如何实现,及实现思路和方法! 效果图 前台大体呈现效 ...

  5. html5的audio实现高仿微信语音播放效果

    效果图 前台大体呈现效果图如下: 点击就可以播放mp3格式的录音.点击另外一个录音,当前录音停止! 思路 关于播放动画,这个很简单,我们可以用css3的逐帧动画来实现.关于逐帧动画,我之前的文章也写过 ...

  6. html5语音即时聊天|仿微信语音效果|表情|定位

    h5+css3语音实时聊天系统|仿微信语音聊天|js音频录制|仿微信地图定位 基于html5开发的仿微信语音聊天系统weChatIM项目,采用了h5+css3+jQuery+swiper+wcPop+ ...

  7. java实现仿微信app聊天功能_Android仿微信语音聊天功能

    本文实例讲述了Android仿微信语音聊天功能代码.分享给大家供大家参考.具体如下: 项目效果如下: 具体代码如下: AudioManager.java package com.xuliugen.we ...

  8. html仿微信语音功能案例,html5聊天案例|趣聊h5|仿微信界面聊天|红包|语音聊天|地图...

    之前有开发过一个h5微直播项目,当时里面也用到过聊天模块部分,今天就在之前聊天部分的基础上重新抽离模块,开发了这个h5趣聊项目,功能效果比较类似微信聊天界面.采用html5+css3+Zepto+sw ...

  9. h5语音聊天audio实战|仿微信语音效果|h5即时聊天系统

    最近一段时间不是那么忙,就抽空整理了下之前的项目,因为之前有开发过H5聊天项目,只是觉得好些功能都没有特别的完善,所以就把之前项目重新开发了下,如是就有了这个html5版实时聊天语音项目weChatI ...

最新文章

  1. Java并发编程:线程封闭和ThreadLocal详解
  2. 移动端material风格日期时间选择器
  3. 牛客题霸 [ 在二叉树中找到两个节点的最近公共祖先] C++题解/答案
  4. 工作194:vue.runtime.esm.js?2b0e:619 [Vue warn]: Duplicate keys detected: ‘/system‘. This may cause an
  5. selectByExample和selectByExampleWithBLOBs的区别
  6. 计算机教育应用3t模式,开题报告样例--初中语文说明文阅读教学整合模式研究.doc...
  7. python 遍历一个空列表
  8. lnmp改php版本,lnmp安装多版本PHP共存的方法详解
  9. SAP屏幕设计器专题:表格控件属性的设定(七)
  10. 小白教程:如何在MAC菜单栏上显示日期和时间
  11. python 特征选择卡方_为什么pythonsklearn特征选择卡方(chi2)测试不是对称的?
  12. sqlite3 命令行操作
  13. 中国铁路车次编号规则
  14. Pr 水墨动画转场效果
  15. Android App开发实战之实现微信记账本(附源码 超详细必看)
  16. IT30知识星球—工作报告7步曲
  17. C语言:srand函数与rand函数的使用(纯干货)【易懂】
  18. JavaScript slice( )、splice( )、split( )
  19. 五、T100采购应付之应付账款核销管理篇
  20. python数据分析的一般步骤_50. Python 数据处理(1)

热门文章

  1. 全面屏大战 夏普手机为别人做嫁衣
  2. 解决电脑无法休眠的问题如下
  3. Java操作磁盘文件
  4. 将1~n的数排成最大堆
  5. python报错:AttributeError: module ‘jieba‘ has no attribute ‘analyse‘
  6. 【1】C#下的Directshow
  7. C# Microsoft.Office.Interop.Excel 引用的用法
  8. android 多级下拉菜单实现教程
  9. 在我的工程中移植文件系统--FATFS v14
  10. 华中科技大学计算机科学与技术学院实验班,计算机卓越计划实验班