Android 接入指南
这份文档说明如何在 Android 工程中接入 TiRTC SDK,并完成连接、远端音视频播放、流消息收发与问题排查。
官方 Demo
如果你想先参考一个可独立构建的示例工程,优先使用官方开源 Android Demo 仓库:tangeai/tirtc-example-android。
前置条件
在开始接入前,先确认下面这些条件:
| 项目 | 要求 |
|---|---|
| Android 版本 | Android 5.0(API Level 21)及以上 |
| 支持架构 | 仅支持 arm64-v8a |
| Android Studio | 无强制要求,推荐使用最新稳定版 |
| Gradle | 无强制要求,推荐使用 8.0 及以上版本 |
| Java | 无强制要求,推荐使用 17 及以上版本,并与 Gradle 官方要求保持一致 |
| Android Gradle Plugin | 无强制要求,推荐使用 8.1.1 及以上版本 |
compileSdk | 35 |
工具链版本尽量与官方示例保持一致。
获取 SDK
SDK 版本号从Android SDK 发布说明获取,并替换下面示例中的 <latest-version>。
pluginManagement {
repositories {
maven {
url = uri("http://repo-sdk.tange-ai.com/repository/maven-public/")
isAllowInsecureProtocol = true
credentials {
username = "tange_user"
password = "tange_user"
}
}
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencies {
implementation("com.tange.ai:tirtc-av:<latest-version>")
}声明权限
AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />初始化 SDK
在调用其他 SDK API 之前,先初始化 TiRTC 运行时。
class DemoApp : Application() {
override fun onCreate() {
super.onCreate()
TiRtc.initialize(
TiRtc.Config.Builder(this)
.setEnableConsoleLog(BuildConfig.DEBUG)
.build(),
)
}
}获取连接 Token
连接 Token 是一个短时有效的鉴权凭证,用于在建立连接时校验客户端是否有权限接入 TiRTC。
建立连接前,先准备 peerId,再获取这次连接需要的 token。
获取 token 的方式有两种:
- 开发联调阶段,通过 TiRTC DevTools CLI 获取
- 正式接入阶段,通过自己的业务服务端按规则生成,具体可参考获取连接 Token
TiRTC DevTools CLI 只适合开发和联调阶段使用,不能作为正式接入方案。
正式接入时,应当由业务服务器负责签名并颁发 token,客户端只负责请求结果。
客户端工程内不得保存 accessId、secretKey 或其他签名凭证。这些信息属于服务端敏感信息,泄露后会带来重大安全风险。
建立连接
使用 TiRtcConn 建立一条到远端的连接。后续的命令、流消息和音视频绑定都围绕这条连接进行。
下面的示例都基于同一个 PlayerActivity:
class PlayerActivity : AppCompatActivity() {
private val conn = TiRtcConn()
private val peerId = "your-peer-id"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
conn.setObserver(createConnObserver())
}
override fun onResume() {
super.onResume()
lifecycleScope.launch {
val token = yourBackend.fetchTiRtcToken(peerId = peerId)
conn.connect(peerId, token)
}
}
override fun onPause() {
super.onPause()
conn.disconnect()
}
override fun onDestroy() {
super.onDestroy()
releaseRtc()
}
}peerId可以来自你的设备列表、固定配置或业务下发token应该在连接前实时获取,而不是写死在客户端里- 上面
yourBackend.fetchTiRtcToken(...)只是示意,实际实现由你的业务接口决定
private fun createConnObserver() =
object : TiRtcConn.Observer {
override fun onStateChanged(state: TiRtcConn.State) {
println("conn state=$state")
}
override fun onDisconnected(errorCode: Int) {
println("conn disconnected error=$errorCode")
}
override fun onRemoteCommandRequest(
remoteRequestId: Long,
command: RtcConnCommand,
) {
replyRemoteCommand(remoteRequestId, command)
}
override fun onStreamMessageReceived(
streamId: Int,
message: RtcStreamMessage,
) {
val text = message.data.decodeToString()
println(
"stream message received streamId=$streamId ts=${message.timestampMs} text=$text",
)
}
override fun onError(code: Int, message: String) {
println("conn error code=$code message=$message")
}
}conn表示这次连接onResume()建立连接onPause()断开连接onDestroy()调用releaseRtc()释放对象资源releaseRtc()的定义见后面的“释放资源”章节- 音视频相关对象放在下一节补上
播放远端音视频
开始播放远端音视频前,你需要准备一个视频容器、一个音频输出、一个视频输出,以及各自对应的 streamId。
准备视频容器
<FrameLayout
android:id="@+id/remote_video_container"
android:layout_width="match_parent"
android:layout_height="240dp" />在 Activity 里补充媒体相关成员
private val audioOutput = TiRtcAudioOutput()
private val videoOutput = TiRtcVideoOutput()
private lateinit var remoteVideoContainer: FrameLayout
private val audioStreamId = 1
private val videoStreamId = 2
private val messageStreamId = 3audioOutput用来播放远端音频videoOutput用来渲染远端视频messageStreamId用来发送业务自定义流消息,不能复用音频或视频的streamId
绑定输出
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
// ...
bindRemoteMedia()
}private fun bindRemoteMedia() {
videoOutput.attach(remoteVideoContainer)
conn.attachAudioOutput(audioStreamId, audioOutput)
conn.attachVideoOutput(videoStreamId, videoOutput)
}当远端音视频到达并且 streamId 匹配时,音频会开始播放,视频会渲染到这个容器中。
Kotlin 示例直接使用 TiRtcConn 的扩展方法;如果是 Java,再看 API Reference 里的 binding 写法。
audioStreamId 和 videoStreamId
音频和视频各自使用自己的 streamId。
audioStreamId用来绑定远端音频流videoStreamId用来绑定远端视频流- 当前 Android SDK 的
streamId应按0..15使用 - 音频、视频、流消息共用同一组
streamId命名空间,不能重号 - 这两个值通常由远端能力约定或业务配置提供
收发流消息
流消息适合承载轻量业务消息,并且总是绑定到具体的 streamId。
流消息绑定到具体 streamId。
接收流消息
override fun onStreamMessageReceived(
streamId: Int,
message: RtcStreamMessage,
) {
val text = message.data.decodeToString()
println(
"stream message received streamId=$streamId ts=${message.timestampMs} text=$text",
)
}发送命令
命令用于控制远端行为,或者向远端发起一个请求并等待结果。
先定义你和远端约定好的命令 ID:
private val toggleLedCommandId = 0x1001
private val getTimeCommandId = 0x1002这些 commandId 需要和远端协议约定,并使用 0x1000..0x7FFF 范围。0x1000 以下保留给 SDK 内部使用。
单向命令
private fun sendToggleCommand() {
conn.sendCommand(toggleLedCommandId, "toggle_led".encodeToByteArray())
}请求-响应命令
private fun requestRemoteTime() {
conn.requestCommand(
commandId = getTimeCommandId,
data = "get_time".encodeToByteArray(),
timeoutMs = 3_000,
callback = object : TiRtcConn.CommandCallback {
override fun onSuccess(response: RtcConnCommandResponse) {
println(
"request success commandId=${response.commandId} payload=${response.data.decodeToString()}",
)
}
override fun onFailure(code: Int, message: String) {
println("request failed code=$code message=$message")
}
},
)
}回复远端请求
private fun replyRemoteCommand(
remoteRequestId: Long,
command: RtcConnCommand,
) {
conn.replyRemoteCommand(
remoteRequestId = remoteRequestId,
commandId = command.commandId,
data = "ok".encodeToByteArray(),
)
}释放资源
页面退出或不再使用这些对象时,按与创建相反的方向释放资源。
private fun releaseRtc() {
conn.detachAudioOutput(audioStreamId)
conn.detachVideoOutput(videoStreamId)
videoOutput.detach()
audioOutput.destroy()
videoOutput.destroy()
conn.destroy()
}遇到问题先看这里
连不上
如果连接没有建立起来,先按下面的顺序检查:
peerId是否正确token是否过期或不匹配- 应用是否已经声明
INTERNET权限 - 是否已经先完成
TiRtc.initialize(...)
再检查回调里是否已经给出直接线索:
onError(...)onDisconnected(...)
上传日志
需要协助排查问题时,调用 TiRtcDebugging.uploadLogs(...) 上传日志。
val code =
TiRtcDebugging.uploadLogs(
object : TiRtcDebugging.UploadLogsCallback {
override fun onSuccess(logId: String) {
println("upload logs success logId=$logId")
}
override fun onFailure(code: Int, message: String) {
println("upload logs failed code=$code message=$message")
}
},
)
println("uploadLogs submit code=$code")反馈问题时,建议同时附带下面这些信息:
- 问题发生时间
- 设备型号和 Android 版本
- 连接目标
- 是连不上、没声音、没画面,还是命令异常