Skip to content

TiRTC Android Integration Guide

This guide explains how to integrate the TiRTC SDK into an Android project and complete connection setup, remote audio-video playback, stream messaging, and troubleshooting.

Official Demo

If you want a standalone sample project you can build directly, start with the official open-source Android demo repository: tangeai/tirtc-example-android.

Prerequisites

Confirm the following before integration starts:

ItemRequirement
Android versionAndroid 5.0 (API Level 21) or higher
Supported ABIarm64-v8a only
Android StudioNo hard requirement; latest stable version recommended
GradleNo hard requirement; version 8.0 or newer recommended
JavaNo hard requirement; version 17 or newer recommended, aligned with Gradle requirements
Android Gradle PluginNo hard requirement; version 8.1.1 or newer recommended
compileSdk35

Keep your toolchain versions as close as possible to the official samples.

Get the SDK

Get the SDK version from the Android SDK Release Notes, then replace <latest-version> in the example below.

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

Declare Permissions

AndroidManifest.xml:

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

Initialize the SDK

Initialize the TiRTC runtime before calling any other SDK API.

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

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

Get a Connection Token

A connection token is a short-lived credential used to verify that the client is allowed to connect to TiRTC.

Before you connect, prepare peerId and then fetch the token required for this connection.

There are two common ways to get a token:

  1. During development and debugging, generate it with TiRTC DevTools CLI
  2. In production, generate it in your own backend according to the documented signing rule. See Generate a Connection Token

TiRTC DevTools CLI is only suitable for development and local debugging. It is not a production delivery strategy.

In production, your business backend should sign and issue the token, and the client should only request the result.

Do not store accessId, secretKey, or any other signing credentials in the client app. These are server-side secrets, and leakage is a serious security risk.

Establish a Connection

Use TiRtcConn to create one connection to the remote peer. Commands, stream messages, and audio-video bindings all operate around this connection.

The following examples assume a single 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 can come from your device list, fixed config, or business-provided data
  • token should be fetched right before connecting, not hard-coded in the client
  • yourBackend.fetchTiRtcToken(...) is only a placeholder. The real implementation depends on your backend API
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 represents this connection
  • onResume() establishes the connection
  • onPause() disconnects it
  • onDestroy() calls releaseRtc() to release object resources
  • releaseRtc() is defined later in the Release Resources section
  • Audio and video objects are added in the next section

Play Remote Audio and Video

Before you can play remote media, prepare a video container, an audio output, a video output, and the corresponding streamId values.

Prepare the Video Container

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

Add the Media Members to the 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 plays remote audio
  • videoOutput renders remote video
  • messageStreamId is used for custom business stream messages and must not reuse the audio or video streamId

Bind the Outputs

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)
}

When the remote media arrives and the streamId matches, audio starts playing and video is rendered into the container.

Kotlin examples use the TiRtcConn extension methods directly. If you use Java, see the API Reference for the binding-based API shape.

audioStreamId and videoStreamId

Audio and video use separate streamId values.

  • audioStreamId binds the remote audio stream
  • videoStreamId binds the remote video stream
  • These values usually come from the remote capability contract or your business configuration

Send and Receive Stream Messages

Stream messages are suitable for lightweight business data and are always attached to a specific streamId.

Receive Stream Messages

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

Send a Stream Message

kotlin
private fun sendStreamPing() {
  conn.sendStreamMessage(
    streamId = messageStreamId,
    timestampMs = System.currentTimeMillis(),
    data = "hello".encodeToByteArray(),
  )
}

Send Commands

Commands are used to control remote behavior or to send a request and wait for a response.

First define the command IDs agreed between your client and the remote side:

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

These commandId values must be agreed in your peer protocol and stay within 0x1000..0x7FFF. Values below 0x1000 are reserved for internal runtime commands.

One-Way Command

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

Request-Response Command

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

Reply to a Remote Request

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

Release Resources

When the page exits or these objects are no longer needed, release them in the reverse direction of creation.

kotlin
private fun releaseRtc() {
  conn.detachAudioOutput(audioStreamId)
  conn.detachVideoOutput(videoStreamId)

  videoOutput.detach()

  audioOutput.destroy()
  videoOutput.destroy()
  conn.destroy()
}

Troubleshooting Starts Here

Cannot Connect

If the connection cannot be established, check these items first:

  • Is peerId correct
  • Has the token expired or does it not match the target
  • Has the app declared the INTERNET permission
  • Did you call TiRtc.initialize(...) before using the SDK

Then inspect the callbacks for direct clues:

  • onError(...)
  • onDisconnected(...)

Upload Logs

When you need help diagnosing an issue, call TiRtcDebugging.uploadLogs(...) to upload logs.

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")

When reporting an issue, it is best to include:

  • The time when the problem occurred
  • Device model and Android version
  • The connection target
  • Whether the issue is connection failure, no audio, no video, or a command problem

Continue Reading

TiRTC