Skip to content

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 及以上版本
compileSdk35

工具链版本尽量与官方示例保持一致。

获取 SDK

SDK 版本号从Android SDK 发布说明获取,并替换下面示例中的 <latest-version>

kotlin
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

xml
<uses-permission android:name="android.permission.INTERNET" />

初始化 SDK

在调用其他 SDK API 之前,先初始化 TiRTC 运行时。

kotlin
class DemoApp : Application() {
  override fun onCreate() {
    super.onCreate()

    TiRtc.initialize(
        TiRtc.Config.Builder(this)
          .setEnableConsoleLog(BuildConfig.DEBUG)
          .build(),
      )
  }
}

获取连接 Token

连接 Token 是一个短时有效的鉴权凭证,用于在建立连接时校验客户端是否有权限接入 TiRTC。

建立连接前,先准备 peerId,再获取这次连接需要的 token

获取 token 的方式有两种:

  1. 开发联调阶段,通过 TiRTC DevTools CLI 获取
  2. 正式接入阶段,通过自己的业务服务端按规则生成,具体可参考获取连接 Token

TiRTC DevTools CLI 只适合开发和联调阶段使用,不能作为正式接入方案。

正式接入时,应当由业务服务器负责签名并颁发 token,客户端只负责请求结果。

客户端工程内不得保存 accessIdsecretKey 或其他签名凭证。这些信息属于服务端敏感信息,泄露后会带来重大安全风险。

建立连接

使用 TiRtcConn 建立一条到远端的连接。后续的命令、流消息和音视频绑定都围绕这条连接进行。

下面的示例都基于同一个 PlayerActivity

kotlin
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(...) 只是示意,实际实现由你的业务接口决定
kotlin
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

准备视频容器

xml
<FrameLayout
  android:id="@+id/remote_video_container"
  android:layout_width="match_parent"
  android:layout_height="240dp" />

在 Activity 里补充媒体相关成员

kotlin
private val audioOutput = TiRtcAudioOutput()
private val videoOutput = TiRtcVideoOutput()

private lateinit var remoteVideoContainer: FrameLayout

private val audioStreamId = 1
private val videoStreamId = 2
private val messageStreamId = 3
  • audioOutput 用来播放远端音频
  • videoOutput 用来渲染远端视频
  • messageStreamId 用来发送业务自定义流消息,不能复用音频或视频的 streamId

绑定输出

kotlin
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  setContentView(R.layout.activity_player)

  // ...

  bindRemoteMedia()
}
kotlin
private fun bindRemoteMedia() {
  videoOutput.attach(remoteVideoContainer)
  conn.attachAudioOutput(audioStreamId, audioOutput)
  conn.attachVideoOutput(videoStreamId, videoOutput)
}

当远端音视频到达并且 streamId 匹配时,音频会开始播放,视频会渲染到这个容器中。

Kotlin 示例直接使用 TiRtcConn 的扩展方法;如果是 Java,再看 API Reference 里的 binding 写法。

audioStreamIdvideoStreamId

音频和视频各自使用自己的 streamId

  • audioStreamId 用来绑定远端音频流
  • videoStreamId 用来绑定远端视频流
  • 当前 Android SDK 的 streamId 应按 0..15 使用
  • 音频、视频、流消息共用同一组 streamId 命名空间,不能重号
  • 这两个值通常由远端能力约定或业务配置提供

收发流消息

流消息适合承载轻量业务消息,并且总是绑定到具体的 streamId

流消息绑定到具体 streamId

接收流消息

kotlin
override fun onStreamMessageReceived(
  streamId: Int,
  message: RtcStreamMessage,
) {
  val text = message.data.decodeToString()
  println(
    "stream message received streamId=$streamId ts=${message.timestampMs} text=$text",
  )
}

发送命令

命令用于控制远端行为,或者向远端发起一个请求并等待结果。

先定义你和远端约定好的命令 ID:

kotlin
private val toggleLedCommandId = 0x1001
private val getTimeCommandId = 0x1002

这些 commandId 需要和远端协议约定,并使用 0x1000..0x7FFF 范围。0x1000 以下保留给 SDK 内部使用。

单向命令

kotlin
private fun sendToggleCommand() {
  conn.sendCommand(toggleLedCommandId, "toggle_led".encodeToByteArray())
}

请求-响应命令

kotlin
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")
      }
    },
  )
}

回复远端请求

kotlin
private fun replyRemoteCommand(
  remoteRequestId: Long,
  command: RtcConnCommand,
) {
  conn.replyRemoteCommand(
    remoteRequestId = remoteRequestId,
    commandId = command.commandId,
    data = "ok".encodeToByteArray(),
  )
}

释放资源

页面退出或不再使用这些对象时,按与创建相反的方向释放资源。

kotlin
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(...) 上传日志。

kotlin
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 版本
  • 连接目标
  • 是连不上、没声音、没画面,还是命令异常

继续阅读

Ti RTC 开发文档