# SellyRTC Android SDK 接入文档
本文档用于指导 **Android App 开发者** 快速接入 SellyRTC,完成一对一或多人实时音视频通话能力。
SDK 核心以 `InteractiveRtcEngine` 为中心,通过 `InteractiveRtcEngineEventHandler` 回调通话状态、用户事件、音视频状态及异常。
当前版本的互动渲染模型已经从“仅 `SurfaceViewRenderer`”扩展为“`RtcRenderTarget` 抽象 + 多种后端实现”:
- `SurfaceViewRenderer` 旧路径仍可用
- `TextureView` 已可用于本地/远端视频渲染
- 推荐在 **加入频道前** 选定本地渲染后端
---
## 目录
1. 准备工作
2. 快速开始
3. 基础通话流程
4. 常用功能
5. 屏幕分享
6. 视频帧前后处理
7. 事件回调(EventHandler)
8. 通话统计
9. Token 机制
10. 代理地址配置
11. 常见问题(FAQ)
---
## 准备工作
### 1. 环境要求
- Android 8.0+(Demo `minSdk` 为 26)
- 需真机运行(摄像头 / 麦克风)
### 2. 权限配置(AndroidManifest.xml)
```xml
```
---
## 快速开始
### 1. SDK 初始化
在使用音视频通话功能前,需先初始化 SDK:
```kotlin
SellyCloudManager.initialize(
context = applicationContext,
appId = "your-app-id"
)
```
> `initialize` 的 `appId` 参数为权威值。可选传入 `SellyCloudConfig` 配置 `vhost`、`logEnabled` 等,详见推拉流文档。
### 2. 代理地址设置(可选)
若需通过代理(如洋葱盾)连接信令服务器,在创建引擎前设置:
```kotlin
SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")
```
> SDK 内部通过代理地址解析真实信令服务器 IP。不设置则使用直连。详见「代理地址配置」章节。
### 3. 创建引擎
```kotlin
val appId = getString(R.string.signaling_app_id)
val token = getString(R.string.signaling_token).takeIf { it.isNotBlank() }
val rtcEngine = InteractiveRtcEngine.create(
InteractiveRtcEngineConfig(
context = applicationContext,
appId = appId,
defaultToken = token
)
).apply {
setEventHandler(eventHandler)
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
setVideoEncoderConfiguration(
InteractiveVideoEncoderConfig(
width = 640,
height = 480,
fps = 20,
minBitrateKbps = 150,
maxBitrateKbps = 850
)
)
setDefaultAudioRoutetoSpeakerphone(true)
}
```
`InteractiveRtcEngineConfig` 参数说明:
| 参数 | 类型 | 说明 |
| ---- | ---- | ---- |
| `context` | Context | 应用上下文 |
| `appId` | String | 应用 ID |
| `defaultCallType` | CallType | 默认通话类型,默认 ONE_TO_ONE |
| `defaultToken` | String? | 默认 Token |
| `signalingUrlPrefix` | String | 信令 URL 前缀,默认 `ws://` |
| `signalingUrlSuffix` | String | 信令 URL 后缀,默认 `/ws/signaling` |
> 完整 Demo 见 `example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt`。
### 4. 设置本地/远端画布
推荐使用 `InteractiveVideoCanvas(renderTarget, userId)` 新接口。
#### 4.1 SurfaceViewRenderer 旧路径
```kotlin
val localRenderer = SurfaceViewRenderer(this)
val localCanvas = InteractiveVideoCanvas(
com.sellycloud.sellycloudsdk.render.SurfaceViewRtcTarget(localRenderer),
userId
)
rtcEngine.setupLocalVideo(localCanvas)
```
```kotlin
val remoteRenderer = SurfaceViewRenderer(this)
val remoteCanvas = InteractiveVideoCanvas(
com.sellycloud.sellycloudsdk.render.SurfaceViewRtcTarget(remoteRenderer),
remoteUserId
)
rtcEngine.setupRemoteVideo(remoteCanvas)
```
#### 4.2 TextureView 路径
```kotlin
val localTextureView = com.sellycloud.sellycloudsdk.widget.AspectRatioTextureView(this)
val localCanvas = InteractiveVideoCanvas(
com.sellycloud.sellycloudsdk.render.TextureViewRtcTarget(localTextureView),
userId
)
rtcEngine.setupLocalVideo(localCanvas)
```
```kotlin
val remoteTextureView = com.sellycloud.sellycloudsdk.widget.AspectRatioTextureView(this)
val remoteCanvas = InteractiveVideoCanvas(
com.sellycloud.sellycloudsdk.render.TextureViewRtcTarget(remoteTextureView),
remoteUserId
)
rtcEngine.setupRemoteVideo(remoteCanvas)
```
兼容说明:
- `InteractiveVideoCanvas(view: SurfaceViewRenderer, userId)` 旧构造仍可用(deprecated)
- 推荐新接入统一走 `RtcRenderTarget`
- 当前高层互动 API 还没有直接暴露 `SurfaceTexture` 入口;Android 场景推荐 `SurfaceViewRenderer` 或 `TextureView`
所有权说明:
- 调用方自己创建的 `SurfaceViewRenderer` / `TextureView`,由调用方负责释放
- SDK 只在 `setupLocalVideo` / `setupRemoteVideo` 中绑定 target,在 `leaveChannel` 时解绑
- 调用方应在 `leaveChannel` 之后、Activity 销毁前释放自己创建的 View
### 5. 加入通话
```kotlin
val options = InteractiveChannelMediaOptions(callType = CallType.ONE_TO_ONE)
rtcEngine.joinChannel(
token = token,
channel = callId,
userId = userId,
options = options,
tokenSecret = tokenSecret,
tokenExpiresAtSec = tokenExpiresAtSec,
tokenTtlSeconds = tokenTtlSeconds
)
```
结束通话:
```kotlin
rtcEngine.leaveChannel()
```
销毁引擎:
```kotlin
InteractiveRtcEngine.destroy(rtcEngine)
```
### 6. 进阶配置(Demo 未覆盖)
#### 6.1 InteractiveChannelMediaOptions 订阅控制
```kotlin
val options = InteractiveChannelMediaOptions(
callType = CallType.GROUP,
autoSubscribeAudio = true,
autoSubscribeVideo = false
)
```
#### 6.2 InteractiveVideoEncoderConfig 更多参数
可选项(按需设置):
- `codecType`:编码类型
- `bitrateMode`:码率控制模式
- `minBitrateKbps` / `maxBitrateKbps`:码率范围
- `orientationMode`:画面方向策略
- `degradationPreference`:弱网降级策略
- `mirrorMode`:镜像策略
---
## 基础通话流程
1. 初始化 SDK(`SellyCloudManager.initialize`)
2. 设置代理地址(可选,`SellyCloudManager.setProxyAddress`)
3. 创建 `InteractiveRtcEngine`
4. 设置 `EventHandler`
5. 配置 `InteractiveVideoEncoderConfig`
6. 设置本地画布 `setupLocalVideo`(建议在 `joinChannel` 前完成,并在此阶段确定 backend)
7. `joinChannel` 加入频道
8. `onUserJoined` 后设置远端画布;也可以提前为某个 `userId` 调用 `setupRemoteVideo`,SDK 会在用户真正上线后自动 attach
9. 通话中进行音视频控制
10. `leaveChannel` 并释放资源
---
## 常用功能
### 本地音视频控制
```kotlin
rtcEngine.enableLocalVideo(true)
rtcEngine.enableLocalAudio(false)
```
### 切换摄像头
```kotlin
rtcEngine.switchCamera()
```
### 音频输出(扬声器 / 听筒)
```kotlin
rtcEngine.setDefaultAudioRoutetoSpeakerphone(true)
```
### 发送自定义消息
```kotlin
rtcEngine.sendMessage("hello") { error ->
// error == null 表示成功
}
```
### 按用户静音远端音频/视频
```kotlin
rtcEngine.muteRemoteAudioStream(remoteUserId, true)
rtcEngine.muteRemoteVideoStream(remoteUserId, true)
```
### 全量静音远端音视频(Demo 未覆盖)
```kotlin
rtcEngine.muteAllRemoteAudioStreams(true)
rtcEngine.muteAllRemoteVideoStreams(true)
```
### 清理远端画布(Demo 未覆盖)
```kotlin
rtcEngine.clearRemoteVideo(remoteUserId)
```
### 向指定用户发送消息(Demo 未覆盖)
```kotlin
rtcEngine.sendMessage("hello", targetUserId) { error ->
// error == null 表示成功
}
```
---
## 屏幕分享
```kotlin
val started = rtcEngine.startScreenShare(
resultCode = resultCode,
data = data,
width = 720,
height = 1280,
fps = 15
)
```
```kotlin
rtcEngine.stopScreenShare()
```
```kotlin
val isSharing = rtcEngine.isScreenSharing()
```
> Android 14+ 屏幕捕获需要前台服务(Demo 使用 `InteractiveForegroundService` 处理)。
---
## 视频帧前后处理
```kotlin
rtcEngine.setCaptureVideoProcessor(object : VideoProcessor {
override val config = VideoProcessorConfig(
preferredFormat = VideoProcessFormat.TEXTURE_2D,
mode = VideoProcessMode.READ_WRITE,
fullRewrite = true
)
override fun processTexture(input: VideoTextureFrame, outputTextureId: Int) {
// 推荐在 GPU texture 上处理采集前帧,美颜/滤镜直接写入 outputTextureId
}
})
```
```kotlin
val renderObserver = rtcEngine.addRenderVideoFrameObserver(object : VideoFrameObserver {
override val config = VideoFrameObserverConfig(
preferredFormat = VideoProcessFormat.TEXTURE_2D,
stage = VideoStage.RENDER_PRE_DISPLAY
)
override fun onTextureFrame(frame: VideoTextureFrame) {
// 远端渲染前只读观测
val userId = frame.sourceId
}
})
```
> 推荐优先使用 `TEXTURE_2D`:
> - `TEXTURE_2D` 适合美颜、滤镜、AR、水印等 GPU 处理链路。
> - `I420` / `RGBA` 仅在算法必须访问 CPU 像素时再使用。
> - 对 RTC / WHIP 的 texture-backed 帧,走 CPU observer / processor 会触发额外的 texture-to-CPU 转换。
> - `VideoFrameObserverConfig` 默认仍为 `I420` 以兼容旧接入;新 RTC / WHIP 接入建议显式写 `preferredFormat = TEXTURE_2D`。
> - 完整重写输出的处理器建议设置 `fullRewrite = true`;水印/叠加类处理保留默认值即可。
>
> Demo 中的采集前美颜示例见:
> `example/src/main/java/com/demo/SellyCloudSDK/beauty/FaceUnityBeautyEngine.kt`
>
> 当前 Demo 的互动页接入见:
> `example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt`
---
## 事件回调(EventHandler)
```kotlin
val eventHandler = object : InteractiveRtcEngineEventHandler {
override fun onJoinChannelSuccess(channel: String, userId: String, code: Int) {}
override fun onLeaveChannel(durationSeconds: Int) {}
override fun onUserJoined(userId: String, code: Int) {}
override fun onUserLeave(userId: String, code: Int) {}
override fun onConnectionStateChanged(state: InteractiveConnectionState, reason: Int, userId: String?) {}
override fun onMessageReceived(message: String, userId: String?) {}
override fun onError(code: String, message: String) {}
override fun onTokenWillExpire(token: String?, expiresAt: Long) {}
override fun onTokenExpired(token: String?, expiresAt: Long) {}
override fun onLocalVideoStats(stats: InteractiveStreamStats) {}
override fun onRemoteVideoStats(stats: InteractiveStreamStats) {}
override fun onDuration(durationSeconds: Long) {}
override fun onRemoteVideoEnabled(enabled: Boolean, userId: String?) {}
override fun onRemoteAudioEnabled(enabled: Boolean, userId: String?) {}
override fun onStreamStateChanged(peerId: String, state: RemoteState, code: Int, message: String?) {}
}
```
---
## 通话统计
`InteractiveStreamStats` 常用字段:
- width / height
- fps
- videoBitrateKbps / audioBitrateKbps
- rttMs
- videoCodec / audioCodec
---
## Token 机制
### Token 来源
- 业务侧服务端生成 Token 并下发
- Demo 可在 `strings.xml` 配置 `signaling_token` 或 `signaling_secret` 进行本地生成
### Token 过期处理
- `onTokenWillExpire` / `onTokenExpired` 回调提示即将过期或已过期
- 建议获取新 Token 并 **重新 joinChannel**
Token 续期接口(Demo 未覆盖):
```kotlin
rtcEngine.renewToken(newToken, expiresAtSec)
```
---
## 代理地址配置
SDK 支持通过外部代理(如洋葱盾等安全加速服务)连接信令服务器。代理地址由业务方在 SDK 外部获取,然后注入 SDK。
### 设置方式
```kotlin
// 设置代理地址(在 joinChannel 之前)
SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")
// 清除代理(恢复直连)
SellyCloudManager.setProxyAddress(null)
// 查询当前代理地址
val proxy = SellyCloudManager.getProxyAddress() // null 表示未设置
```
### 格式要求
- 必须以 `http://` 或 `https://` 开头
- 传 `null` 或空字符串表示清除代理
- 格式不合法时抛出 `IllegalArgumentException`
### 生效范围
设置后,SDK 内部通过代理地址解析真实信令服务器 IP,对上层接口透明。
### 时机要求
- 必须在 `joinChannel()` **之前** 设置
- 通话过程中修改代理地址,需 `leaveChannel` 后重新 `joinChannel` 才能生效
### Demo 中的接入示例
Demo 使用 `KiwiHelper` 封装洋葱盾的初始化与代理获取,采用三阶段模式:
```kotlin
// 阶段 1:Application.onCreate() 异步初始化
KiwiHelper.initializeAsync()
// 阶段 2:Activity 初始化时启动代理获取(非阻塞)
KiwiHelper.startProxySetup(enableKiwi = true, rsName = "your-rs-name")
// 阶段 3:joinChannel 前确保代理已就绪
lifecycleScope.launch {
KiwiHelper.awaitProxyReady()
rtcEngine.joinChannel(...)
}
```
> `KiwiHelper` 内部通过 `SellyCloudManager.setProxyAddress()` 将代理地址传给 SDK。
> 详见 `example/src/main/java/com/demo/SellyCloudSDK/KiwiHelper.kt`。
---
## 更多 API 速览(含 Demo 未覆盖)
引擎创建与销毁:
- `InteractiveRtcEngine.create(config)`:创建引擎
- `InteractiveRtcEngine.destroy(engine)` / `engine.destroy()`:释放引擎
SDK 初始化与代理:
- `SellyCloudManager.initialize(context, appId, config)`:初始化 SDK
- `SellyCloudManager.setProxyAddress(address)`:设置代理地址
- `SellyCloudManager.getProxyAddress()`:获取当前代理地址
通话控制:
- `setEventHandler(handler)`:设置事件回调
- `setClientRole(role)`:设置角色(主播/观众)
- `setVideoEncoderConfiguration(config)`:设置编码参数
- `setDefaultAudioRoutetoSpeakerphone(true/false)`:设置音频输出
- `joinChannel(...)` / `leaveChannel()`:加入 / 离开频道
本地与远端控制:
- `setupLocalVideo(canvas)` / `setupRemoteVideo(canvas)`:设置画布
- `InteractiveVideoCanvas(renderTarget, userId, renderMode)`:推荐画布模型
- `clearRemoteVideo(userId)`:清理远端画面
- `enableLocalVideo(true/false)` / `enableLocalAudio(true/false)`:开关本地音视频
- `muteRemoteAudioStream(userId, true/false)` / `muteRemoteVideoStream(userId, true/false)`:按用户静音
- `muteAllRemoteAudioStreams(true/false)` / `muteAllRemoteVideoStreams(true/false)`:全量静音
- `switchCamera()`:切换摄像头
帧处理与屏幕共享:
- `setCaptureVideoProcessor(...)`:采集前可写处理
- `addCaptureVideoFrameObserver(...)`:采集前只读观测
- `addRenderVideoFrameObserver(...)`:远端渲染前只读观测
- `startScreenShare(...)` / `stopScreenShare()` / `isScreenSharing()`:屏幕共享
消息与 Token:
- `sendMessage(message, targetUserId, callback)`:发送消息(`targetUserId` 可为空)
- `renewToken(newToken, expiresAtSec)`:续期 Token
---
## 常见问题(FAQ)
### Q:远端画面不显示?
1. 是否设置了正确的 `remoteUserId`
2. 是否在 `onUserJoined` 后调用 `setupRemoteVideo`
3. 远端是否关闭了视频
### Q:互动直播可以用 `TextureView` 吗?
可以。
推荐用法是:
- 本地:`InteractiveVideoCanvas(TextureViewRtcTarget(textureView), userId)`
- 远端:`InteractiveVideoCanvas(TextureViewRtcTarget(textureView), remoteUserId)`
注意:
- 建议在 `joinChannel` 前确定本地 backend
- 当前 Demo 在首页设置中统一选择本地 backend,进入互动页面后不再暴露切换入口
- 高层互动 API 当前未直接暴露 `SurfaceTexture` 入口
### Q:加入频道失败?
1. 检查 `signaling_app_id` 是否正确
2. Token 是否为空或已过期
3. 网络是否受限
4. 若使用代理,检查代理地址是否已正确设置
### Q:屏幕分享失败?
1. 是否已获取 `MediaProjection` 授权
2. Android 14+ 是否启动前台服务
### Q:互动通话支持 XOR 吗?
当前高层互动 API 还没有暴露 `xorKeyHex` 一类的配置入口。
- 目前已支持 XOR 的 WebRTC 路径,是直播 RTC 的 `WHIP / WHEP` 推拉流
- 互动通话如需接入 XOR,需要后续在互动链路单独暴露配置并挂载 FrameCrypto
### Q:如何接入代理/加速服务?
SDK 本身不集成任何第三方代理 SDK。业务方需在外部完成代理初始化,获取本地代理地址后,通过 `SellyCloudManager.setProxyAddress()` 注入。详见「代理地址配置」章节。