这两个在集成环信实现IM功能,在开发过程中遇到一个问题,发送文字、表情、图片、位置都是正常的,但是语音就是一直发送不成功,并且只是在第一次安装app登录账号之后发送语音的时候才会出现发送不成功的问题,如果把app杀掉重新打开或者退出账号重新登录再发送语音就能成功发送了。一开始以为是某些初始化的代码没有加,但是仔细查看该加的初始化的东西都加了,没办法,问题要解决啊,于是开始一行一行的看环信的代码,最开始单点到ACTION_UP的时候发现调用recorder.stop()方法的时候会一直报IllegalStateException,显示在这句话这里找原因,一直没找到解决办法。后来开始转换思路,既然recorder.stop()方法会出现问题,肯定是创建的时候哪里出了问题,然后开始在ACTION_DOWN的地方找原因,最后发现是环信给的代码里有一个文件创建的问题,导致文件创建不成功,语音无法发送成功。在说这个问题之前先了解一下环信发送语音功能的实现过程。
环信的聊天页面的底部是一个自定义View,
当点击“按住说话”按钮的时候会出发它的onTouch事件,
//发送语音按钮被点击的时候
buttonPressToSpeak.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (listener != null) {
return listener.onPressToSpeakBtnTouch(v, event);
}
return false;
}
});
}
然后这个listener会回调到聊天页面,EaseChatFragment,在这个页面处理它的touch事件,
/**
* 发送语音
* @param v
* @param event
* @return
*/
@Override
public boolean onPressToSpeakBtnTouch(View v, MotionEvent event) {
return voiceRecorderView.onPressToSpeakBtnTouch(v, event, new EaseVoiceRecorderCallback() {
@Override
public void onVoiceRecordComplete(String voiceFilePath, int voiceTimeLength) {
sendVoiceMessage(voiceFilePath, voiceTimeLength);
}
});
}
我们进入onPressSpeakBtnTouch()方法,会看到在这里面对touch的按下、移动、抬起手势进行了处理。
public boolean onPressToSpeakBtnTouch(View v, MotionEvent event, EaseVoiceRecorderCallback recorderCallback) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: try { if (EaseChatRowVoicePlayClickListener.isPlaying) EaseChatRowVoicePlayClickListener.currentPlayListener.stopPlayVoice(); v.setPressed(true); startRecording(); } catch (Exception e) { v.setPressed(false); } return true; case MotionEvent.ACTION_MOVE: if (event.getY() < 0) { showReleaseToCancelHint(); } else { showMoveUpToCancelHint(); } return true; case MotionEvent.ACTION_UP: v.setPressed(false); if (event.getY() < 0) { // discard the recorded audio. discardRecording(); } else { // stop recording and send voice file try { int length = stopRecoding(); if (length > 0) { if (recorderCallback != null) { recorderCallback.onVoiceRecordComplete(getVoiceFilePath(), length); } } else if (length == EMError.FILE_INVALID) { Toast.makeText(context, R.string.Recording_without_permission, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, R.string.The_recording_time_is_too_short, Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); Toast.makeText(context, R.string.send_failure_please, Toast.LENGTH_SHORT).show(); } } return true; default: discardRecording(); return false; } }
ACTION_DOWN的时候,开始录音,首先是先判断一下当前是否正处于录音状态,如果是,先关闭录音,重新录,然后开始录音,接下来就是录音的代码了:在录音的代码中,带删除线的那一行代码是环信原来自己的代码,红色标记的部分是我自己添加进去的,就是这里出的错,这里要执行的操作是创建对应的录音文件,但是创建一个文件的时候需要先创建这个文件对应的目录,这里没有创建目录的过程就直接创建文件,所以在执行接下来的语句的时候就会报no such file or directory错误。
public String startRecording(Context appContext) { file = null; try { // need to create recorder every time, otherwise, will got exception // from setOutputFile when try to reuse if (recorder != null) { recorder.release(); recorder = null; } recorder = new MediaRecorder(); recorder.setAudioSource(MediaRecorder.AudioSource.MIC); recorder.setOutputFormat(MediaRecorder.OutputFormat.AMR_NB); recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); recorder.setAudioChannels(1); // MONO recorder.setAudioSamplingRate(8000); // 8000Hz recorder.setAudioEncodingBitRate(64); // seems if change this to voiceFileName = getVoiceFileName(EMClient.getInstance().getCurrentUser()); voiceFilePath = PathUtil.getInstance().getVoicePath() + "/" + voiceFileName;//file = new File(voiceFilePath);File file2=PathUtil.getInstance().getVoicePath(); if(!file2.exists()){ file2.mkdirs(); } file = new File(voiceFilePath); if(!file.exists()){ file.createNewFile(); } recorder.setOutputFile(file.getAbsolutePath()); recorder.prepare(); isRecording = true; recorder.start(); } catch (IOException e) { EMLog.e("voice", "prepare() failed"); } new Thread(new Runnable() { @Override public void run() { try { while (isRecording) { android.os.Message msg = new android.os.Message(); msg.what = recorder.getMaxAmplitude() * 13 / 0x7FFF; handler.sendMessage(msg); SystemClock.sleep(100); } } catch (Exception e) { EMLog.e("voice", e.toString()); } } }).start(); startTime = new Date().getTime(); EMLog.d("voice", "start voice recording to file:" + file.getAbsolutePath()); return file == null ? null : file.getAbsolutePath(); }
ACTION_MOVE的时候,根据手指所在的位置执行不同的结果。
public void showReleaseToCancelHint() { recordingHint.setText(context.getString(R.string.release_to_cancel)); recordingHint.setBackgroundResource(R.drawable.ease_recording_text_hint_bg); } public void showMoveUpToCancelHint() { recordingHint.setText(context.getString(R.string.move_up_to_cancel)); recordingHint.setBackgroundColor(Color.TRANSPARENT); }
ACTION_UP的时候,停止录音,保存语音文件。
public int stopRecoding() { if(recorder != null){ isRecording = false; recorder.stop(); recorder.release(); recorder = null; if(file == null || !file.exists() || !file.isFile()){ return EMError.FILE_INVALID; } if (file.length() == 0) { file.delete(); return EMError.FILE_INVALID; } int seconds = (int) (new Date().getTime() - startTime) / 1000; EMLog.d("voice", "voice recording finished. seconds:" + seconds + " file length:" + file.length()); return seconds; } return 0; }