# DUIX_SDK_Android使用文档

更新时间：2022年09月14日14:04:13




本文介绍了如何使用硅基数字人服务提供的DUIX Android SDK，包括下载安装、关键接口及代码示例。



### 物料准备

```
duix_sdk_release_1.3.9.aar
```



### 下载安装

1. 下载[duix_sample_android_1.1.32.zip](https://duix.guiji.ai/document/demo/android/duix_sample_android_1.1.32.zip)
2. 解压zip包，在duix_sample/app/libs目录下获取AAR格式的SDK包，将AAR包集成到您的工程项目中进行依赖。
3. 使用Android Studio打开此工程查看参考代码实现，其中duix交互的主要代码在DUIXActivity.java文件中。



### Gradle配置

工程下app模块添加如下配置:

```
dependencies {
    implementation "org.igniterealtime.smack:smack-android-extensions:4.3.0"
    implementation "org.igniterealtime.smack:smack-tcp:4.3.0"
    implementation 'org.webrtc:google-webrtc:1.0.32006'

    ...
}

configurations {
    all {
        exclude group: 'xpp3', module: 'xpp3'
    }
}
```



### 权限处理

在SDK中manifest已经添加网络、音频、蓝牙等权限申请,如果需要开启摄像头需要添加camera权限

```
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

<uses-permission android:name="android.permission.CAMERA" />
```



### SDK关键接口

- 使用DUIXFactory.getDUIX()获取DUIXCore实例

```
/**
* @param listener: DUIX回调接口
*/
public void addDUIXListener(IDUIXListener listener)

public removeDUIXListener(IDUIXListener listener)
```

其中IDUIXListener接口包含如下回调:

 1. onIMConnected: IM模块连接成功，当已连接状态调用connect函数也会触发改回调。

    ```
    default void onIMConnected()
    ```

 2.  onIMConnectError: IM连接失败，当im被异常中断和代理用户掉线触发。

    ```
    /**
    * @param msg  错误信息描述
    */
    default void onIMConnectError(String msg)
    ```

 3. onRender: 返回后台数字人节点信息

    ```
    /**
         * 回调后台执行任务的节点信息
         *
         * @param id   渲染节点ID
         * @param name 渲染节点名称
         */
    default void onRender(String id, String name)
    ```

 4. onRenderState: 返回后台渲染状态

    ```
    /**
         * 返回后台渲染状态。可以根据Loading值绘制进度条。Show说明后台已经完成加载推流的工作，可以在这时候隐藏加载框发送客户端就绪信号以开始走话术流程。
         * 可以在该回调中获取数字人语音播报状态(playStart、playStop)
         * @param state    Loading、Ready、Show、playStart、playStop
         * @param dataJson 数据完整结构
         */
    default void onRenderState(String state, String dataJson) {
    }
    ```

 5. onDetectedSpeech: 返回识别结果

    ```
    /**
         * 识别结果返回
         *
         * @param asrText ASR识别的内容
         */
        default void onDetectedSpeech(String asrText) {
        }
    ```

 6. onBusy: 后台服务器繁忙

    ```
    /**
         * 后台资源繁忙,无法接入
         */
        default void onBusy() {
        }
    ```

 7. available: 后台心跳、默认5s执行一次。

    ```
    /**
         * 定时发送活跃信息，默认5s执行一次。可以在该回调中发送会话pingSession()任务
         */
    default void available() {
        }
    ```

 8. onCommand: 返回会话节点信息

    ```
    /**
         * 返回后台数字人操作信息
         *
         * @param type   操作类型
         * @param flowId 和nodeId一起做持久化，方便下次进来makeSession直接恢复到当前话术节点
         * @param nodeId 和flowId一起做持久化，方便下次进来makeSession直接恢复到当前话术节点
         * @param json   完整消息内容
         */
        default void onCommand(String type, String flowId, String nodeId, String json) {
        }
    ```

 9. onByeBye: 远端结束消息

    ```
    /**
         * 远端结束消息
         * @param uuid 通过该UUID过滤是否是自己的会话。在接管模式中可能返回的其他会话的ID。
         * @param code 因认证错误等原因会返回错误码
         * @param reason  因认证错误等原因会返回错误信息
         */
    default void onByeBye(String uuid, int code, String reason){
    }
    ```
    
 10. onRTCError: 创建RTC会话时出现的SDP设置异常

     ```
      default void onRTCError(String message) {
      }
     ```

 11. onIceConnectionChange: RTC连接状态

     ```
     /**
     * rtc连接状态回调
     * @param iceConnectionState 当前rtc状态
     */
     default void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
         }
     ```

     状态列表:

     | 名称         | 说明                                                         |
     | ------------ | ------------------------------------------------------------ |
     | NEW          | ICE 代理正在搜集地址或者等待远程候选可用。                   |
     | CHECKING     | ICE 代理已收到至少一个远程候选，并进行校验，无论此时是否有可用连接。同时可能在继续收集候选。 |
     | CONNECTED    | ICE代理至少对每个候选发现了一个可用的连接，此时仍然会继续测试远程候选以便发现更优的连接。同时可能在继续收集候选。 |
     | COMPLETED    | ICE代理已经发现了可用的连接，不再测试远程候选。              |
     | FAILED       | ICE候选测试了所有远程候选没有发现匹配的候选。也可能有些候选中发现了一些可用连接。 |
     | DISCONNECTED | 测试不再活跃，这可能是一个暂时的状态，可以自我恢复。         |
     | CLOSED       | ICE代理关闭，不再应答任何请求。                              |

 12. onAddTrack: 远端媒体流回调

     ```
     /**
          * UNIFIED_PLAN使用该回调获取远端媒体流
          *
          * @param track 媒体对象基类。可以是VideoTrack和AudioTrack
          */
         default void onAddTrack(MediaStreamTrack track, MediaStream[] mediaStreams) {
         }
     ```

 13. onAddStream: 远端媒体流回调,默认使用UNIFIED_PLAN协议的onAddTrack回调。

     ```
     /**
          * PLAN_B模式下会使用改对象封装VideoTrack和AudioTrack
          *
          * @param mediaStream 流媒体对象封装
          */
         default void onAddStream(MediaStream mediaStream) {
         }
     ```

 14. onLocalAudioSamples: 本地音频数据回调，需要在初始化会话时RTCOptions设置setEnableAudioSamples(true)

     ```
     /**
          * 本地音频数据回调，需要在初始化会话时RTCOptions设置setEnableAudioSamples(true)
          * @param audioSamples
          */
         default void onLocalAudioSamples(JavaAudioDeviceModule.AudioSamples audioSamples) {
         }
     ```

 15. onRemoteAudioSamples: 远端音频数据回调，需要在初始化会话时RTCOptions设置setEnableAudioSamples(true)

     ```
     /**
          * 远端音频数据回调，需要在初始化会话时RTCOptions设置setEnableAudioSamples(true)
          * @param audioSamples
          */
         default void onRemoteAudioSamples(JavaAudioDeviceModule.AudioSamples audioSamples) {
         }
     ```

 16. onCameraSwitch: 交互中切换摄像头

     ```
     /**
          * 摄像头切换的结果回调
          *
          * @param success 是否切换成功
          * @param back    是否是后置摄像头
          * @param msg     如果切换失败返回失败原因
          */
         default void onCameraSwitch(boolean success, boolean back, String msg) {
         }
     ```

 17. onStartVideoRecord: 视频开始录制

     ```
     /**
          * 视频开始录制（包含音频）
          */
         default void onStartVideoRecord() {
         }
     ```

 18. onStopVideoRecord: 视频录制结束

     ```
     /**
          * 视频录制结束
          * @param path 视频保存路径
          */
         default void onStopVideoRecord(String path) {
         }
     ```

 19.  onPauseVideoRecord, onResumeVideoRecord

     ```
      // 视频暂停及恢复
      default void onPauseVideoRecord() {
         }

         default void onResumeVideoRecord() {
         }
     ```

 20. onVideoRecordTime: 返回视频录制时间

     ```
     /**
          * 返回录制的时间
          *
          * @param timeStampAudio 录制的时长,ms单位  /1000/1000=s
          * @param nanoTime       当前时间，纳秒单位
          * @param baseTimeStamp  开始时间
          * @param pauseDelay     暂停时长
          */
         default void onVideoRecordTime(long timeStampAudio, long nanoTime, long baseTimeStamp, long pauseDelay) {
         }
     ```

 21. onAllocate: 坐席分配通知

     ```
     /**
          * 在客户端发起会话时打开分配选项，同组织的坐席会收到该消息
          * @param uuid 客户端会话ID
          */
         default void onAllocate(String uuid) {
         }
     default void onAllocate(String uuid) {
     }
     ```

 22.  onTakeOverConnected：监控客户端会话。

 23.  onTakeOverStartSuccess: 开始监管成功

 24.  onTakeOverStopSuccess: 结束接管成功

 25.  onAnswer: 将用户端回复同步到接管端

     ```
     /**
          * 将用户端回复同步到接管端
          * @param uuid 用户会话
          * @param content 会话内容
          */
         default void onAnswer(String uuid, String content) {
         }
     ```

 26.  onAssistantChatCreated: 辅助会话创建成功

     ```
     // 辅助会话,可以做一些两个客户端直接通信的需求
     default void onAssistantChatCreated(String target) {
         }
     ```

 27.  onSupplyMessage: 通过辅助会话协议补充




- connect: 连接IM服务端

  ```
   /**
       * 连接IM服务端.
       * @param imOptions IM连接参数
       */
  public void connect(Context context, DUIXFactory.IMOptions imOptions)
  ```



-  disconnect: 断开IM连接

  ```
  // 断开IM连接，一般无需调用，在IM需要伴随用户登录整个生命周期的场景中需要用该方法断开消息通知的连接。
  public void disconnect()
  ```

- isOnline: 检查im是否正产连接



- createSession: 创建会话

  ```
  /**
       * 创建会话
       * @param context 应用上下文
       * @param eglBaseContext 和Surface共享的gl上下文
       * @param sessionOptions 会话配置,定义了会话的ID,资源组,对接模式等信息
       * @param rtcOptions rtc对接参数，定义了媒体参数
       */
  public void createSession(Context context, EglBase.Context eglBaseContext, DUIXFactory.SessionOptions sessionOptions, DUIXFactory.RTCOptions rtcOptions)
  ```

- reCreateSession: 重新创建会话



- sendByBot: bot模式下发送文本消息

  ```
  /**
  * bot模式下发送文本消息
  * @param uuid 会话id
  * @param text 需要发送到后台的文本
  */
  public void sendByBot(String uuid, String text)
  ```



- sendByClient: 客戶端驱动模式下发送消息

  ```
  /**
  * 客戶端驱动模式下发送消息,可以是文本，可以是wav的url, 当发送wavUrl的时候text字段置""字符串
  * @param uuid 会话id
  * @param text 需要发送到后台的文本
  * @param wavUrl 需要发送到后台的文本
  */
  public void sendByClient(String uuid, String text, String wavUrl)
  ```



- clientReady: 通知后台准备就绪开始播放话术



- asrSwitch: 长连接识别模式下asr开关

  ```
  /**
  * 长连接识别模式下asr开关
  * @param uuid 会话id
  * @param asrSwitch 通知后台打开或关闭asr
  */
  public void asrSwitch(String uuid, boolean asrSwitch)
  ```

- switchCamera: 当支持前后置摄像头的情况下，切换摄像头



- setMicrophone: 设置麦克风是否静音

  ```
  /**
  * 设置麦克风是否静音
  * @param enable  true: 传输声音，false:静音
  */
  public boolean setMicrophone(boolean enable)
  ```


- audioBreak: 音频播放打断

  ```
  /**
  * 音频播放打断
  */
  public void audioBreak(String UUID)
  ```


- pingSession: 会话ping消息，保持会话连接活跃

  ```
  /**
  * 定期发送，保持连接活跃
  */
  public void pingSession()
  ```



- closeSession: 关闭会话

  ```
  /**
  * 关闭会话
  * @param uuid 会话id
  */
  public void closeSession(String uuid)
  ```



- release: 释放资源,在Activity的onDestroy中调用

  ```
  /**
  * 关闭RTC通信同时关闭IM连接，释放资源。
  */
  public void release()
  ```

- releaseSession: 释放会话，在Activity的onDestroy中调用

  ```
  /**
  * 当需要保持IM通信连接状态是调用改函数释放RTC资源。
  * 关闭RTC通信但是保持IM连接
  */
  public void releaseSession()
  ```

- createTakeOver: 坐席场景中创建接管会话监控，同createSession。

- takeOverStart: 坐席场景中发起接管

- takeOverStop: 坐席场景中结束接管



### 调用步骤

1. 根据实际情况使用DUIXFactory.getDUIX()获取DUIXCore实例.
2. 使用mDUIXCore.connect(imOptions)建立IM连接.
3. 连接成功后mDUIXCore.createSession()发起会话
3. onAddTrack回调中获取媒体流并绑定到renderer中
5. available回调中定时发送pingSession持续会话.
6. 根据启动模式使用不同方式与后台交互(sendByClient、sendByBot)
6. 关闭会话,使用release释放资源



### Proguard配置
如果代码使用了混淆，请在proguard-rules.pro中配置：

```
-keep class org.webrtc.**{ *; }
-keep class org.igniterealtime.**{ *; }
-keep class org.jivesoftware.**{ *; }
-keep class org.jxmpp.**{ *; }
-keep class org.jivesoftware.**{ *; }
```





### 代码示例

- 注册事件监听

  ```
  protected void permissionOk() {
  	mDUIXCore = DUIXFactory.getDUIX();
      mDUIXCore.addDUIXListener(mDUIXCallback);
  }
  ```



- IM参数设置及连接

  ```
  String deviceId = "endpoint_android_" + DeviceUtils.getUUID(mContext);
  DUIXFactory.IMOptions imOptions = new DUIXFactory.IMOptions(deviceId)	// 客户端ID
                  .setImHost(mConfig.getHost())     // IM服务器地址
                  .setImPort(mConfig.getPort())     // IM服务端口号
                  .setImUserJID(mConfig.getJID());  // 代理服务名称 iray-proxy@guiji.ai/guiji
  mDUIXCore.connect(mContext, imOptions);           // 启动连接
  
  @Override
  public void onIMConnected() {
              // 连接成功，发起会话
  }
  ```



- 设置会话参数及RTC媒体信息，发起会话

  ```
  public void onIMConnected() {
      if (mDUIXCore != null) {
          UUID = IMMessageFormat.getRandomUUID();
          // 会话参数
          DUIXFactory.SessionOptions sessionOptions = new DUIXFactory.SessionOptions(UUID, mConfig.getToken(), mConfig.getAppId())
                  .setRobotMode(mConfig.getRobotMode()) // bot模式:  "bot" or ""
                  .setGroupId(mConfig.getGroupId())     // 资源组
                  .setFlowId(mLastSessionFlowId)        // 恢复到该会话
                  .setNodeId(mLastSessionNodeId)        // 恢复到该会话
                  .setAsrEnable(mConfig.isAsr());       // 是否打开ASR识别
          // RTC媒体参数
          DUIXFactory.RTCOptions rtcOptions = new DUIXFactory.RTCOptions()
                  .setAudioEnable(mConfig.getAsrMode() == 1)        // 默认asrMode==1长语音识别，后台返回结果。asrMode=0时需要自己对接三方识别接口。
                  .setVideoEnable(mConfig.isCamera());              // 是否传输视频
          mDUIXCore.createSession(mContext, eglBaseContext, sessionOptions, rtcOptions);
      }
  }
  
  @Override
  protected void renderState(String state, String dataJson){
  		// 当收到改消息时说明后台加载完成
  		if ("Show".equals(state)) {
  		    // 发送客户端准备完成信号，开始播放话术
              mDUIXCore.clientReady(UUID);
              // UI隐藏加载框
          }
  }
  ```



- 发送消息与后台交互

  ```
  // 根据不同的robotMode来决定用哪个接口发送消息
  private void sendIMMsg(String msg) {
      if ("bot".equals(mConfig.getRobotMode())) {
          // bot 模式直接传递文本
          mDUIXCore.sendByBot(UUID, msg);
      } else {
          mDUIXCore.sendByClient(UUID, msg, "");
      }
  }
  
  @Override
  public void wavClick(String url) {
      if (mDUIXCore != null) {
          mDUIXCore.sendByClient(UUID, url);
          appendMsg("send wav url: " + url);
      }
  }
  ```



  - 视频预览控件初始化

    ```
    // rtc模块和控件共享该上下文
    eglBaseContext = EglBase.create().getEglBaseContext();
    remote_renderer.init(eglBaseContext, null);
    remote_renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
    remote_renderer.setMirror(false);						// 使用该标记对画面镜像处理
    remote_renderer.setEnableHardwareScaler(false /* enabled */);
    
    local_renderer.init(eglBaseContext, null);
    local_renderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
    local_renderer.setMirror(true);
    local_renderer.setEnableHardwareScaler(false /* enabled */);
    local_renderer.setZOrderOnTop(true);            			// 使用该标记对画面镜像处理
    local_renderer.setZOrderMediaOverlay(true);			// 处理多个surfaceview覆盖导致的显示问题
    local_renderer.setOnTouchListener(
                      new RendererTouchListener(
                              new Rect(0, 0, DisplayUtils.getScreenWidth(mContext), DisplayUtils.getScreenHeight(mContext))));	// 添加自定义触摸事件来控制控件显示位置
    ```

    



- 视频信号对接

  ```
  @Override
  public void onAddTrack(MediaStreamTrack track, MediaStream[] mediaStreams) {
      if (track instanceof VideoTrack) {
          // 同时展示本地视频画面
          VideoTrack localVideo = mDUIXCore.getLocalVideoTrack();
          if (localVideo != null) {
              localVideo.addSink(small_renderer);
              }
              // 展示远端信号
              VideoTrack remoteVideoTrack = (VideoTrack) track;
              remoteVideoTrack.addSink(fullscreen_renderer);
          }
  }
  
  ```



- 关闭会话并释放资源

  ```
  @Override
  protected void onDestroy() {
      super.onDestroy();
      if (fullscreen_renderer != null) {
          fullscreen_renderer.release();
      }
      if (small_renderer != null) {
          small_renderer.release();
      }
      if (mDUIXCore != null) {
          if (UUID != null) {
              mDUIXCore.closeSession(UUID);
          }
          mDUIXCore.removeDUIXListener(mDUIXCallback);
          mDUIXCore.release();
      }
  }
  ```



### 注意事项

1. rtc模块需要使用麦克风，可能使用到摄像头。需要动态申请权限。
2. 过一段时间后会话自动关闭，看是否在available()回调中发送pingSession()信号。



