Android调用环信的EaseUI V3.0发送语音不成功的问题。

2023-11-11

这两个在集成环信实现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;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android调用环信的EaseUI V3.0发送语音不成功的问题。 的相关文章

随机推荐

  • 用python+pytest框架写UI自动化

    使用 Python 编写 UI 自动化测试通常需要使用 Pytest 测试框架 下面是编写 Python Pytest UI 自动化测试的一般步骤 安装 Python Pytest 和相关的库 例如 Selenium WebDriver 和
  • WM_CHAR 获取键盘按下的字符

    DEMO3 11 CPP WM CHAR demo INCLUDES define WIN32 LEAN AND MEAN just say no to MFC include
  • QT-固定全局文字大小

    问题描述 W10 将缩放与布局设置为 gt 100 时 QT的文字会放大超过原来的文字框 解决 使用这种方法是让Windows来控制缩放 而不是Qt 添加一个资源文件qt conf 内容为 Platforms WindowsArgument
  • linux 时钟漂移,Redis 实现分布式锁之Redlock 算法浅析

    保证分布式锁有效的三个属性 Safety Properties 安全性 此处也就是互斥性 任意时刻只能有一个客户端可以持有锁 Liveness Property A 无死锁 即使持有锁的客户端崩溃或被分区 也可以获得锁 Liveness P
  • pandas中groupby函数中参数ax_index和group_keys的区别

    前言 笔者在学习pandas中groupby函数时 发现ax index True False和group key True False这两个参数相近又有所不同 特写出此文供大家分享 一 首先创建一个DataFrame df pd Data
  • thymeleaf模板报红

    问题 解决 忽略所有警告或错误 1 或者忽略Thymeleaf有关警告或错误 2 2 取消这个勾 关闭IDEA对于thymeleaf的数据验证选项
  • BP神经网络的详细推导

    文章目录 概述 一 神经元模型 二 感知机与多层网络 三 误差逆传播算法 四 全局最小与局部最小 五 BP算法的改进 1 引入动量法 2 尺度变换法 3 自适应学习率调整法 六 BP神经网络的训练 1 产生数据样本集 2 确定网络的类型和结
  • QMap倒序遍历

    for QList
  • 中英文期刊卷号和期号

    一 中文期刊 国内期刊为了给期刊排序方便查询 都按照时间分卷和期 卷是期之上的时间分类 卷是从创刊开始按照年度排序的编号 期是这一年中按时间排序的编号 比如2017年3月发表的论文 按照卷号期号排可能就是 第23卷第3期 国内对期刊卷号期号
  • 【简述】VSCode使用ssh连接linux服务器并安装使用jupyter notebook/Anaconda/pytorch

    1 通过管理员获得该Unbutu服务器的IP 账号 密码 2 打开VSCode 安装扩展Remote SSH 3 在VsCode左侧边栏点击Remote Explorer使用该扩展 新建Remote 输入IP 账号并连接 4 在上方弹出窗口
  • Unix/Linux编程:fork()进程详解

    文章目录 理论 进程 fork wait exec fork 实践 验证 fork函数被调用一次但返回两次 子进程和父进程之间不共享数据空间 父子进程间的文件共享 fork的内存语义 同步信号以规避 fork 之后的竞争条件 fork 解决
  • Flutter切换页面后保持状态不刷新

    1 使用IndexedStack实现 IndexedStack继承自Stack 它的作用是显示第index个child 其它child在页面上是不可见的 但所有child的状态都被保持 所以这个Widget可以实现我们的需求 我们只需要将现
  • PointNet介绍

    论文 PointNet Deep Learning on Point Sets for 3D Classification and Segmentation 代码 https github com charlesq34 pointnet 0
  • 【无标题】C++课程学习笔记(南科大于仕琪老师)

    这几天我突然想写CSDN了 前段时间我打开了我的CSDN 发现我其实只写了3篇文章 其实写CSDN是一个好习惯 我之前这么多年都没有发现这个好习惯 现在我要求自己只有有所心得就应该写下来 一方面与大家共勉 另一方面通过和大家的交流我希望自己
  • UDS诊断之负响应码

    1 0x10服务 服务诊断会话控制 否定响应码 NRC 定义 Defination 0x12 服务器支持诊断请求中的服务标识符 Service ID 但不支持收到的子功能参数时 回复此编码 0x13 请求服务的诊断报文中的数据长度与定义不一
  • Linux虚拟化网络之路由转发实战

    一 Linux路由配置 如果要在不同网段直接通讯 需要添加路由 Linux添加路由命令如下 route add del net host target netmask Nm gw Gw dev If add 添加一条路由规则 del 删除一
  • 简单的shell文件编写:拷贝特定的可执行文件到某特定目录下。

    写这个文件夹是为了把自己在fedora9上交叉编译的可执行文件复制到arm板上便于执行 这样每次都不用输入一长串文件名了 调用这个脚本 它会自动 拷贝ARM可执行文件到指定的目录下 例子是 home stephen stephenshare
  • 平均年薪30万的深度学习算法工程师,正面临100万的人才缺口

    深度学习的突破极大推动了人工智能的发展 并广泛应用在计算机视觉 自然语言处理等领域中 谷歌 百度的IDL 腾讯的AI lab 华为等都在重金布局人工智能 同时 以深度学习为核心技术的人工智能企业不断涌现 我们耳熟能详的有 格林深瞳 商汤科技
  • 【IPv6】设置win10和win11允许访问IPv6站点

    设置win10和win11允许访问IPv6站点 步骤如下 1 打开控制面板 2 点击 网络和 Internet 3 点击 网络和共享中心 4 点击连接的网络 5 点击 属性 6 把 Internet 协议版本 6 TCP IPv6 选项勾上
  • Android调用环信的EaseUI V3.0发送语音不成功的问题。

    这两个在集成环信实现IM功能 在开发过程中遇到一个问题 发送文字 表情 图片 位置都是正常的 但是语音就是一直发送不成功 并且只是在第一次安装app登录账号之后发送语音的时候才会出现发送不成功的问题 如果把app杀掉重新打开或者退出账号重新