sdk更新,文档更新,统一渲染后端配置,优化美颜帧处理逻辑,补充推流状态能力Auto-Framing,webrtc支持xor加解密
This commit is contained in:
@@ -12,13 +12,16 @@ Selly Live SDK 提供完整的音视频直播能力,支持 **推流(直播
|
||||
### 主要能力
|
||||
|
||||
- 支持 **RTMP / RTC** 推流与播放模式
|
||||
- 支持 **SurfaceView / TextureView** 两套渲染后端
|
||||
- 直播播放器与点播播放器支持 **SurfaceTexture** 高级渲染接入
|
||||
- 高性能音视频采集与编码
|
||||
- 灵活的视频参数配置(分辨率 / 帧率 / 码率)
|
||||
- 推流状态与统计回调
|
||||
- 拉流播放状态与错误回调
|
||||
- 支持视频帧处理(美颜 / 滤镜 / 水印)
|
||||
- 基于 **Token 的安全鉴权机制**
|
||||
- 支持 **RTMP H264 + AAC payload XOR 保护(可选)**
|
||||
- 支持 **RTMP Payload XOR 保护(可选)**
|
||||
- 支持 **RTC(WHEP/WHIP)WebRTC Frame XOR 加解密(可选)**
|
||||
- 支持 **外部代理地址注入**(如洋葱盾等第三方安全代理)
|
||||
|
||||
---
|
||||
@@ -44,7 +47,7 @@ Selly Live SDK 提供完整的音视频直播能力,支持 **推流(直播
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation files("libs/sellycloudsdk-1.0.0.aar")
|
||||
implementation files("libs/sellycloudsdk-1.0.1.aar")
|
||||
}
|
||||
```
|
||||
|
||||
@@ -177,17 +180,18 @@ val proxy = SellyCloudManager.getProxyAddress() // null 表示未设置
|
||||
2. 调用 `pusher.token = newToken` / `player.token = newToken`
|
||||
3. 停止并重新开始推流 / 拉流流程
|
||||
|
||||
### 5.4 RTMP Payload XOR 保护(可选)
|
||||
### 5.4 RTMP / WebRTC XOR 保护(可选)
|
||||
|
||||
用途:
|
||||
|
||||
- 防止他人拿到 RTMP 地址后直接播放、转码或截图
|
||||
- 提高流地址泄露后被直接播放、转码或抓流的门槛
|
||||
|
||||
生效范围与约束:
|
||||
|
||||
- 仅对 **RTMP** 生效
|
||||
- 仅支持 **H264 + AAC**(当前版本)
|
||||
- 只处理 payload,配置帧(SPS/PPS、AAC Sequence Header)保持不变
|
||||
- **RTMP** 推拉流:支持 payload XOR,当前仅支持 **H264 + AAC**
|
||||
- **RTC(WHEP/WHIP)** 推拉流:支持 WebRTC frame XOR 加解密
|
||||
- 当前这里的 WebRTC 指直播 RTC 推拉流,不包含互动通话高层 API
|
||||
- RTMP 只处理 payload,配置帧(SPS/PPS、AAC Sequence Header)保持不变
|
||||
- 推流端与播放端必须使用**同一个 key**
|
||||
|
||||
Key 格式:
|
||||
@@ -195,12 +199,13 @@ Key 格式:
|
||||
- `hex` 字符串,建议 16 或 32 字节(即 32/64 个 hex 字符)
|
||||
- 支持 `0x` 前缀
|
||||
- 长度必须为偶数
|
||||
- 非法 key 会被忽略并关闭 XOR(会输出 warning 日志)
|
||||
- 非法 key 会直接抛出 `IllegalArgumentException`,不会静默降级
|
||||
|
||||
时机要求:
|
||||
|
||||
- 推流:请在 `startLiveWithStreamId(...)` / `startLiveWithUrl(...)` 之前设置 key
|
||||
- 推流:请在 `startLiveWithStreamId(...)` / `startLiveWithUrl(...)` 之前调用 `setXorKey(...)`
|
||||
- 拉流:请在 `initWithStreamId(...)` / `initWithUrl(...)` 创建播放器时传入 `xorKeyHex`
|
||||
- 运行中修改 key 不会影响当前连接,需重启推流或重建播放器实例
|
||||
|
||||
---
|
||||
|
||||
@@ -231,7 +236,7 @@ val config = SellyLiveVideoConfiguration.defaultConfiguration().apply {
|
||||
outputImageOrientation = SellyLiveOrientation.PORTRAIT
|
||||
}
|
||||
|
||||
pusher.attachPreview(previewContainer)
|
||||
pusher.attachPreview(previewContainer, useTextureView = false)
|
||||
pusher.startRunning(
|
||||
cameraPosition = SellyLiveCameraPosition.FRONT,
|
||||
videoConfig = config,
|
||||
@@ -239,13 +244,44 @@ pusher.startRunning(
|
||||
)
|
||||
```
|
||||
|
||||
### 6.2.1 预览后端选择
|
||||
|
||||
推流预览支持两种接入方式:
|
||||
|
||||
- `attachPreview(container, useTextureView = false)`:SDK 创建预览 View,默认走旧的 `Surface/OpenGL` 预览链路
|
||||
- `attachPreview(container, useTextureView = true)`:SDK 创建 `TextureView` 预览,适合需要普通 View 层级混排的场景
|
||||
- `setPreviewView(view)`:手动传入预览 View
|
||||
- `setPreviewView(view, mode)`:当传入 `TextureView` 时,建议使用这个显式协议版本
|
||||
|
||||
示例:
|
||||
|
||||
```kotlin
|
||||
// 默认旧路径
|
||||
pusher.attachPreview(previewContainer, useTextureView = false)
|
||||
|
||||
// TextureView 路径
|
||||
pusher.attachPreview(previewContainer, useTextureView = true)
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// 手动指定 TextureView 时,建议显式传入 liveMode
|
||||
val textureView = com.sellycloud.sellycloudsdk.widget.AspectRatioTextureView(this)
|
||||
pusher.setPreviewView(textureView, SellyLiveMode.RTMP)
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `RTMP` 模式下,SDK 内部会根据预览 View 类型自动选择 `OpenGlView` 或 `TextureView`
|
||||
- `RTC/WHIP` 预览也支持 `TextureView`
|
||||
- 当前版本建议在 **开始采集/推流前** 选定预览后端;不保证运行中热切换预览后端
|
||||
|
||||
### 6.3 设置推流 Token(使用 streamId 时)
|
||||
|
||||
```kotlin
|
||||
pusher.token = pushToken
|
||||
```
|
||||
|
||||
#### RTMP Payload XOR(可选)
|
||||
#### 推流 XOR(RTMP / RTC-WHIP,可选)
|
||||
|
||||
```kotlin
|
||||
val xorKeyHex = "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6"
|
||||
@@ -254,7 +290,7 @@ val xorKeyHex = "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6"
|
||||
pusher.setXorKey(xorKeyHex)
|
||||
```
|
||||
|
||||
> 若在推流中修改 key,需停止并重新开始推流后才会使用新 key。
|
||||
> `setXorKey(...)` 同时作用于 RTMP 推流与 RTC/WHIP 推流。若在推流中修改 key,需停止并重新开始推流后才会使用新 key。
|
||||
|
||||
### 6.4 开始/停止推流
|
||||
|
||||
@@ -295,10 +331,118 @@ pusher.stopLive { error ->
|
||||
- `setCameraEnabled(true/false)`:关闭/开启摄像头
|
||||
- `setStreamOrientation(...)`:切换推流方向
|
||||
- `setVideoConfiguration(...)` + `changeResolution(...)`:动态调整分辨率
|
||||
- `setAutoFramingEnabled(...)` / `getAutoFramingCapability()` / `getAutoFramingState()`:自动取景
|
||||
- `setBeautyEngine(...)` + `setBeautyEnabled(...)`:接入美颜
|
||||
- `setBeautyLevel(level)`:设置美颜强度
|
||||
- `setBitmapAsVideoSource(...)` / `restoreCameraVideoSource()`:背景图推流
|
||||
|
||||
### 6.5.1 美颜引擎接入
|
||||
|
||||
当前版本推荐通过 `BeautyEngine` + `VideoProcessor` 接入美颜。Demo 使用 `FaceUnityBeautyEngine`,位于:
|
||||
|
||||
- `example/src/main/java/com/demo/SellyCloudSDK/beauty/FaceUnityBeautyEngine.kt`
|
||||
|
||||
接入示例:
|
||||
|
||||
```kotlin
|
||||
val beautyEngine = FaceUnityBeautyEngine()
|
||||
|
||||
pusher.setBeautyEngine(beautyEngine)
|
||||
pusher.setBeautyEnabled(true)
|
||||
pusher.setBeautyLevel(3.0f)
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `BeautyEngine.createProcessor()` 返回的是 SDK V2 `VideoProcessor`
|
||||
- 当前 Demo 的美颜实现走 `TEXTURE_2D + READ_WRITE`
|
||||
- 美颜属于“完整重写输出”的场景,建议在 `VideoProcessorConfig` 中设置 `fullRewrite = true`
|
||||
- `RTC/WHIP` 路径优先推荐 `TEXTURE_2D`,避免对 texture-backed 帧做额外的 texture-to-CPU 转换
|
||||
|
||||
### 6.5.2 推流前帧处理与观察
|
||||
|
||||
直播推流支持:
|
||||
|
||||
- 一个可写 `VideoProcessor`
|
||||
- 多个只读 `VideoFrameObserver`
|
||||
|
||||
只读观测示例:
|
||||
|
||||
```kotlin
|
||||
val disposable = pusher.addVideoFrameObserver(object : VideoFrameObserver {
|
||||
override val config = VideoFrameObserverConfig(
|
||||
preferredFormat = VideoProcessFormat.TEXTURE_2D
|
||||
)
|
||||
|
||||
override fun onTextureFrame(frame: VideoTextureFrame) {
|
||||
// 只读观测,不修改输出
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
可写处理示例:
|
||||
|
||||
```kotlin
|
||||
pusher.setVideoProcessor(object : VideoProcessor {
|
||||
override val config = VideoProcessorConfig(
|
||||
preferredFormat = VideoProcessFormat.TEXTURE_2D,
|
||||
mode = VideoProcessMode.READ_WRITE
|
||||
)
|
||||
|
||||
override fun processTexture(input: VideoTextureFrame, outputTextureId: Int) {
|
||||
// 将滤镜/水印直接写入 SDK 提供的 outputTextureId
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
当前 SDK / Demo 的处理建议:
|
||||
|
||||
- `RTC/WHIP` 路径优先使用 `TEXTURE_2D`
|
||||
- `RTMP` 在确实需要 CPU 像素时,可使用 `I420` / `RGBA`
|
||||
- `READ_WRITE` 模式下,SDK 会准备输出缓冲;只有“完整覆盖输出”的场景才建议 `fullRewrite = true`
|
||||
- `outputTextureId` 由 SDK 管理,处理器不应转移所有权,也不应在回调里主动删除纹理
|
||||
- `VideoFrameObserverConfig` 的默认值仍为 `I420` 以兼容旧接入;新接入建议显式声明 `preferredFormat`
|
||||
|
||||
Demo 中当前可直接验证的模式:
|
||||
|
||||
- `帧回调纹理`:`TEXTURE_2D` observer
|
||||
- `帧回调空CPU`:声明 `I420`,不处理像素
|
||||
- `帧回调单CPU`:单个 `I420` observer
|
||||
- `帧回调双CPU`:两个 `I420` observer,共享同一次 CPU 转换
|
||||
- `改帧`:`RTC` 下走 `TEXTURE_2D`,`RTMP` 示例走 `RGBA`
|
||||
|
||||
### 6.5.3 自动取景(Auto Framing)
|
||||
|
||||
当前高层 API 已暴露:
|
||||
|
||||
- `setAutoFramingEnabled(enabled)`:开启 / 关闭自动取景
|
||||
- `getAutoFramingCapability()`:查询当前是否支持及原因
|
||||
- `getAutoFramingState()`:读取当前状态
|
||||
- `delegate.onAutoFramingStateChanged(state)`:接收状态变化回调
|
||||
|
||||
状态枚举:
|
||||
|
||||
- `OFF`
|
||||
- `INACTIVE`
|
||||
- `FRAMING`
|
||||
- `CONVERGED`
|
||||
- `UNSUPPORTED`
|
||||
|
||||
当前约束:
|
||||
|
||||
- 当前自动取景只在 **RTMP 推流** 路径可用
|
||||
- `RTC / WHIP` 推流当前会返回 `UNSUPPORTED`
|
||||
- 需要摄像头已启动后再查询 capability;相机关闭、背景图推流等场景也会返回不支持
|
||||
|
||||
示例:
|
||||
|
||||
```kotlin
|
||||
val capability = pusher.getAutoFramingCapability()
|
||||
if (capability.supported) {
|
||||
pusher.setAutoFramingEnabled(true)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.6 生命周期建议
|
||||
|
||||
在宿主 Activity 中对齐生命周期:
|
||||
@@ -324,21 +468,25 @@ pusher.stopLive { error ->
|
||||
- videoBitrateKbps / audioBitrateKbps
|
||||
- rttMs
|
||||
- cpu 使用率(Demo 通过 `CpuUsage` 读取)
|
||||
- auto framing state(通过 `onAutoFramingStateChanged` / `getAutoFramingState()` 获取)
|
||||
|
||||
### 6.8 推流 API 速览(含 Demo 未覆盖)
|
||||
|
||||
初始化与预览:
|
||||
|
||||
- `initWithLiveMode(context, liveMode)`:创建推流实例
|
||||
- `setPreviewView(view)`:设置预览 View
|
||||
- `attachPreview(container)`:将预览 View 添加到容器
|
||||
- `setPreviewView(view)`:设置预览 View;`TextureView` 会按当前 `liveMode` 选择协议
|
||||
- `setPreviewView(view, mode)`:显式设置预览 View 与协议,`TextureView` 推荐使用
|
||||
- `attachPreview(container)`:将默认预览 View 添加到容器
|
||||
- `attachPreview(container, useTextureView)`:创建并绑定 `Surface/OpenGL` 或 `TextureView` 预览
|
||||
- `getPreviewView()`:获取当前预览 View
|
||||
|
||||
采集与推流:
|
||||
|
||||
- `startRunning(cameraPosition, videoConfig, audioConfig)`:开始采集预览
|
||||
- `setVideoConfiguration(config)`:更新视频参数
|
||||
- `setXorKey(hexKey)`:设置 RTMP payload XOR key(可选)
|
||||
- `setXorKey(hexKey)`:设置推流 XOR key(RTMP payload / RTC-WHIP frame,可选)
|
||||
- `setAutoFramingEnabled(enabled)` / `getAutoFramingCapability()` / `getAutoFramingState()`:自动取景控制与状态查询
|
||||
- `startLiveWithStreamId(streamId)`:使用 streamId 推流
|
||||
- `startLiveWithUrl(url)`:使用完整 URL 推流
|
||||
- `stopLive()` / `stopLive(callback)`:停止推流
|
||||
@@ -356,6 +504,7 @@ pusher.stopLive { error ->
|
||||
- `setBeautyEngine(engine)`:设置美颜引擎
|
||||
- `setBeautyEnabled(true/false)`:启用 / 关闭美颜
|
||||
- `setBeautyLevel(level)`:设置美颜强度
|
||||
- `onAutoFramingStateChanged(state)`:自动取景状态回调
|
||||
- `setStreamOrientation(orientation)`:设置推流方向
|
||||
- `changeResolution(width, height)`:动态调整分辨率
|
||||
- `setBitmapAsVideoSource(bitmap)` / `restoreCameraVideoSource()`:背景图推流
|
||||
@@ -375,7 +524,7 @@ val player = SellyLiveVideoPlayer.initWithStreamId(
|
||||
context = this,
|
||||
streamId = streamId,
|
||||
liveMode = SellyLiveMode.RTC,
|
||||
xorKeyHex = "" // RTC 场景可留空
|
||||
xorKeyHex = "" // 加密流传入同一 key,明文流可留空
|
||||
)
|
||||
// 或直接使用完整 URL
|
||||
// val player = SellyLiveVideoPlayer.initWithUrl(this, playUrl, xorKeyHex = "A1B2...")
|
||||
@@ -394,7 +543,7 @@ val player = SellyLiveVideoPlayer.initWithStreamId(
|
||||
)
|
||||
```
|
||||
|
||||
> 使用 RTMP 加密流时,请在创建播放器时传入 `xorKeyHex`;后续如需换 key,请重建播放器实例。
|
||||
> 使用 RTMP 或 RTC/WHEP 加密流时,请在创建播放器时传入 `xorKeyHex`;后续如需换 key,请重建播放器实例。
|
||||
|
||||
### 7.2 设置拉流 Token(使用 streamId 时)
|
||||
|
||||
@@ -406,11 +555,35 @@ player.token = playToken
|
||||
### 7.3 播放流程
|
||||
|
||||
```kotlin
|
||||
player.attachRenderView(renderContainer)
|
||||
player.attachRenderView(renderContainer, com.sellycloud.sellycloudsdk.render.RenderBackend.SURFACE_VIEW)
|
||||
player.prepareToPlay()
|
||||
player.play()
|
||||
```
|
||||
|
||||
### 7.3.1 播放渲染后端选择
|
||||
|
||||
直播播放器支持以下渲染接入方式:
|
||||
|
||||
- `attachRenderView(container, RenderBackend.SURFACE_VIEW)`:默认旧路径
|
||||
- `attachRenderView(container, RenderBackend.TEXTURE_VIEW)`:使用 `TextureView`
|
||||
- `setRenderView(view)`:手动传入 `SurfaceView`、`SurfaceViewRenderer` 或 `TextureView`
|
||||
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:高级场景下直接绑定 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
|
||||
|
||||
示例:
|
||||
|
||||
```kotlin
|
||||
val backend = com.sellycloud.sellycloudsdk.render.RenderBackend.TEXTURE_VIEW
|
||||
player.attachRenderView(renderContainer, backend)
|
||||
player.prepareToPlay()
|
||||
player.play()
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `RTMP` 播放支持 `SurfaceView`、`TextureView`、`SurfaceTexture`
|
||||
- `RTC/WHEP` 播放支持 `SurfaceViewRenderer`、`TextureView`,以及高级场景下的 `SurfaceTexture`
|
||||
- 当前版本建议在 **开始播放前** 选定渲染后端;当前 Demo 在首页设置中统一选择,进入页面后不再暴露热切换
|
||||
|
||||
控制接口:
|
||||
|
||||
- `pause()`
|
||||
@@ -422,6 +595,8 @@ player.play()
|
||||
补充接口(Demo 未覆盖):
|
||||
|
||||
- `setRenderView(view)`:手动指定渲染 View
|
||||
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:直接绑定 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
|
||||
- `clearRenderTarget()`:解绑当前渲染面,播放会话可继续存活
|
||||
- `seekBy(deltaMs)`:播放进度跳转(仅在流支持快进/回放时有效)
|
||||
|
||||
### 7.4 播放回调
|
||||
@@ -453,7 +628,11 @@ player.delegate = object : SellyLiveVideoPlayerDelegate {
|
||||
|
||||
- `initWithStreamId(context, streamId, liveMode, vhost, appName, xorKeyHex)`:使用 streamId 创建播放器
|
||||
- `initWithUrl(context, url, xorKeyHex)`:使用完整 URL 创建播放器
|
||||
- `attachRenderView(container)` / `setRenderView(view)`:设置渲染 View
|
||||
- `attachRenderView(container)`:创建默认 `SurfaceView` 渲染 View
|
||||
- `attachRenderView(container, backend)`:创建指定 backend 的渲染 View
|
||||
- `setRenderView(view)`:手动设置渲染 View
|
||||
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:绑定 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
|
||||
- `clearRenderTarget()`:解绑当前渲染面
|
||||
- `getRenderView()`:获取当前渲染 View
|
||||
|
||||
播放控制:
|
||||
@@ -468,6 +647,17 @@ player.delegate = object : SellyLiveVideoPlayerDelegate {
|
||||
- `setStatsListener { snapshot -> }`:播放统计回调
|
||||
- `release()`:释放播放器资源
|
||||
|
||||
### 7.6 点播播放器渲染说明
|
||||
|
||||
`SellyVodPlayer` 与直播播放器在渲染后端模型上保持一致:
|
||||
|
||||
- `attachRenderView(container, backend)`:支持 `SURFACE_VIEW` / `TEXTURE_VIEW`
|
||||
- `setRenderView(surfaceView)` / `setRenderView(textureView)`:手动绑定现有 View
|
||||
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:高级场景使用 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
|
||||
- `clearRenderTarget()`:解绑当前渲染面但不一定立即销毁播放实例
|
||||
|
||||
因此 Demo 中点播页的 `SurfaceView / TextureView` 选择,也与直播播放页保持一致,均在首页设置中统一生效。
|
||||
|
||||
---
|
||||
|
||||
## 8. 错误处理与重试建议
|
||||
@@ -490,6 +680,10 @@ player.delegate = object : SellyLiveVideoPlayerDelegate {
|
||||
## 9. 最佳实践
|
||||
|
||||
- 推流前先完成采集预览
|
||||
- `SurfaceView / TextureView` backend 建议在开始推流或播放前选定
|
||||
- `RTC/WHIP` 的美颜、滤镜、水印、观测优先使用 `TEXTURE_2D`
|
||||
- `I420 / RGBA` 仅在算法必须访问 CPU 像素时再使用
|
||||
- 完整重写输出的 GPU 处理器设置 `fullRewrite = true`;叠加类处理保留默认值
|
||||
- Token 即将过期前提前刷新
|
||||
- 使用统计回调做质量监控
|
||||
- 拉流失败避免无限重试
|
||||
@@ -521,10 +715,32 @@ SDK 不解析 URL 中的鉴权信息,所有鉴权均通过 `token` 属性完
|
||||
|
||||
- 推流端与播放端 `xorKeyHex` 是否完全一致
|
||||
- key 格式是否为合法 hex(偶数长度,支持 `0x` 前缀)
|
||||
- 当前是否为 RTMP + H264 + AAC
|
||||
- 当前是 `RTMP` 还是 `RTC/WHEP`,两端是否都走了对应的加密流配置
|
||||
- 变更 key 后是否已重启推流 / 重建播放器
|
||||
|
||||
### Q5:如何接入代理/加速服务(如洋葱盾)?
|
||||
### Q5:什么时候选择 `SurfaceView`,什么时候选择 `TextureView`?
|
||||
**A:**
|
||||
|
||||
- 普通原生 Android 页面,优先使用默认 `SurfaceView`,性能最优
|
||||
- 需要与按钮、封面、弹层等普通 View 正常混排时,优先使用 `TextureView`
|
||||
- Flutter 场景通过 `setRenderSurfaceTexture()` 接入,走 `TextureView` 同一套渲染管线
|
||||
- 当前版本建议在开始推流/播放前选定 backend;当前 Demo 在首页设置中统一选择,进入页面后不支持切换
|
||||
|
||||
### Q5.1:`TextureView` 模式下,VOD/RTMP 播放的 `BufferQueueProducer timeout` 日志是什么?
|
||||
**A:**
|
||||
|
||||
SDK 内部使用 GL Bridge 将 MediaCodec 硬解输出通过 OpenGL 中转渲染到 TextureView,大幅减少此类日志。如在极端场景下仍偶现,属于 Android 系统 BufferQueue 机制限制,不影响播放功能。`SurfaceView` 路径不存在此问题。
|
||||
|
||||
### Q5.2:`attach` 和 `set` 两套 API 的区别?
|
||||
**A:**
|
||||
|
||||
| API | 谁创建 View | 谁释放 |
|
||||
|---|---|---|
|
||||
| `attachRenderView()` / `attachPreview()` | SDK 创建 | SDK 在 `release()` 时自动释放 |
|
||||
| `setRenderView()` / `setPreviewView()` | 调用方创建并传入 | 调用方负责释放,SDK 只做绑定/解绑 |
|
||||
| `setRenderSurfaceTexture()` | 调用方传入 SurfaceTexture | 调用方负责 SurfaceTexture 生命周期 |
|
||||
|
||||
### Q6:如何接入代理/加速服务(如洋葱盾)?
|
||||
**A:**
|
||||
SDK 本身不集成任何第三方代理 SDK。业务方需在 SDK 外部完成代理初始化与地址获取,然后通过 `SellyCloudManager.setProxyAddress(proxyUrl)` 注入。SDK 内部会自动通过代理地址解析真实服务器 IP。
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
|
||||
SDK 核心以 `InteractiveRtcEngine` 为中心,通过 `InteractiveRtcEngineEventHandler` 回调通话状态、用户事件、音视频状态及异常。
|
||||
|
||||
当前版本的互动渲染模型已经从“仅 `SurfaceViewRenderer`”扩展为“`RtcRenderTarget` 抽象 + 多种后端实现”:
|
||||
|
||||
- `SurfaceViewRenderer` 旧路径仍可用
|
||||
- `TextureView` 已可用于本地/远端视频渲染
|
||||
- 推荐在 **加入频道前** 选定本地渲染后端
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
@@ -105,16 +111,60 @@ val rtcEngine = InteractiveRtcEngine.create(
|
||||
|
||||
### 4. 设置本地/远端画布
|
||||
|
||||
推荐使用 `InteractiveVideoCanvas(renderTarget, userId)` 新接口。
|
||||
|
||||
#### 4.1 SurfaceViewRenderer 旧路径
|
||||
|
||||
```kotlin
|
||||
val localRenderer = SurfaceViewRenderer(this)
|
||||
rtcEngine.setupLocalVideo(InteractiveVideoCanvas(localRenderer, userId))
|
||||
val localCanvas = InteractiveVideoCanvas(
|
||||
com.sellycloud.sellycloudsdk.render.SurfaceViewRtcTarget(localRenderer),
|
||||
userId
|
||||
)
|
||||
rtcEngine.setupLocalVideo(localCanvas)
|
||||
```
|
||||
|
||||
```kotlin
|
||||
val remoteRenderer = SurfaceViewRenderer(this)
|
||||
rtcEngine.setupRemoteVideo(InteractiveVideoCanvas(remoteRenderer, remoteUserId))
|
||||
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
|
||||
@@ -174,9 +224,9 @@ val options = InteractiveChannelMediaOptions(
|
||||
3. 创建 `InteractiveRtcEngine`
|
||||
4. 设置 `EventHandler`
|
||||
5. 配置 `InteractiveVideoEncoderConfig`
|
||||
6. 设置本地画布 `setupLocalVideo`
|
||||
6. 设置本地画布 `setupLocalVideo`(建议在 `joinChannel` 前完成,并在此阶段确定 backend)
|
||||
7. `joinChannel` 加入频道
|
||||
8. `onUserJoined` 后设置远端画布
|
||||
8. `onUserJoined` 后设置远端画布;也可以提前为某个 `userId` 调用 `setupRemoteVideo`,SDK 会在用户真正上线后自动 attach
|
||||
9. 通话中进行音视频控制
|
||||
10. `leaveChannel` 并释放资源
|
||||
|
||||
@@ -268,21 +318,45 @@ val isSharing = rtcEngine.isScreenSharing()
|
||||
## 视频帧前后处理
|
||||
|
||||
```kotlin
|
||||
rtcEngine.setCaptureVideoFrameInterceptor { frame ->
|
||||
// 在此处理美颜/滤镜,返回新的 frame
|
||||
frame
|
||||
}
|
||||
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
|
||||
rtcEngine.setRenderVideoFrameInterceptor { frame, userId ->
|
||||
// 远端渲染前处理,返回 true 表示继续渲染
|
||||
true
|
||||
}
|
||||
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
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> Demo 中的美颜示例见:
|
||||
> `example/src/main/java/com/demo/SellyCloudSDK/beauty/FuVideoFrameInterceptor.kt`
|
||||
> 推荐优先使用 `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`
|
||||
|
||||
---
|
||||
|
||||
@@ -421,6 +495,7 @@ SDK 初始化与代理:
|
||||
本地与远端控制:
|
||||
|
||||
- `setupLocalVideo(canvas)` / `setupRemoteVideo(canvas)`:设置画布
|
||||
- `InteractiveVideoCanvas(renderTarget, userId, renderMode)`:推荐画布模型
|
||||
- `clearRemoteVideo(userId)`:清理远端画面
|
||||
- `enableLocalVideo(true/false)` / `enableLocalAudio(true/false)`:开关本地音视频
|
||||
- `muteRemoteAudioStream(userId, true/false)` / `muteRemoteVideoStream(userId, true/false)`:按用户静音
|
||||
@@ -429,8 +504,9 @@ SDK 初始化与代理:
|
||||
|
||||
帧处理与屏幕共享:
|
||||
|
||||
- `setCaptureVideoFrameInterceptor(...)`:采集前帧处理
|
||||
- `setRenderVideoFrameInterceptor(...)`:渲染前帧处理
|
||||
- `setCaptureVideoProcessor(...)`:采集前可写处理
|
||||
- `addCaptureVideoFrameObserver(...)`:采集前只读观测
|
||||
- `addRenderVideoFrameObserver(...)`:远端渲染前只读观测
|
||||
- `startScreenShare(...)` / `stopScreenShare()` / `isScreenSharing()`:屏幕共享
|
||||
|
||||
消息与 Token:
|
||||
@@ -447,6 +523,20 @@ SDK 初始化与代理:
|
||||
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 是否为空或已过期
|
||||
@@ -457,5 +547,11 @@ SDK 初始化与代理:
|
||||
1. 是否已获取 `MediaProjection` 授权
|
||||
2. Android 14+ 是否启动前台服务
|
||||
|
||||
### Q:互动通话支持 XOR 吗?
|
||||
当前高层互动 API 还没有暴露 `xorKeyHex` 一类的配置入口。
|
||||
|
||||
- 目前已支持 XOR 的 WebRTC 路径,是直播 RTC 的 `WHIP / WHEP` 推拉流
|
||||
- 互动通话如需接入 XOR,需要后续在互动链路单独暴露配置并挂载 FrameCrypto
|
||||
|
||||
### Q:如何接入代理/加速服务?
|
||||
SDK 本身不集成任何第三方代理 SDK。业务方需在外部完成代理初始化,获取本地代理地址后,通过 `SellyCloudManager.setProxyAddress()` 注入。详见「代理地址配置」章节。
|
||||
|
||||
Reference in New Issue
Block a user