# 进阶集成指引 (/zh-CN/solutions/one-to-one-live/custom-signaling/build/customize-and-extend/integration-guideline)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
## 使用已初始化过的 rtmClient [#使用已初始化过的-rtmclient]

在集成 1v1 私密房方案过程中，你可以参考本文对 API 的调用时机、逻辑、参数设置等进行调优。声网 1v1 私密房的 `CallAPI` 中已经实现了对所需 RTM 服务的封装。如果在集成 1v1 私密房之前，你的项目中已经有 RTM 实例 `rtmClient` 了，则可以直接使用初始化过的 RTM 实例，然后调用相关功能。

:::info[信息]
如果你使用自己创建的 `rtmClient` 实例，则可以自行维持 RTM 的登录状态；你也可以通过我们提供的 `CallRtmManager` 中的 `login` 和 `logout` 方法管理登录和登出。
:::

```kotlin
val rtmConfig = RtmConfig.Builder(appId, userId.toString()).build()
val rtmClient = RtmClient.create(rtmConfig)
rtmClient.login(token, object : ResultCallback<Void?> {
    override fun onSuccess(p0: Void?) {
        // 登录成功即可初始化 CallRtmManager、CallRtmSignalClient、CallApi
    }

    override fun onFailure(p0: ErrorInfo?) {
        // 登录失败
    }
})
```

## 切换被叫的发布和订阅时机以节省费用 [#切换被叫的发布和订阅时机以节省费用]

在收到呼叫后，被叫的发布和订阅时机有两种：

- 收到呼叫后自动发布音视频流和订阅视频流，这是默认行为。
- 收到呼叫后，需要被叫接受后再发布音视频流、订阅视频流。

你可以通过 `ICallApiListener` 中的可选回调 `canJoinRtcOnCalling` 来设置发布和订阅时机：

- 如果返回 `true`，或不实现该回调方法，则使用默认的推流策略，即收到呼叫就发布音视频流和订阅视频流
- 如果返回 `false`，则使用接受呼叫后再发布音视频流、订阅视频流的策略

```kotlin
/**
  * 当呼叫时判断是否可以加入Rtc
  * @param eventInfo 收到呼叫时的扩展信息
  * @return true: 可以加入 false: 不可以加入
  */
fun canJoinRtcOnCalling(eventInfo: Map<String, Any>) : Boolean?
```

## 手动开启和关闭音视频流发布 [#手动开启和关闭音视频流发布]

由于 `CallAPI` 内部会在通话时开启、结束通话时关闭采集音视频，因此如果在结束通话后外部需要手动开启音视频采集，例如当 `onCallStateChanged` 返回 `(state: prepared)` 时，可以开启采集。

```kotlin
rtcEngine.enableLocalAudio(true)
rtcEngine.enableLocalVideo(true)
```

## 消息中携带自定义数据结构 [#消息中携带自定义数据结构]

通过在 `PrepareConfig` 的 `userExtension` 属性中设置参数，你可以在向对端发送消息时（例如呼叫/取消呼叫/同意/拒绝等）附加额外的用户扩展信息。对端可以通过回调消息接收到这个 `userExtension`，以便在处理消息时获取相关的附加信息。

```kotlin
override fun onCallStateChanged(
    state: CallStateType,
    stateReason: CallStateReason,
    eventReason: String,
    eventInfo: Map<String, Any>
) {
    val userExtension = eventInfo[CallApiImpl.kFromUserExtension] as? Map<String, Any>
    ...
}
```

## 通话异常定位 [#通话异常定位]

在双端连接过程中（`state` 为 `calling`/`connecting`/`connected` 时），可以通过 `getCallId` 方法获取当次通话双端的呼叫 ID。

你还可以通过 `CallAPI` 内部的日志上报，在声网后台查询到当次通话的各个节点耗时。如果需要使用，可以联系 sales@shengwang.cn 申请开通声网自定义数据上报和分析服务。

## 监听通话频道的 RTC 回调 [#监听通话频道的-rtc-回调]

为了更好地了解当前通话频道的状态和事件，你还可以监听 RTC 频道的回调。由于 `CallAPI` 里使用的 `joinChannelEx` 方式加入 RTC 频道，因此不可以使用 `rtcEngine.addHandler` 方式，需要通过 `rtcEngine.addHandlerEx` 并指定对应的频道来添加 `delegate`：

```kotlin
// 可以在收到呼叫状态时保存呼叫的频道 ID
override fun onCallStateChanged(
        state: CallStateType,
        stateReason: CallStateReason,
        eventReason: String,
        eventInfo: Map<String, Any>
    ) {
    when (state) {
        CallStateType.Calling -> {
          roomId = eventInfo[kFromRoomId] as? String ?: ""
        }
        else -> {}
    }
}

// 在收到 joinRTCStart 事件后通过频道 ID 来设置监听
override fun onCallEventChanged(event: CallEvent, eventReason: String?) {
    when(event) {
        CallEvent.JoinRTCStart -> {
            rtcEngine.addHandlerEx(
                object : IRtcEngineEventHandler() {
                    override fun onJoinChannelSuccess(
                        channel: String?,
                        uid: Int,
                        elapsed: Int
                    ) {
                        super.onJoinChannelSuccess(channel, uid, elapsed)
                        // ...
                    }
                },
                RtcConnection(roomId, currentUid.toInt()) // demo 为了方便将本端uid的字符串作为了频道名
            )
        }
        else -> {}
    }
}
```

其中，当前 RTC 通话频道的 ID（`roomId` 参数）可以通过 `onCallStateChanged` 为 `calling` 时从 `eventInfo` 里解析获取。

:::warning[注意]
需要保证加入时已经触发过了 `joinRTCStart` 事件，在此事件之前调用 `rtcEngine.addHandlerEx` 会无效。
:::

## 更新过期 Token [#更新过期-token]

你可以通过如下方式，更新信令和 RTC 的过期 Token。

下列仅以 RTM 方案作为信令举例，自定义信令请参考对应信令的过期检测方法，如环信[连接状态相关](https://doc.easemob.com/document/android/connection.html)。

#### 信令 Token [#信令-token]

1. 监听 RTM Token 是否过期。通过[添加并监听 CallRtmManager 状态回调](/zh-CN/solutions/one-to-one-live/custom-signaling/build/implement-core-features/start-one-to-one) 监听 `ICallRtmManagerListener` 的 `onTokenPrivilegeWillExpire`。

   ```kotlin
   val listener = object: ICallRtmManagerListener {
       override fun onTokenPrivilegeWillExpire(channelName: String) {
           // Token 过期，需要重新获取 Token
       }
   }
   ```

2. 更新 Token。

   ```kotlin
   // 更新 RTC Token
   this.api.renewToken(rtcToken)

   // 更新 RTM Token
   this.rtmManager?.renewToken(rtmToken)

   // 为确保 RTM 和 RTC Token 同时有效，建议同时更新这两个 Token
   ```

### RTC Token [#rtc-token]

1. 监听 RTC Token 是否过期。

   - 监听进行通话时过期的 Token。

     ```kotlin
     val listener = object: ICallApiListener {
         override fun tokenPrivilegeWillExpire() {
             // Token过期，需要重新获取 Token
         }
     }
     ```

   - 监听进行通话之前过期的 Token。

     ```kotlin
     val listener = ICallApiListener {
         override fun onCallError(errorEvent: CallErrorEvent,
                     errorType: CallErrorCodeType,
                     errorCode: Int,
                     message: String?) {
             if (errorEvent == CallErrorEvent.RtcOccurError && errorType == CallErrorCodeType.Rtc && errorCode == ErrorCode.TokenExpired.value {
                 // RTC 加入频道失败，需要取消呼叫，并重新获取 Token
                 this.api.cancelCall { err -> }
             }
         }
     }
     ```

2. 更新 Token。

   ```kotlin
   // 更新 RTC Token
   this.api.renewToken(rtcToken)

   // 更新 RTM Token
   this.rtmManager?.renewToken(rtmToken)

   // 为确保 RTM 和 RTC Token 同时有效，建议同时更新这两个 Token
   ```

:::info[信息]
建议给 RTC 和 RTM 配置相同的 Token 过期时间，然后通过[信令 Token](/zh-CN/solutions/one-to-one-live/custom-signaling/build/customize-and-extend/integration-guideline) 方式，借助 RTM 来检测过期并同时更新。
:::
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
在集成 1v1 私密房方案过程中，你可以参考本文对 API 的调用时机、逻辑、参数设置等进行调优。

## 使用已初始化过的 rtmClient [#使用已初始化过的-rtmclient-1]

声网 1v1 私密房的 `CallAPI` 中已经实现了对所需 RTM 服务的封装。如果在集成 1v1 私密房之前，你的项目中已经有 RTM 实例 `rtmClient` 了，则可以直接使用初始化过的 RTM 实例，然后调用相关功能。

:::info[信息]
如果你使用自己创建的 `rtmClient` 实例，则可以自行维持 RTM 的登录状态；你也可以通过我们提供的 `CallRtmManager` 中的 `login` 和 `logout` 方法管理登录和登出。
:::

```swift
let rtmClient:AgoraRtmClientKit? = _createRtmClient()
rtmClient?.login(token) {[weak self] resp, error in
    if let error = error {return}
  
    // 登录成功即可初始化 CallRtmManager、CallRtmSignalClient、CallApi
```

## 切换被叫的发布和订阅时机以节省费用 [#切换被叫的发布和订阅时机以节省费用-1]

在收到呼叫后，被叫的发布和订阅时机有两种：

- 收到呼叫后自动发布音视频流和订阅视频流，这是默认行为。
- 收到呼叫后，需要被叫接受后再发布音视频流、订阅视频流。

你可以通过 `CallApiListenerProtocol` 中的可选回调 `canJoinRtcOnCalling` 来设置发布和订阅时机：

- 如果返回 `true`，或不实现该回调方法，则使用默认的推流策略，即收到呼叫就发布音视频流和订阅视频流
- 如果返回 `false`，则使用接受呼叫后再发布音视频流、订阅视频流的策略

```swift
/// 当收到呼叫时判断是否可以加入 RTC
/// @param eventInfo: 收到呼叫时的扩展信息
/// @return
/// - true：可以加入
/// - false：不可以加入
@objc optional func canJoinRtcOnCalling(eventInfo: [String: Any]) -> Bool
```

## 手动开启和关闭音视频流发布 [#手动开启和关闭音视频流发布-1]

由于 `CallAPI` 内部会在通话时开启、结束通话时关闭采集音视频，因此如果在结束通话后外部需要手动开启音视频采集，例如当 `onCallStateChanged` 返回 `(state: prepared)` 时，可以开启采集。

```swift
rtcEngine.enableLocalAudio(true)
rtcEngine.enableLocalVideo(true)
```

## 消息中携带自定义数据结构 [#消息中携带自定义数据结构-1]

通过在 `PrepareConfig` 的 `userExtension` 属性中设置参数，你可以在向对端发送消息时（例如呼叫/取消呼叫/同意/拒绝等）附加额外的用户扩展信息。对端可以通过回调消息接收到这个 `userExtension`，以便在处理消息时获取相关的附加信息。

```swift
public func onCallStateChanged(with state: CallStateType,
                               stateReason: CallStateReason,
                               eventReason: String,
                               eventInfo: [String : Any]) {
    let userExtension = eventInfo[kFromUserExtension] as? [String: Any]
    ...          
}
```

## 通话异常定位 [#通话异常定位-1]

在双端连接过程中（`state` 为 `calling`/`connecting`/`connected` 时），可以通过 `getCallId` 方法获取当次通话双端的呼叫 ID。

你还可以通过 `CallAPI` 内部的日志上报，在声网后台查询到当次通话的各个节点耗时。如果需要使用，可以联系 sales@shengwang.cn 申请开通声网自定义数据上报和分析服务。

## 监听通话频道的 RTC 回调 [#监听通话频道的-rtc-回调-1]

为了更好地了解当前通话频道的状态和事件，你还可以监听 RTC 频道的回调。由于 `CallAPI` 里使用的 `joinChannelEx` 方式加入 RTC 频道，因此不可以使用 `rtcEngine.addDelegate` 方式，需要通过 `rtcEngine.addDelegateEx` 并指定对应的频道来添加 `delegate`：

```swift
// 可以在收到呼叫状态时保存呼叫的频道 ID
func onCallStateChanged(with state: CallStateType,
                        stateReason: CallStateReason,
                        eventReason: String,
                        eventInfo: [String : Any]) {
    switch state {
    case .calling:
        roomId = eventInfo[kFromRoomId] as? String ?? ""
    default:
        break
    }
}

// 在收到 joinRTCStart 事件后通过频道 ID 来设置监听
@objc func onCallEventChanged(with event: CallEvent, eventReason: String?) {
    switch event {
    case .joinRTCStart:
    /// @param roomId 当前 RTC 通话频道 ID
    /// @param currentUid 当前用户 ID
    let connection = AgoraRtcConnection(channelId: roomId, localUid: Int(currentUid))
    rtcEngine.addDelegateEx(self, connection: connection)
    default:
        break
    }
}
```

其中，当前 RTC 通话频道的 ID（`roomId` 参数）可以通过 `onCallStateChanged` 为 `calling` 时从 `eventInfo` 里解析获取。

:::warning[注意]
需要保证加入时已经触发过了 `joinRTCStart` 事件，在此事件之前调用 `rtcEngine.addDelegateEx` 会无效。
:::

## 更新过期 Token [#更新过期-token-1]

你可以通过如下方式，更新信令和 RTC 的过期 Token。

下列仅以 RTM 方案作为信令举例，自定义信令请参考对应信令的过期检测方法，如环信[连接状态相关](https://doc.easemob.com/document/ios/connection.html)。

#### 信令 Token [#信令-token-1]

1. 监听 RTM Token 是否过期。通过[添加并监听 CallRtmManager 状态回调](/zh-CN/solutions/one-to-one-live/custom-signaling/build/implement-core-features/start-one-to-one) 监听 `ICallRtmManagerListener` 的 `onTokenPrivilegeWillExpire`。

   ```swift
   extension Pure1v1RoomViewController: ICallRtmManagerListener {
       func onTokenPrivilegeWillExpire(channelName: String) {
           // Token 过期，需要重新获取 Token
       }
   }
   ```

2. 更新 Token。

   ```swift
   // 更新 RTC Token
   self.api.renewToken(with: rtcToken)

   // 更新 RTM Token
   self.rtmManager?.renewToken(rtmToken: rtmToken)

   // 为确保 RTM 和 RTC Token 同时有效，建议同时更新这两个 Token
   ```

#### RTC Token [#rtc-token-1]

1. 监听 RTC Token 是否过期。

   - 监听进行通话时过期的 Token。

     ```swift
     extension Pure1v1RoomViewController:CallApiListenerProtocol {
         func tokenPrivilegeWillExpire() {
             // Token过期，需要重新获取 Token
         }
     }
     ```

   - 监听进行通话之前过期的 Token。

     ```swift
     extension Pure1v1RoomViewController:CallApiListenerProtocol {
         @objc func onCallError(with errorEvent: CallErrorEvent,
                                     errorType: CallErrorCodeType,
                                     errorCode: Int,
                                     message: String?) {
             if errorEvent == .rtcOccurError, errorType == .rtc, errorCode == AgoraErrorCode.tokenExpired.rawValue {
                 // RTC 加入频道失败，需要取消呼叫，并重新获取 Token
                 self.api.cancelCall { err in
                 }
             }
         }
     }
     ```

2. 更新 Token。

   ```swift
   // 更新 RTC Token
   self.api.renewToken(with: rtcToken)

   // 更新 RTM Token
   self.rtmManager?.renewToken(rtmToken: rtmToken)

   // 为确保 RTM 和 RTC Token 同时有效，建议同时更新这两个 Token
   ```

:::info[信息]
建议给 RTC 和 RTM 配置相同的 Token 过期时间，然后通过[信令 Token](/zh-CN/solutions/one-to-one-live/custom-signaling/build/customize-and-extend/integration-guideline) 方式，借助 RTM 来检测过期并同时更新。
:::
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
