# 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()` 注入。详见「代理地址配置」章节。