Files
SellyCloudSDK_Android_demo/docs/SellySDK_直播推拉流接入文档_Android.md
shou 54a78130b1 demo:新增xor播放支持
sdk:player: texture 播放链优化
2026-04-14 00:53:11 +08:00

845 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Selly Live SDK 推拉流接入文档Android
> 统一 SDK 名称:**SellyCloudSDK**
> 本文档适用于 Android 客户端,面向对外集成方与内部使用。
---
## 1. 概述
Selly Live SDK 提供完整的音视频直播能力,支持 **推流(直播发布)****拉流(直播播放)** 两大核心场景,适用于泛直播、互动直播、实时音视频等业务。
### 主要能力
- 支持 **RTMP / RTC** 推流与播放模式
- 支持 **SurfaceView / TextureView** 两套渲染后端
- 直播播放器与点播播放器支持 **SurfaceTexture** 高级渲染接入
- 高性能音视频采集与编码
- 灵活的视频参数配置(分辨率 / 帧率 / 码率)
- 推流状态与统计回调
- 拉流播放状态与错误回调
- 支持视频帧处理(美颜 / 滤镜 / 水印)
- 基于 **Token 的安全鉴权机制**
- 支持 **RTMP Payload XOR 保护(可选)**
- 支持 **RTCWHEP/WHIPWebRTC Frame XOR 加解密(可选)**
- 支持 **外部代理地址注入**(如洋葱盾等第三方安全代理)
---
## 2. 系统要求
- Android 8.0+Demo `minSdk` 为 26
- 需真机运行(音视频采集限制)
- 需要摄像头 / 麦克风权限
---
## 3. SDK 集成
### 3.1 项目结构参考
- `example/`Android Demo 工程
- 推流示例:`example/src/main/java/com/demo/SellyCloudSDK/live/LivePushActivity.kt`
- 拉流示例:`example/src/main/java/com/demo/SellyCloudSDK/live/LivePlayActivity.kt`
- `example/libs/`:本地 AAR 依赖存放目录
### 3.2 Gradle 依赖方式(仅 AAR
```gradle
dependencies {
implementation files("libs/sellycloudsdk-1.0.1.aar")
}
```
> 若接入美颜等能力,请按业务侧 SDK 要求额外引入第三方美颜库Demo 使用 FaceUnity AAR
### 3.3 权限配置AndroidManifest.xml
```xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
```
> 如需截图保存且兼容 Android 9 及以下,可额外申请 `WRITE_EXTERNAL_STORAGE`。
---
## 4. SDK 初始化与代理配置
### 4.1 SDK 初始化
在使用任何推流 / 拉流功能前,必须先初始化 SDK
```kotlin
SellyCloudManager.initialize(
context = applicationContext,
appId = "your-app-id",
config = SellyCloudConfig(
vhost = "your-vhost",
vhostKey = "your-vhost-key",
defaultStreamId = "default-stream",
defaultLiveMode = SellyLiveMode.RTMP
)
)
```
`initialize` 参数说明:
| 参数 | 类型 | 说明 |
| ---- | ---- | ---- |
| `context` | Context | 应用上下文 |
| `appId` | String | 应用 ID权威值会覆盖 config 中的 appId |
| `config` | SellyCloudConfig? | 可选配置,不传则使用默认值 |
`SellyCloudConfig` 字段说明:
| 字段 | 类型 | 说明 |
| ---- | ---- | ---- |
| `vhost` | String | 虚拟主机 |
| `vhostKey` | String | vhost 密钥(用于鉴权签名) |
| `defaultStreamId` | String | 默认流 ID |
| `logEnabled` | Boolean | 是否启用日志,默认 true |
| `defaultLiveMode` | SellyLiveMode | 默认推拉流模式RTMP / RTC |
| `appName` | String | 应用名称,为空时自动使用 appId一般无需设置 |
> `config.appId` 无需设置SDK 内部会用 `initialize(appId=)` 参数覆盖。
### 4.2 代理地址配置(可选)
SDK 支持通过外部代理(如洋葱盾等安全加速服务)进行流媒体连接。代理地址由业务方在 SDK 外部获取,然后通过以下接口注入:
```kotlin
// 设置代理地址
SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")
// 清除代理(恢复直连)
SellyCloudManager.setProxyAddress(null)
// 查询当前代理地址
val proxy = SellyCloudManager.getProxyAddress() // null 表示未设置
```
**格式要求:**
- 必须以 `http://``https://` 开头
-`null` 或空字符串表示清除代理
- 格式不合法时抛出 `IllegalArgumentException`
**生效范围:**
- 设置后对 RTMP 推拉流、RTCWHEP/WHIP播放推流、Signaling 信令连接均生效
- SDK 内部通过代理地址解析真实服务器 IP对上层透明
**时机要求:**
- 必须在推流 / 拉流 **开始之前** 设置
- 推流 / 拉流过程中修改代理地址,需停止后重新开始才能生效
> Demo 中使用 `KiwiHelper` 封装了洋葱盾 SDK 的初始化与代理地址获取流程,通过 `SellyCloudManager.setProxyAddress()` 将结果传给 SDK。详见 `example/src/main/java/com/demo/SellyCloudSDK/KiwiHelper.kt`。
---
## 5. Token 鉴权机制(重点)
### 5.1 Token 注入方式
| 场景 | 设置位置 |
| ---- | ---- |
| 推流 | `SellyLiveVideoPusher.token` |
| 拉流 | `SellyLiveVideoPlayer.token` |
说明:
- Token **不拼接到 URL**
- Token **不绑定 streamId**
- SDK 内部在建立连接时自动携带当前 Token
- 直接使用 RTMP 地址推/拉流不需要 Token可不设置
### 5.2 Token 设置时机(强约束)
#### 推流
必须在以下接口调用 **之前** 设置:
- `startLiveWithStreamId(...)`
#### 拉流
必须在以下接口调用 **之前** 设置:
- `prepareToPlay()`
- `play()`
> 在连接建立后修改 Token不会影响当前连接。
### 5.3 Token 刷新机制说明
- SDK **不提供自动刷新**
- 业务层可在任意时刻 **重新设置 token 属性**
推荐流程:
1. 业务侧向服务端获取新 Token
2. 调用 `pusher.token = newToken` / `player.token = newToken`
3. 停止并重新开始推流 / 拉流流程
### 5.4 RTMP / WebRTC XOR 保护(可选)
用途:
- 提高流地址泄露后被直接播放、转码或抓流的门槛
生效范围与约束:
- **RTMP** 推拉流:支持 payload XOR当前仅支持 **H264 + AAC**
- **RTCWHEP/WHIP** 推拉流:支持 WebRTC frame XOR 加解密
- 当前这里的 WebRTC 指直播 RTC 推拉流,不包含互动通话高层 API
- RTMP 只处理 payload配置帧SPS/PPS、AAC Sequence Header保持不变
- 推流端与播放端必须使用**同一个 key**
Key 格式:
- `hex` 字符串,建议 16 或 32 字节(即 32/64 个 hex 字符)
- 支持 `0x` 前缀
- 长度必须为偶数
- 非法 key 会直接抛出 `IllegalArgumentException`,不会静默降级
时机要求:
- 推流:请在 `startLiveWithStreamId(...)` / `startLiveWithUrl(...)` 之前调用 `setXorKey(...)`
- 拉流:请在 `initWithStreamId(...)` / `initWithUrl(...)` 创建播放器时传入 `xorKeyHex`
- 运行中修改 key 不会影响当前连接,需重启推流或重建播放器实例
---
## 6. 推流接入详解
### 6.1 创建推流实例
```kotlin
val pusher = SellyLiveVideoPusher.initWithLiveMode(
context = this,
liveMode = SellyLiveMode.RTMP
)
pusher.delegate = object : SellyLiveVideoPusherDelegate {
override fun liveStatusDidChanged(status: SellyLiveStatus) {}
override fun onStatisticsUpdate(stats: SellyLivePusherStats) {}
override fun onError(error: SellyLiveError) {}
}
```
### 6.2 视频参数配置与预览
```kotlin
val config = SellyLiveVideoConfiguration.defaultConfiguration().apply {
videoSize = SellyLiveVideoResolution.RES_1280x720
videoFrameRate = 25
videoBitRate = 1000 * 1000
videoMinBitRate = 500 * 1000
outputImageOrientation = SellyLiveOrientation.PORTRAIT
}
pusher.attachPreview(previewContainer, useTextureView = false)
pusher.startRunning(
cameraPosition = SellyLiveCameraPosition.FRONT,
videoConfig = config,
audioConfig = null
)
```
### 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
```
#### 推流 XORRTMP / RTC-WHIP可选
```kotlin
val xorKeyHex = "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6"
// 建议在 startLiveWith... 之前设置
pusher.setXorKey(xorKeyHex)
```
> `setXorKey(...)` 同时作用于 RTMP 推流与 RTC/WHIP 推流。若在推流中修改 key需停止并重新开始推流后才会使用新 key。
### 6.4 开始/停止推流
```kotlin
pusher.startLiveWithStreamId(streamId)
```
```kotlin
pusher.stopLive()
```
#### 直接使用 RTMP 地址推流Demo 未覆盖)
```kotlin
val rtmpUrl = "rtmp://your.host/live/streamKey"
pusher.startLiveWithUrl(rtmpUrl)
```
> 直接使用 RTMP 地址推流不需要 Token可不设置。
#### 停止推流(带回调)
```kotlin
pusher.stopLive { error ->
if (error != null) {
// 处理停止失败
}
}
```
### 6.5 常用控制接口
- `setMuted(true/false)`:静音
- `switchCameraPosition(...)`:切换摄像头
- `switchCamera()`:前后摄像头切换(自动切换)
- `startCamera()` / `stopCamera()`:控制摄像头
- `startMicrophone()` / `stopMicrophone()`:控制麦克风
- `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 中对齐生命周期:
- `onResume()``pusher.onResume()`
- `onPause()``pusher.onPause()`
- `onDestroy()``pusher.release()`
### 6.7 状态与统计回调
**状态枚举:**
- `Idle`
- `Connecting`
- `Publishing`
- `Reconnecting`
- `Stopped`
- `Failed`
**统计字段:**
- fps
- videoBitrateKbps / audioBitrateKbps
- rttMs
- cpu 使用率Demo 通过 `CpuUsage` 读取)
- auto framing state通过 `onAutoFramingStateChanged` / `getAutoFramingState()` 获取)
### 6.8 推流 API 速览(含 Demo 未覆盖)
初始化与预览:
- `initWithLiveMode(context, liveMode)`:创建推流实例
- `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)`:设置推流 XOR keyRTMP payload / RTC-WHIP frame可选
- `setAutoFramingEnabled(enabled)` / `getAutoFramingCapability()` / `getAutoFramingState()`:自动取景控制与状态查询
- `startLiveWithStreamId(streamId)`:使用 streamId 推流
- `startLiveWithUrl(url)`:使用完整 URL 推流
- `stopLive()` / `stopLive(callback)`:停止推流
设备与音视频控制:
- `switchCameraPosition(position)` / `switchCamera()`:切换摄像头
- `startCamera()` / `stopCamera()`:启动 / 停止摄像头
- `startMicrophone()` / `stopMicrophone()`:启动 / 停止麦克风
- `setMuted(true/false)`:静音推流音频
- `setCameraEnabled(true/false)`:开启 / 关闭摄像头
美颜与画面:
- `setBeautyEngine(engine)`:设置美颜引擎
- `setBeautyEnabled(true/false)`:启用 / 关闭美颜
- `setBeautyLevel(level)`:设置美颜强度
- `onAutoFramingStateChanged(state)`:自动取景状态回调
- `setStreamOrientation(orientation)`:设置推流方向
- `changeResolution(width, height)`:动态调整分辨率
- `setBitmapAsVideoSource(bitmap)` / `restoreCameraVideoSource()`:背景图推流
生命周期:
- `onPause()` / `onResume()` / `release()`:与 Activity 生命周期对齐
---
## 7. 拉流接入详解
### 7.1 创建播放器
```kotlin
val player = SellyLiveVideoPlayer.initWithStreamId(
context = this,
streamId = streamId,
liveMode = SellyLiveMode.RTC,
xorKeyHex = "" // 加密流传入同一 key明文流可留空
)
// 或直接使用完整 URL
// val player = SellyLiveVideoPlayer.initWithUrl(this, playUrl, xorKeyHex = "A1B2...")
```
若需要指定 `vhost` / `appName`
```kotlin
val player = SellyLiveVideoPlayer.initWithStreamId(
context = this,
streamId = streamId,
liveMode = SellyLiveMode.RTMP,
vhost = "your-vhost",
appName = "live",
xorKeyHex = "A1B2C3D4E5F6A7B8C9D0E1F2A3B4C5D6"
)
```
> 使用 RTMP 或 RTC/WHEP 加密流时,请在创建播放器时传入 `xorKeyHex`;后续如需换 key请重建播放器实例。
### 7.2 设置拉流 Token使用 streamId 时)
```kotlin
player.token = playToken
```
> 直接使用 RTMP 地址拉流不需要 Token可不设置。
### 7.3 播放流程
```kotlin
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`
- `RTMP/VOD``TextureView / SurfaceTexture` 默认走 **direct output**,优先保证首帧和低延迟
- 当前版本建议在 **开始播放前** 选定渲染后端;运行中如需变更目标,请走 `clearRenderTarget()` + `setRenderView(...)` / `setRenderSurfaceTexture(...)` 的显式重绑流程
- Flutter 场景优先使用 `setRenderSurfaceTexture(...)`,配合 Flutter `Texture` widget 使用;如 UI 层级正确性优先,不建议继续依赖 `Hybrid Composition + SurfaceView`
控制接口:
- `pause()`
- `stop()`
- `play()`
- `setMuted(true/false)`
- `release()`
补充接口Demo 未覆盖):
- `setRenderView(view)`:手动指定渲染 View
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:直接绑定 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
- `clearRenderTarget()`:解绑当前渲染面,播放会话可继续存活
- `seekBy(deltaMs)`:播放进度跳转(仅在流支持快进/回放时有效)
### 7.3.2 Flutter / SurfaceTexture 接入建议
如果业务侧需要把视频放到 Flutter UI 层下面,并正常叠加按钮、封面、弹层、动画,推荐使用:
- Flutter 侧创建 `TextureRegistry.SurfaceTextureEntry`
- Android 插件层取出 `SurfaceTexture`
- 调用 `setRenderSurfaceTexture(surfaceTexture, width, height)`
- Flutter 页面使用 `Texture(textureId)` 显示视频
示意:
```kotlin
player.setRenderSurfaceTexture(surfaceTexture, width, height)
player.prepareToPlay()
player.play()
```
说明:
- `SurfaceTexture` 生命周期由调用方负责
- 销毁前建议先调用 `clearRenderTarget()` 或直接 `release()`
- 如果页面重建、Texture 重新申请或 Flutter 侧切换 textureId需要重新绑定新的 `SurfaceTexture`
### 7.4 播放回调
```kotlin
player.delegate = object : SellyLiveVideoPlayerDelegate {
override fun playbackStateChanged(state: SellyPlayerState) {}
override fun onFirstVideoFrameRendered() {}
override fun onFirstAudioFrameRendered() {}
override fun onLatencyChasingUpdate(update: SellyLatencyChasingUpdate) {}
override fun onLatencyChasingReloadRequired(latencyMs: Long) {}
override fun onError(error: SellyLiveError) {}
}
```
状态枚举:
- `Idle`
- `Connecting`
- `Playing`
- `Paused`
- `Stopped`
- `Reconnecting`
- `Failed`
首帧语义说明:
- 默认 `DIRECT` 模式下,`onFirstVideoFrameRendered()` 对应 decoder 首帧可用时机
-`TextureView / SurfaceTexture` 且启用了 playback processing 的场景SDK 会等待目标渲染面确认首帧已真正呈现后,再触发 `onFirstVideoFrameRendered()`
- `onFirstAudioFrameRendered()` 仍表示音频首帧可播放时机;在 texture-backed processing 场景中,音频与视频首帧不一定完全同一时刻
### 7.4.1 播放侧帧回调与二次处理
播放器支持一组独立于采集/推流链路的播放侧高级能力:
- `setPlaybackFrameObserver(observer)`:播放侧只读帧回调
- `setPlaybackVideoProcessor(processor)`:播放侧可写纹理处理
当前能力边界:
- 当前仅支持 **texture-backed** 播放目标:`TextureView` / `SurfaceTexture`
- 当前仅支持 `preferredFormat = TEXTURE_2D`
- 当前仅支持 `stage = RENDER_PRE_DISPLAY`
- 当前默认渲染模式为 `DIRECT`
- 只有设置了有效的 observer / processor才会切到 `PROCESSING`
- 如果当前 render target 已经绑定,新增或移除 observer / processor 后,需要 **重绑一次 texture render target** 才会生效
- `RTC/WHEP` 播放当前不支持这套 playback processing当前主要用于 `RTMP/VOD` 播放链
只读 observer 示例:
```kotlin
player.setPlaybackFrameObserver(object : PlaybackFrameObserver {
override val config = PlaybackFrameObserverConfig(
preferredFormat = VideoProcessFormat.TEXTURE_2D,
stage = VideoStage.RENDER_PRE_DISPLAY
)
override fun onTextureFrame(frame: VideoTextureFrame) {
// 读取播放侧纹理帧信息
}
})
```
可写 processor 示例:
```kotlin
player.setPlaybackVideoProcessor(object : PlaybackVideoProcessor {
override val config = PlaybackVideoProcessorConfig(
preferredFormat = VideoProcessFormat.TEXTURE_2D,
mode = VideoProcessMode.READ_WRITE,
stage = VideoStage.RENDER_PRE_DISPLAY
)
override fun processTexture(input: VideoTextureFrame, outputTextureId: Int) {
// 将后处理结果写入 outputTextureId
}
})
```
### 7.5 播放 API 速览(含 Demo 未覆盖)
创建与渲染:
- `initWithStreamId(context, streamId, liveMode, vhost, appName, xorKeyHex)`:使用 streamId 创建播放器
- `initWithUrl(context, url, xorKeyHex)`:使用完整 URL 创建播放器
- `attachRenderView(container)`:创建默认 `SurfaceView` 渲染 View
- `attachRenderView(container, backend)`:创建指定 backend 的渲染 View
- `setRenderView(view)`:手动设置渲染 View
- `setRenderSurfaceTexture(surfaceTexture, width, height)`:绑定 `SurfaceTexture`(调用方负责 SurfaceTexture 生命周期)
- `clearRenderTarget()`:解绑当前渲染面
- `getRenderView()`:获取当前渲染 View
- `setPlaybackFrameObserver(observer)`:设置播放侧只读 observertexture 路径)
- `setPlaybackVideoProcessor(processor)`:设置播放侧 processortexture 路径)
播放控制:
- `prepareToPlay()` / `play()` / `pause()` / `stop()`:播放流程控制
- `seekBy(deltaMs)`:播放进度跳转(流支持回放时有效)
- `isPlaying()`:查询播放状态
- `setMuted(true/false)`:静音播放
统计与释放:
- `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()`:解绑当前渲染面但不一定立即销毁播放实例
- `setPlaybackFrameObserver(observer)` / `setPlaybackVideoProcessor(processor)`:点播同样支持 texture-backed playback processing
补充说明:
- 点播在重绑 `TextureView / SurfaceTexture` 后,会自动复用最近一次视频宽高信息,保持正确显示比例
- 如在已有 texture 目标上新增或移除 observer / processor也需要重绑一次 texture render target 才会应用新的渲染模式
因此 Demo 中点播页的 `SurfaceView / TextureView` 选择,也与直播播放页保持一致,均在首页设置中统一生效。
---
## 8. 错误处理与重试建议
### Token 错误
1. 停止当前推 / 拉
2. 获取新 Token
3. 重新设置 Token
4. 重新开始推流 / 拉流流程
### 网络错误
- 监听 `onStatisticsUpdate` 或播放器状态
- 弱网时适当降低分辨率 / 码率
- 必要时重启连接
---
## 9. 最佳实践
- 推流前先完成采集预览
- `SurfaceView / TextureView` backend 建议在开始推流或播放前选定
- Flutter 场景优先使用 `setRenderSurfaceTexture(...)`,不要把 `Hybrid Composition + SurfaceView` 当成默认方案
- 普通播放默认保持 `DIRECT`;只有确实需要播放侧帧观察或纹理后处理时,再启用 playback processing
- playback processing 当前仅建议用于 `TextureView / SurfaceTexture + TEXTURE_2D + RENDER_PRE_DISPLAY`
- 变更 texture 路径的 observer / processor 后,显式重绑一次 render target
- `RTC/WHIP` 的美颜、滤镜、水印、观测优先使用 `TEXTURE_2D`
- `I420 / RGBA` 仅在算法必须访问 CPU 像素时再使用
- 完整重写输出的 GPU 处理器设置 `fullRewrite = true`;叠加类处理保留默认值
- Token 即将过期前提前刷新
- 使用统计回调做质量监控
- 拉流失败避免无限重试
- 使用代理时,确保在推拉流开始前代理地址已设置完毕
---
## 10. 常见问题FAQ
### Q1Token 可以拼接到 URL 吗?
**A** 不可以。
SDK 不解析 URL 中的鉴权信息,所有鉴权均通过 `token` 属性完成。
### Q2运行中修改 Token 是否生效?
**A**
运行中修改 Token **不会影响当前已建立的连接**
**下次重连或重新启动推流 / 拉流时会使用新的 Token**
### Q3播放器出现黑屏怎么办
**A** 可按以下步骤排查:
- 检查播放地址是否正确
- 确认当前网络连接正常
- 查看播放器回调中的错误信息
- 确认视频流格式是否被 SDK 支持
### Q4加密流播放花屏/噪音怎么办?
**A** 重点检查以下项:
- 推流端与播放端 `xorKeyHex` 是否完全一致
- key 格式是否为合法 hex偶数长度支持 `0x` 前缀)
- 当前是 `RTMP` 还是 `RTC/WHEP`,两端是否都走了对应的加密流配置
- 变更 key 后是否已重启推流 / 重建播放器
### Q5什么时候选择 `SurfaceView`,什么时候选择 `TextureView`
**A**
- 普通原生 Android 页面,优先使用默认 `SurfaceView`,性能最优
- 需要与按钮、封面、弹层等普通 View 正常混排时,优先使用 `TextureView`
- Flutter 场景通过 `setRenderSurfaceTexture()` 接入,配合 Flutter `Texture` widget 使用
- 当前版本建议在开始推流/播放前选定 backend当前 Demo 在首页设置中统一选择,进入页面后不支持切换
### Q5.1`TextureView` 模式下VOD/RTMP 播放的 `BufferQueueProducer timeout` 日志是什么?
**A**
当前 `RTMP/VOD``TextureView / SurfaceTexture` 默认走 direct output以缩短首帧和减少黑屏。极端机型或系统版本下仍可能偶现 `BufferQueueProducer timeout` / `BufferQueue has been abandoned` 之类系统日志;如果不伴随黑屏、花屏、卡死,通常可视为 Android BufferQueue 机制噪声。开启 playback processing 时texture 路径内部会启用额外的处理中转链,日志形态也可能与 direct 模式不同。
### 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。
示例流程:
1. 在 Application 或 Activity 中初始化代理 SDK
2. 获取本地代理地址(如 `http://127.0.0.1:12345`
3. 调用 `SellyCloudManager.setProxyAddress("http://127.0.0.1:12345")`
4. 正常进行推流 / 拉流
> Demo 中的 `KiwiHelper` 展示了洋葱盾的完整接入流程,可作为参考。