Skip to content

Integration Model

To run the first end-to-end path, you only need three pieces:

  1. the device side sends audio and video after starting with a license
  2. your business backend issues a short-lived token for a target peer_id
  3. the client side connects with peer_id + token and plays the remote media

Prepare These Three Inputs First

InputPurpose
licenseDevice identity in the format <peer_id>,<device_secret_key>. The device uses it to start and come online.
peer_idThe connection identifier of the device, for example AABBCCDDEEFF. The client and backend both operate around it.
token APIBefore an Android client connects, it requests one short-lived connection credential from your backend.

1. Your Backend Returns a Token

The client first sends an authorization request:

http
GET /connect-token?peer_id=device://AABBCCDDEEFF
Authorization: Bearer <user_access_token>

Your backend checks whether this user is allowed to connect to the target device:

go
if !canConnectDevice(userID, peerID) {
    return http403("forbidden")
}

If the check passes, the backend issues a short-lived token:

go
func issueConnectToken(userID string, peerID string) string {
    now := time.Now().Unix()
    payload := map[string]any{
        "sub": userID,
        "scope": "connect:" + peerID,
        "iss": accessID,
        "iat": now,
        "exp": now + 300,
        "nonce": randomNonce(),
    }

    payloadB64 := base64url(jsonMarshal(payload))
    deviceSig := hmacSHA256(deviceSecretKey, payloadB64)
    appSig := hmacSHA256(secretKey, payloadB64+"."+deviceSig)
    return "v1." + payloadB64 + "." + appSig
}

Then return it through the HTTP response:

json
{
  "peer_id": "device://AABBCCDDEEFF",
  "token": "v1.xxxxxx.yyyyyy",
  "expires_in": 300
}

2. The Device Starts and Sends Media

Initialize the SDK and start the device:

c
TiRtcInit();
TiRtcSetOpt(TIRTC_OPT_SERVICE_ENTRY, service_entry, (uint32_t)strlen(service_entry));

static const TIRTCCALLBACKS callbacks = {
    .on_event = on_event,
    .on_conn_accepted = on_conn_accepted,
};

TiRtcStart(license, &callbacks);

Assemble and send H264 video frames:

c
TIRTCFRAMEINFO vfi;
memset(&vfi, 0, sizeof(vfi));
vfi.stream_id = 0;
vfi.media = TIRTC_VIDEO_H264;
vfi.flags = is_key_frame ? TIRTC_FRAME_FLAG_KEY_FRAME : 0;
vfi.ts = video_timestamp_ms;
vfi.length = video_len;
TiRtcSendMedia(hconn, &vfi, video_data);

Assemble and send G711A audio frames:

c
TIRTCFRAMEINFO afi;
memset(&afi, 0, sizeof(afi));
afi.stream_id = 1;
afi.media = TIRTC_AUDIO_ALAW;
afi.flags = TIRTC_AUDIOSAMPLE_16K16B1C;
afi.ts = audio_timestamp_ms;
afi.length = audio_len;
TiRtcSendMedia(hconn, &afi, audio_data);

3. The Client Connects and Plays

On Android, prepare the playback objects first:

kotlin
val audioOutput = TiRtcAudioOutput()
val videoOutput = TiRtcVideoOutput()
val remoteVideoContainer = findViewById<FrameLayout>(R.id.remote_video_container)

Before connecting, declare the render host and the remote consume routes:

kotlin
val conn = TiRtcConn()

videoOutput.attachView(remoteVideoContainer)
audioOutput.attach(conn, streamId = 1)
videoOutput.attach(conn, streamId = 0)

conn.connect(peerId, token)

These are three separate actions:

  • videoOutput.attachView(...) only binds the local render host
  • audioOutput.attach(...) and videoOutput.attach(...) only declare which remote routes to consume on the connection
  • conn.connect(...) only establishes the transport connection

They are intentionally decoupled. TiRtcConn no longer owns the public audio-video attach or detach APIs.

Next

TiRTC