助手
扣子 AI 帮助与支持
你好,我是 扣子 文档问答助手 🎉 你在阅读当前文档的过程中,无论对文档概念的解释,还是文档内容方面的疑问,都可以随时向我提问,我会全力为你解答
推荐问题
如何快速了解这个空间的核心内容?
有哪些近期更新的重点文档?
我应该从哪些文档开始阅读?
文档反馈
低代码项目
快速开始
智能体
工作流
应用
资源
发布
模型
多人协作
推广与变现

集成音视频 Realtime Android SDK

更新于: 2026-06-25 19:29:14

本文档介绍如何集成扣子编程 Realtime Android SDK,将你在扣子编程中搭建的 AI 智能体集成到你的 Android 应用中,实现用户与智能体进行音视频通话。

扣子编程 Realtime Android SDK 封装了火山引擎 Android RTC 音视频链路相关 API,接入流程简洁高效。

准备工作

开始集成 Realtime SDK 前,请确保开发环境满足以下要求:

操作 说明

发布智能体

已成功搭建并发布智能体为 API 服务。搭建步骤可参考搭建可视化智能体搭建低延时语音助手,发布步骤请参见发布为 API 服务

说明

若需使用视频理解能力,请为智能体配置一个视觉模型,例如豆包视觉理解模型。

获取访问密钥

获取访问密钥,用于身份认证与鉴权。

  • 体验或调试场景:建议生成短期的个人访问令牌(PAT),以快速完成 Realtime SDK 的整体流程。个人访问令牌的获取方法请参见添加个人访问令牌
  • 线上环境:在线上环境中,应使用服务访问令牌(SAT)或 OAuth 鉴权方案,各鉴权方式的详细说明请参考鉴权方式概述

说明

扣子编程 SDK 封装了多种鉴权方式,能够有效简化鉴权流程,你可以参考鉴权示例代码实现不同方式的 OAuth 认证,以获取和管理访问扣子编程 API 所需的令牌。

准备开发环境

  • macOS 开发电脑,且可以正常访问互联网
  • Android Studio
  • Kotlin 1.9.22 及以上
  • Android 7.0 或以上版本真机设备,且可以正常访问互联网
  • Gradle 8.0及以上

跑通示例项目源码

示例项目目录结构

音视频通话 Android 示例项目的目录结构如下:

app/

├── build.gradle.kts

├── .gitignore

└── src/

    └── main/

        ├── java/

        │   └── com/

        │       └── coze/

        │           └── java_example/

        │               ├── MainActivity.java

        │               ├── config/

        │               │   └── Config.java

        │               ├── manager/

        │               │   └── CozeAPIManager.java

        │               └── utils/

        │                   └── ToastUtil.java

        ├── res/

        │   ├── layout/

        │   │   └── activity_main.xml

        │   ├── values/

        │   │   ├── colors.xml

        │   │   ├── strings.xml

        │   │   └── themes.xml

        │   ├── ... 其他目录

        └── AndroidManifest.xml
kotlin-example/

├── build.gradle.kts

├── .gitignore

└── src/

    └── main/

        ├── java/

        │   └── com/

        │       └── coze/

        │           └── kotlin_example/

        │               ├── MainActivity.kt

        │               ├── config/

        │               │   └── Config.kt

        │               ├── manager/

        │               │   └── CozeAPIManager.kt

        │               └── utils/

        │                   └── ToastUtil.kt

        ├── res/

        │   ├── layout/

        │   │   └── activity_main.xml

        │   ├── values/

        │   │   ├── colors.xml

        │   │   ├── strings.xml

        │   │   └── themes.xml

        │   ├── ... 其他文件

        └── AndroidManifest.xml

配置并跑通示例项目

  1. 克隆音视频通话 Android 示例项目到本地。
    git clone https://github.com/coze-dev/coze-android
    
  2. 在配置文件中设置访问令牌、智能体 ID 和音色。
    编辑 main/java-example/src/main/res/values/strings.xml 配置文件,配置 coze_access_tokenbot_id和音色 ID。
    你可以调用查看音色列表 API 查看当前可用的音色列表,也可以使用系统音色列表
     <resources>
        <!-- 其他配置 -->
        
        <!-- 替换为从 https://www.coze.cn/open/oauth/pats 获取到的token-->
        <string name="coze_access_token">pat_PLraJCVarqLuoE1********</string>
        <string name="base_url">https://api.coze.cn</string>
        <!-- 替换为自己的bot -->
        <string name="bot_id">74********</string>
        <!-- 音色ID,可选 -->
        <string name="voice_id">742********09</string>
    </resources>
    
  3. 编译并运行示例项目。
    1. 开启 Android 设备的开发者选项,打开 USB 调试,通过 USB 连接线将 Android 设备接入电脑,并在 Android 设备选项中勾选你的 Android 设备。详情参看在硬件设备上运行应用
    2. 单击 Android Studio 窗口右上角的 Sync Project with Gradle Files(或使用 Shift ⇧ + Command ⌘ + O 快捷键)同步项目,拉取项目依赖。
    3. 单击 Android Studio 窗口右上角的 Run ‘app’ (或使用 Control ⌃ + R 快捷键)开始编译。
    4. 编译成功后,你的 Android 设备上会出现 Coze Android RTC Demo,在弹窗中选择开启摄像头和麦克风权限。应用界面如下:

步骤一:(可选)创建并配置 Android 项目

本步骤为如何创建一个新项目,如集成到已有项目,请直接查看步骤二。

  1. 打开 Android Studio。在 Welcome to Android Studio 窗口中,单击 New Project
  2. 在项目模板页选择 Phone and Tablet,模板使用 No Activity
  3. 配置项目信息,完成后单击 Finish
    在项目配置页,设置项目名称、软件包名称、存储路径等信息,开发语言选择 KotlinJavaMinimum SDK 选择 API 24
    Image
  4. 配置权限。
    SDK 已在内部声明所需权限,无需手动添加。

步骤二:集成 Realtime SDK

本文以通过 Gradle 直接集成为例。如需手动集成,请参考火山引擎 RTC 文档。

  1. 添加 Maven 仓库。
    在项目的根目录下找到 settings.gradle.kts 文件,并添加以下内容以配置 Maven 仓库:
    dependencyResolutionManagement {
        repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
        repositories {
            google()
            mavenCentral()
            maven { url = uri("https://artifact.bytedance.com/repository/Volcengine/") }
            maven { url = uri("https://repo.maven.apache.org/maven2/") }
        }
    }
    
  2. 配置依赖版本。
    打开项目根目录下的 gradle/libs.versions.toml 文件,配置火山 RTC SDK 和扣子编程 SDK 的版本信息。建议使用 VSCode 或其他文本编辑器打开该文件。
    [versions]
    # 你的其他配置
    volcenginertc = "3.58.1.19400"  # 替换为实际版本号
    cozeApi = "0.2.1"  # Coze SDK 版本号
    
    [libraries]
    # 你的其他配置
    volcenginertc = { module = "com.volcengine:VolcEngineRTC", version.ref = "volcenginertc" }
    coze-api = { module = "com.coze:coze-api", version.ref = "cozeApi" }
    

    [versions]
    # 你的其他配置
    volcenginertc = "3.58.1.19400"  # 替换为实际版本号
    cozeApi = "0.2.1"  # Coze SDK 版本号
    
    [libraries]
    # 你的其他配置
    volcenginertc = { module = "com.volcengine:VolcEngineRTC", version.ref = "volcenginertc" }
    coze-api = { module = "com.coze:coze-api", version.ref = "cozeApi" }
    

    说明

    • volcenginertc 中的版本号替换为 RTC 实际使用的版本号。具体版本信息可在下载 RTC SDK页面查看。
    • cozeApi 中的版本号替换为扣子编程 SDK 的最新版本,具体版本信息可在扣子编程 releases 页面查看。
  3. 拉取依赖。
    修改 toml 文件后,可以通过以下方式拉取相关依赖:
    • 在终端窗口执行以下命令。
      ./gradlew clean build --refresh-dependencies
      
    • 在 IDE 中单击 Sync Now
      Image

步骤三:实现音视频通话

说明

本文以 MainActivity.ktMainActivity.java 中的实现步骤为例,讲解如何实现一个基本的音视频通话功能。本示例并未覆盖全部代码内容,如需查看完整代码,请从音视频通话 Android 示例项目获取完整代码。

引入头文件

引入实现音视频通话功能所需的头文件。

import com.ss.bytertc.engine.RTCRoom;
import com.ss.bytertc.engine.RTCVideo;
import com.coze.openapi.service.service.CozeAPI

public class MainActivity extends AppCompatActivity {
    private RTCVideo rtcVideo;
    private RTCRoom rtcRoom;
    private CozeAPI cozeCli;
}

import com.ss.bytertc.engine.RTCRoom;
import com.ss.bytertc.engine.RTCVideo;
import com.coze.openapi.service.service.CozeAPI

class MainActivity : AppCompatActivity() {
    private var rtcVideo: RTCVideo? = null
    private var rtcRoom: RTCRoom? = null
    private lateinit var cozeCli: CozeAPI
}

创建用户界面

创建基本的用户界面,包括本地视频预览视图、控制按钮等,并设置初始状态。将布局文件中定义的组件绑定到代码中,如控制按钮、本地视频预览视图等,并设置相应的事件监听器。

private void initUI() {
    localViewContainer = findViewById(R.id.local_view_container);

    btnConnect = findViewById(R.id.btn_connect);
    btnConnect.setBackgroundColor(Color.GRAY);
    btnVideo = findViewById(R.id.btn_video);
    btnVideo.setBackgroundColor(Color.GRAY);
    btnAudio = findViewById(R.id.btn_audio);
    btnAudio.setBackgroundColor(Color.GRAY);
    btnInterrupt = findViewById(R.id.btn_interrupt);
    btnInterrupt.setBackgroundColor(Color.GRAY);
    roomIdInput = findViewById(R.id.room_id_input);
    messageTextView = findViewById(R.id.message_text_view);

    connect();
    initVideoControl();
    initAudioControl();
    setBtnInterrupt();
}

private fun initUI() {
    localViewContainer = findViewById(R.id.local_view_container)

    btnConnect = findViewById<Button>(R.id.btn_connect).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnVideo = findViewById<Button>(R.id.btn_video).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnAudio = findViewById<Button>(R.id.btn_audio).apply {
        setBackgroundColor(Color.GRAY)
    }
    btnInterrupt = findViewById<Button>(R.id.btn_interrupt).apply {
        setBackgroundColor(Color.GRAY)
    }
    roomIdInput = findViewById(R.id.room_id_input)
    messageTextView = findViewById(R.id.message_text_view)

    connect()
    initVideoControl()
    initAudioControl()
    setBtnInterrupt()
}

申请设备权限

在使用音视频通话功能之前,需要确保应用已获取必要的权限,包括麦克风、摄像头、网络访问权限。

public void requestPermission() {
    String[] PERMISSIONS_STORAGE = {
            Manifest.permission.RECORD_AUDIO,
            Manifest.permission.CAMERA,
            Manifest.permission.INTERNET

    };
    boolean needPermission = false;

    for (String permission : PERMISSIONS_STORAGE) {
        if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            needPermission = true;
            break;
        }
    }
    if(needPermission){
        requestPermissions(PERMISSIONS_STORAGE, 22);
    }

}

private fun checkAndRequestPermissions(): Boolean {
    val permissions = arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.RECORD_AUDIO,
        Manifest.permission.INTERNET,
        Manifest.permission.MODIFY_AUDIO_SETTINGS
    )

    val permissionsToRequest = permissions.filter { permission ->
        ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
    }

    return if (permissionsToRequest.isNotEmpty()) {
        ActivityCompat.requestPermissions(
            this,
            permissionsToRequest.toTypedArray(),
            100
        )
        false  // 返回false表示有权限需要申请
    } else {
        true  // 返回true表示所有权限都已获取
    }
}

创建房间

调用扣子编程的创建房间接口创建房间,以便后续从扣子编程平台获取房间信息。

// 第一步,在coze创建房间
CreateRoomReq req = CreateRoomReq.builder()
        .botID(Config.getInstance().getBotID())
        .voiceID(Config.getInstance().getVoiceID())
        .build();
roomInfoTemp = cozeCli.audio().rooms().create(req);

// 第一步,在coze创建房间 
CreateRoomReq req = CreateRoomReq.builder()
        .botID(Config.getInstance().getBotID())
        .voiceID(Config.getInstance().getVoiceID())
        .build();
roomInfoTemp = cozeCli.audio().rooms().create(req);

说明

创建房间后返回的 Token,其默认有效期为 3 分钟,如果 3 分钟内没有用户加入房间,或者用户静音 3 分钟,智能体将自动退出房间,房间随即被释放,后续若要再次和智能体对话,则需重新创建房间。

创建引擎

调用 createRTCVideo() 方法,使用扣子编程平台返回的房间信息初始化音视频引擎。

// 创建引擎
rtcVideo = RTCVideo.createRTCVideo(
        getApplicationContext(),  // 使用ApplicationContext而不是Activity
        roomInfo.getAppID(),
        new IRTCVideoEventHandler() {
            @Override
            public void onWarning(int warn) {
                Log.w(TAG, "RTCVideo warning: " + warn);
            }

            @Override
            public void onError(int err) {
                Log.e(TAG, "RTCVideo error: " + err);
            }
        },
        null,
        null
);

// 创建引擎
rtcVideo = RTCVideo.createRTCVideo(
        getApplicationContext(),  // 使用ApplicationContext而不是Activity
        roomInfo.getAppID(),
        new IRTCVideoEventHandler() {
            @Override
            public void onWarning(int warn) {
                Log.w(TAG, "RTCVideo warning: " + warn);
            }

            @Override
            public void onError(int err) {
                Log.e(TAG, "RTCVideo error: " + err);
            }
        },
        null,
        null
);

设置本端视频渲染视图

调用 setLocalVideoCanvas() 方法设置本端视频渲染视图。

// 设置本地预览窗口
TextureView localTextureView = new TextureView(MainActivity.this);
localViewContainer.removeAllViews();
localViewContainer.addView(localTextureView);

VideoCanvas videoCanvas = new VideoCanvas();
videoCanvas.renderView = localTextureView;
videoCanvas.renderMode = VideoCanvas.RENDER_MODE_HIDDEN;
// 设置本地视频渲染视图
rtcVideo.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, videoCanvas);

// 设置本地预览窗口
val localTextureView = TextureView(this@MainActivity)
localViewContainer.removeAllViews()
localViewContainer.addView(localTextureView)

VideoCanvas().apply {
    renderView = localTextureView
    renderMode = VideoCanvas.RENDER_MODE_HIDDEN
    // 设置本地视频渲染视图
    rtcVideo?.setLocalVideoCanvas(StreamIndex.STREAM_INDEX_MAIN, this)
}

创建并加入 RTC 房间

调用 createRTCRoom 方法创建 RTC 房间,调用 joinRoom() 方法加入房间。

// 第创建RTC房间
rtcRoom = rtcVideo.createRTCRoom(roomInfo.getRoomID());
rtcRoom.setRTCRoomEventHandler(rtcRoomEventHandler);
// 用户信息
UserInfo userInfo = new UserInfo(roomInfo.getUid(), "");
// 设置房间配置
RTCRoomConfig roomConfig = new RTCRoomConfig(
        ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM,
        true, true, true);

// 第四步,加入房间
rtcRoom.joinRoom(roomInfo.getToken(), userInfo, roomConfig);

// 创建RTC房间
rtcRoom = rtcVideo?.createRTCRoom(roomInfo?.roomID)?.apply {
    setRTCRoomEventHandler(rtcRoomEventHandler)
    // 用户信息
    val userInfo = UserInfo(roomInfo?.uid, "")
    // 设置房间配置
    val roomConfig = RTCRoomConfig(
        ChannelProfile.CHANNEL_PROFILE_CHAT_ROOM,
        true, true, true
    )

    // 加入房间
    joinRoom(roomInfo?.token, userInfo, roomConfig)
}

显示实时字幕

IRTCRoomEventHandler 回调中重写onUserMessageReceived方法,解析接收到的用户消息,根据消息中的 event_type 进行不同处理,调用 updateMessage() 方法将消息内容更新到界面上。

@Override
public void onUserMessageReceived(String uid, String message) {
    try {
        Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
        Log.d(TAG, "接收到原始消息: " + messageMap);

        Map<String, String> jsonMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
            try {
                if (entry.getValue() instanceof String) {
                    jsonMap.put(entry.getKey(), (String) entry.getValue());
                    continue;
                }
                String jsonValue = mapper.writeValueAsString(entry.getValue());
                jsonMap.put(entry.getKey(), jsonValue);
            } catch (JsonProcessingException e) {
                Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
            }
        }
        if (ChatEventType.CONVERSATION_MESSAGE_DELTA.getValue().equals(jsonMap.get("event_type"))){
            Message msg = mapper.readValue(jsonMap.get("data"), Message.class);
            updateMessage(msg.getContent());
        } else if (ChatEventType.CONVERSATION_MESSAGE_COMPLETED.getValue().equals(jsonMap.get("event_type"))) {
            updateMessage("\n");
        }
    } catch (JsonProcessingException e) {
        Log.e(TAG, "解析消息失败", e);
    }
}

override fun onUserMessageReceived(uid: String, message: String) {
    try {
        val messageMap = mapper.readValue<Map<String, Any>>(
            message,
            object : TypeReference<Map<String, Any>>() {}
        )
        Log.d(TAG, "接收到原始消息: $messageMap")

        val jsonMap = messageMap.mapValues { (_, value) ->
            when (value) {
                is String -> value
                else -> try {
                    mapper.writeValueAsString(value)
                } catch (e: Exception) {
                    Log.e(TAG, "序列化value失败: ${e.message}")
                    null
                }
            }
        }

        when (jsonMap["event_type"]) {
            ChatEventType.CONVERSATION_MESSAGE_DELTA.value -> {
                val msg = mapper.readValue(
                    jsonMap["data"] as String,
                    Message::class.java
                )
                updateMessage(msg.content)
            }
            ChatEventType.CONVERSATION_MESSAGE_COMPLETED.value -> {
                updateMessage("\n")
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "解析消息失败", e)
    }
}

开启或关闭本端视频流采集

监听按钮事件,根据 isVideoEnabled 的状态,调用 stopVideoCapture()startVideoCapture() 方法,实现摄像头的启用或禁用功能。

private void initVideoControl() {
    btnVideo.setOnClickListener(v -> {
        if (rtcVideo != null) {
            if (isVideoEnabled) {
                stopVideo();
            } else {
                startVideo();
            }
        }else{
            ToastUtil.showAlert(this, "请先连接");
        }
    });
}

private void stopVideo(){
    rtcVideo.stopVideoCapture();
    btnVideo.setText("打开视频");
    isVideoEnabled = !isVideoEnabled;
}

private void startVideo(){
    rtcVideo.startVideoCapture();
    btnVideo.setText("关闭视频");
    isVideoEnabled = !isVideoEnabled;
}

private fun initVideoControl() {
    btnVideo.setOnClickListener {
        if (rtcVideo != null) {
            if (isVideoEnabled) {
                stopVideo()
            } else {
                startVideo()
            }
        } else {
            ToastUtil.showAlert(this, "请先连接")
        }
    }
}

private fun stopVideo() {
    rtcVideo?.stopVideoCapture()
    btnVideo.text = "打开视频"
    isVideoEnabled = !isVideoEnabled
}

private fun startVideo() {
    rtcVideo?.startVideoCapture()
    btnVideo.text = "关闭视频"
    isVideoEnabled = !isVideoEnabled
}

静音或取消静音

监听按钮事件,根据 isAudioEnabled 的状态调用 stopAudioCapture()startAudioCapture() 方法,从而实现麦克风的启用或静音功能。

private void initAudioControl() {
    btnAudio.setOnClickListener(v -> {
        if (rtcVideo != null) {
            if (isAudioEnabled) {
                stopVoice();
            } else {
                startVoice();
            }
        }else {
            ToastUtil.showAlert(this, "请先连接");
        }
    });
}

private void startVoice(){
    rtcVideo.startAudioCapture();
    btnAudio.setText("静音");
    isAudioEnabled = !isAudioEnabled;
}

private void stopVoice(){
    rtcVideo.stopAudioCapture();
    btnAudio.setText("打开声音");
    isAudioEnabled = !isAudioEnabled;
}

private fun initAudioControl() {
    btnAudio.setOnClickListener {
        if (rtcVideo != null) {
            if (isAudioEnabled) {
                stopVoice()
            } else {
                startVoice()
            }
        } else {
            ToastUtil.showAlert(this, "请先连接")
        }
    }
}

private fun startVoice() {
    rtcVideo?.startAudioCapture()
    btnAudio.text = "静音"
    isAudioEnabled = !isAudioEnabled
}

private fun stopVoice() {
    rtcVideo?.stopAudioCapture()
    btnAudio.text = "打开声音"
    isAudioEnabled = !isAudioEnabled
}

停止音视频通话

  1. 调用 leaveRoom()destroy() 方法离开并销毁房间。
  2. 调用 stopVoice()stopVideo() 方法停止音视频采集。
  3. 调用 destroyRTCVideo() 方法销毁引擎。
private void disconnect(){
    if (rtcRoom != null) {
        rtcRoom.leaveRoom();
        rtcRoom.destroy();
    }
    if (rtcVideo != null){
        stopVoice();
        stopVideo();
        RTCVideo.destroyRTCVideo();
        rtcVideo = null;
    }
    ToastUtil.showAlert(this, "断开连接成功");
}

private fun disconnect() {
    rtcRoom?.apply {
        leaveRoom()
        destroy()
    }
    rtcVideo?.apply {
        stopVoice()
        stopVideo()
        RTCVideo.destroyRTCVideo()
        rtcVideo = null
    }
    ToastUtil.showAlert(this, "断开连接成功")
}

说明

在实现音视频通话后,如遇无声音、无画面、视频卡顿等问题时,你可以使用诊断工具快速排查和定位异常房间及用户,并获取异常根因分析、处理建议、分析报告等。

信令事件

事件类型

智能语音信令事件包括上行事件和下行事件。每个事件有 ID 和 EventType, 通过 EventType 可以区分具体的事件类型,每个事件类型对应的 Payload 在 Data 中,开发者可以按需去提取需要的内容。

  • 上行事件:设备端上报给服务端的事件。应用程序需要根据扣子编程提供的事件结构,在触发事件时填充字段内容并上报事件。设备端需要在监听到 Realtime SDK 的 onUserJoined 回调智能体进房后才能发送上行事件,可通过 Realtime SDK 的 sendUserMessage 发送,如果是嵌入式设备集成音视频,可通过 Realtime SDK 的 byte_rtc_rts_send_message 发送。详细信息可参考Realtime 上行事件
  • 下行事件:服务端下发给设备端的事件,可订阅 Realtime SDK的 onUserMessageReceived回调,如果是嵌入式设备集成音视频,可订阅 Realtime SDK 的 on_message_received回调,接收下行事件。应用程序需要解析下行事件,并根据业务需求进行下一步操作。详细信息可参考Realtime 下行事件

公共参数

智能语音信令事件的公共参数如下:

参数名称 类型 描述
id String 事件 ID,也就是事件的唯一标识。由客户端或服务端生成,在故障排查场景下用于定位具体的事件,便于排查问题。
event_type String 事件的类型。
data JSON 事件的详细信息,其中包含具体事件的业务字段。

信令事件的使用流程

信令事件的使用流程如下:

  1. 初始化。通过监听 session.created 事件,确认房间初始化完成。
  2. 发送请求。使用上行事件(如 conversation.message.create)向智能体发送消息。
  3. 处理响应。监听下行事件(如 conversation.message.delta)获取智能体的增量回复。
  4. 插件交互。在收到事件 conversation.chat.requires_action 后,执行插件操作并通过事件 conversation.chat.submit_tool_outputs 提交结果。
  5. 错误处理。通过监听 error 事件捕获和处理异常情况。

使用示例如下:

// 通过重载 onUserMessageReceived 方法即可监听所有的下行事件,可以通过 event_type 过滤需要处理的事件
@Override
public void onUserMessageReceived(String uid, String message) {
    try {
        Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
        Log.i(TAG, "接收到原始消息: " + messageMap);

        Map<String, String> jsonMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
            try {
                if (entry.getValue() instanceof String) {
                    jsonMap.put(entry.getKey(), (String) entry.getValue());
                    continue;
                }
                String jsonValue = mapper.writeValueAsString(entry.getValue());
                jsonMap.put(entry.getKey(), jsonValue);
            } catch (JsonProcessingException e) {
                Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
            }
        }
        // 根据你需要监听的 event,解析成对应的结构
        if (ChatEventType.CONVERSATION_MESSAGE_DELTA.getValue().equals(jsonMap.get("event_type"))){
            Message msg = mapper.readValue(jsonMap.get("data"), Message.class);
            updateMessage(msg.getContent());
        } else if (ChatEventType.CONVERSATION_MESSAGE_COMPLETED.getValue().equals(jsonMap.get("event_type"))) {
            updateMessage("\n");
        }
    } catch (JsonProcessingException e) {
        Log.e(TAG, "解析消息失败", e);
    }
}

// 通过重载 onUserMessageReceived 方法即可监听所有的下行事件,可以通过 event_type 过滤需要处理的事件
override fun onUserMessageReceived(uid: String, message: String) {
    try {
        val messageMap = mapper.readValue<Map<String, Any>>(
            message,
            object : TypeReference<Map<String, Any>>() {}
        )
        Log.d(TAG, "接收到原始消息: $messageMap")

        val jsonMap = messageMap.mapValues { (_, value) ->
            when (value) {
                is String -> value
                else -> try {
                    mapper.writeValueAsString(value)
                } catch (e: Exception) {
                    Log.e(TAG, "序列化value失败: ${e.message}")
                    null
                }
            }
        }

        when (jsonMap["event_type"]) {
            // 根据你需要监听的 event,解析成对应的结构
            ChatEventType.CONVERSATION_MESSAGE_DELTA.value -> {
                val msg = mapper.readValue(
                    jsonMap["data"] as String,
                    Message::class.java
                )
                updateMessage(msg.content)
            }
            ChatEventType.CONVERSATION_MESSAGE_COMPLETED.value -> {
                updateMessage("\n")
            }
        }
    } catch (e: Exception) {
        Log.e(TAG, "解析消息失败", e)
    }
}

信令事件的处理思路

在处理信令事件时,建议采用以下思路:

  • 监听所有事件:捕获客户端和服务端的所有交互,便于调试和监控。扣子编程推荐你监听所有的事件,你也可以根据实际的业务需求,监听不同类型的事件,例如客户端事件、服务端事件等。
  • 处理特定事件:根据业务需求,监听和处理特定的事件,例如连接成功、中断、断开、音频状态变化等。
  • 错误处理:捕获和处理错误事件,确保应用的稳定性和用户体验。

示例代码如下:

// 调用 RTCRoom 对象的 sendUserMessage 方法即可发送上行事件,指定其中的 userId 为聊天的 bot_id 即可将事件发送给指定的 bot,下面以处理端插件事件为例

        @Override
        public void onUserMessageReceived(String uid, String message) {
            try {
                Map<String, Object> messageMap = mapper.readValue(message, new TypeReference<Map<String, Object>>() {});
                Log.i(TAG, "接收到原始消息: " + messageMap);

                Map<String, String> jsonMap = new HashMap<>();
                for (Map.Entry<String, Object> entry : messageMap.entrySet()) {
                    try {
                        if (entry.getValue() instanceof String) {
                            jsonMap.put(entry.getKey(), (String) entry.getValue());
                            continue;
                        }
                        String jsonValue = mapper.writeValueAsString(entry.getValue());
                        jsonMap.put(entry.getKey(), jsonValue);
                    } catch (JsonProcessingException e) {
                        Log.e(TAG, "序列化value失败: " + entry.getKey(), e);
                    }
                }
                }else if(ChatEventType.ERROR.getValue().equals(jsonMap.get("event_type"))){
                    Log.i(TAG, jsonMap.toString());
                }
            } catch (JsonProcessingException e) {
                Log.e(TAG, "解析消息失败", e);
            }
        }