376 lines
9.1 KiB
Markdown
376 lines
9.1 KiB
Markdown
# SellyRTC Android SDK 接入文档
|
||
|
||
本文档用于指导 **Android App 开发者** 快速接入 SellyRTC,完成一对一或多人实时音视频通话能力。
|
||
|
||
SDK 核心以 `InteractiveRtcEngine` 为中心,通过 `InteractiveRtcEngineEventHandler` 回调通话状态、用户事件、音视频状态及异常。
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. 准备工作
|
||
2. 快速开始
|
||
3. 基础通话流程
|
||
4. 常用功能
|
||
5. 屏幕分享
|
||
6. 视频帧前后处理
|
||
7. 事件回调(EventHandler)
|
||
8. 通话统计
|
||
9. Token 机制
|
||
10. 常见问题(FAQ)
|
||
|
||
---
|
||
|
||
## 准备工作
|
||
|
||
### 1. 环境要求
|
||
- Android 8.0+(Demo `minSdk` 为 26)
|
||
- 需真机运行(摄像头 / 麦克风)
|
||
|
||
### 2. 权限配置(AndroidManifest.xml)
|
||
|
||
```xml
|
||
<uses-permission android:name="android.permission.CAMERA" />
|
||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||
```
|
||
|
||
---
|
||
|
||
## 快速开始
|
||
|
||
### 1. 创建引擎
|
||
|
||
```kotlin
|
||
val appId = getString(R.string.signaling_app_id)
|
||
val token = getString(R.string.signaling_token).takeIf { it.isNotBlank() }
|
||
val kiwiRsName = getString(R.string.signaling_kiwi_rsname).trim()
|
||
|
||
val rtcEngine = InteractiveRtcEngine.create(
|
||
InteractiveRtcEngineConfig(
|
||
context = applicationContext,
|
||
appId = appId,
|
||
defaultToken = token,
|
||
kiwiRsName = kiwiRsName
|
||
)
|
||
).apply {
|
||
setEventHandler(eventHandler)
|
||
setClientRole(InteractiveRtcEngine.ClientRole.BROADCASTER)
|
||
setVideoEncoderConfiguration(
|
||
InteractiveVideoEncoderConfig(
|
||
width = 640,
|
||
height = 480,
|
||
fps = 20,
|
||
minBitrateKbps = 150,
|
||
maxBitrateKbps = 850
|
||
)
|
||
)
|
||
setDefaultAudioRoutetoSpeakerphone(true)
|
||
}
|
||
```
|
||
|
||
> `InteractiveRtcEngineConfig` 与默认 token 配置见 `example/src/main/java/com/demo/SellyCloudSDK/interactive/InteractiveLiveActivity.kt`。
|
||
|
||
### 2. 设置本地/远端画布
|
||
|
||
```kotlin
|
||
val localRenderer = SurfaceViewRenderer(this)
|
||
rtcEngine.setupLocalVideo(InteractiveVideoCanvas(localRenderer, userId))
|
||
```
|
||
|
||
```kotlin
|
||
val remoteRenderer = SurfaceViewRenderer(this)
|
||
rtcEngine.setupRemoteVideo(InteractiveVideoCanvas(remoteRenderer, remoteUserId))
|
||
```
|
||
|
||
### 3. 加入通话
|
||
|
||
```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)
|
||
```
|
||
|
||
### 4. 进阶配置(Demo 未覆盖)
|
||
|
||
#### 4.1 InteractiveRtcEngineConfig 高级字段
|
||
|
||
```kotlin
|
||
val config = InteractiveRtcEngineConfig(
|
||
context = applicationContext,
|
||
appId = appId,
|
||
defaultCallType = CallType.ONE_TO_ONE,
|
||
defaultToken = token,
|
||
kiwiRsName = kiwiRsName,
|
||
signalingUrlPrefix = "https://",
|
||
signalingUrlSuffix = "/signaling"
|
||
)
|
||
```
|
||
|
||
#### 4.2 InteractiveChannelMediaOptions 订阅控制
|
||
|
||
```kotlin
|
||
val options = InteractiveChannelMediaOptions(
|
||
callType = CallType.GROUP,
|
||
autoSubscribeAudio = true,
|
||
autoSubscribeVideo = false
|
||
)
|
||
```
|
||
|
||
#### 4.3 InteractiveVideoEncoderConfig 更多参数
|
||
|
||
可选项(按需设置):
|
||
|
||
- `codecType`:编码类型
|
||
- `bitrateMode`:码率控制模式
|
||
- `minBitrateKbps` / `maxBitrateKbps`:码率范围
|
||
- `orientationMode`:画面方向策略
|
||
- `degradationPreference`:弱网降级策略
|
||
- `mirrorMode`:镜像策略
|
||
|
||
---
|
||
|
||
## 基础通话流程
|
||
|
||
1. 创建 `InteractiveRtcEngine`
|
||
2. 设置 `EventHandler`
|
||
3. 配置 `InteractiveVideoEncoderConfig`
|
||
4. 设置本地画布 `setupLocalVideo`
|
||
5. `joinChannel` 加入频道
|
||
6. `onUserJoined` 后设置远端画布
|
||
7. 通话中进行音视频控制
|
||
8. `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.setCaptureVideoFrameInterceptor { frame ->
|
||
// 在此处理美颜/滤镜,返回新的 frame
|
||
frame
|
||
}
|
||
```
|
||
|
||
```kotlin
|
||
rtcEngine.setRenderVideoFrameInterceptor { frame, userId ->
|
||
// 远端渲染前处理,返回 true 表示继续渲染
|
||
true
|
||
}
|
||
```
|
||
|
||
> Demo 中的美颜示例见:
|
||
> `example/src/main/java/com/demo/SellyCloudSDK/beauty/FuVideoFrameInterceptor.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)
|
||
```
|
||
|
||
---
|
||
|
||
## 更多 API 速览(含 Demo 未覆盖)
|
||
|
||
引擎创建与销毁:
|
||
|
||
- `InteractiveRtcEngine.create(config)`:创建引擎
|
||
- `InteractiveRtcEngine.destroy(engine)` / `engine.destroy()`:释放引擎
|
||
|
||
通话控制:
|
||
|
||
- `setEventHandler(handler)`:设置事件回调
|
||
- `setClientRole(role)`:设置角色(主播/观众)
|
||
- `setVideoEncoderConfiguration(config)`:设置编码参数
|
||
- `setDefaultAudioRoutetoSpeakerphone(true/false)`:设置音频输出
|
||
- `joinChannel(...)` / `leaveChannel()`:加入 / 离开频道
|
||
|
||
本地与远端控制:
|
||
|
||
- `setupLocalVideo(canvas)` / `setupRemoteVideo(canvas)`:设置画布
|
||
- `clearRemoteVideo(userId)`:清理远端画面
|
||
- `enableLocalVideo(true/false)` / `enableLocalAudio(true/false)`:开关本地音视频
|
||
- `muteRemoteAudioStream(userId, true/false)` / `muteRemoteVideoStream(userId, true/false)`:按用户静音
|
||
- `muteAllRemoteAudioStreams(true/false)` / `muteAllRemoteVideoStreams(true/false)`:全量静音
|
||
- `switchCamera()`:切换摄像头
|
||
|
||
帧处理与屏幕共享:
|
||
|
||
- `setCaptureVideoFrameInterceptor(...)`:采集前帧处理
|
||
- `setRenderVideoFrameInterceptor(...)`:渲染前帧处理
|
||
- `startScreenShare(...)` / `stopScreenShare()` / `isScreenSharing()`:屏幕共享
|
||
|
||
消息与 Token:
|
||
|
||
- `sendMessage(message, targetUserId, callback)`:发送消息(`targetUserId` 可为空)
|
||
- `renewToken(newToken, expiresAtSec)`:续期 Token
|
||
|
||
---
|
||
|
||
## 常见问题(FAQ)
|
||
|
||
### Q:远端画面不显示?
|
||
1. 是否设置了正确的 `remoteUserId`
|
||
2. 是否在 `onUserJoined` 后调用 `setupRemoteVideo`
|
||
3. 远端是否关闭了视频
|
||
|
||
### Q:加入频道失败?
|
||
1. 检查 `signaling_app_id` 是否正确
|
||
2. Token 是否为空或已过期
|
||
3. 网络是否受限
|
||
|
||
### Q:屏幕分享失败?
|
||
1. 是否已获取 `MediaProjection` 授权
|
||
2. Android 14+ 是否启动前台服务
|