Connection
Connection spans three roles: the device starts and waits for inbound access, your backend issues the connect token, and the client uses that token to connect the target device. This page covers that end-to-end path.
Prepare These Inputs
| Item | Purpose |
|---|---|
device_id | Identifies the target device |
device_secret_key | Device secret used by device startup |
access_id | Application credential identifier |
secret_key | Application secret used by your backend |
remote_id | The target identifier used by the client connect call |
sub | The subject identity for this request, such as your user_id |
In the common case, the client-side remote_id is the target device device_id.
Overall Flow
- The device starts the SDK with
device_idanddevice_secret_key. - Your backend checks whether the current subject is allowed to access the target device, then issues the connect
tokenwith the public custom signing algorithm. - The client uses the returned
tokento connect the targetremote_id.
Step 1: Start the Device and Wait for Connections
The device-side C SDK call order is: TiRtcSetOption(...) -> TiRtcInit() -> TiRtcStart().
- If you use a custom service entry, set
TIRTC_OPT_SERVICE_ENDPOINTfirst. TiRtcStart()can take"<device_id>,<device_secret_key>"directly.- If you already set
device_secret_keythroughTIRTC_OPT_SECRET_KEY,TiRtcStart()can also take onlydevice_id.
#include <stdio.h>
#include "tiRTC.h"
static void on_event(int event, const void *data, int len) {
(void)data;
(void)len;
if (event == TiEVENT_SYS_STARTED) {
puts("device started");
}
}
static TIRTCCALLBACKS cbs = {
.on_event = on_event,
};
int start_device(void) {
int code = TiRtcInit();
if (code != 0) {
return code;
}
return TiRtcStart("your-device-id,your-device-secret-key", &cbs);
}A 0 return from TiRtcStart() only means the initial argument check passed. Startup is complete when TiEVENT_SYS_STARTED arrives. After that, the device is waiting for inbound connections.
Step 2: Issue the Token on Your Backend
Before issuing the token, do your own authorization check: can this subject access this target device? If yes, issue the short-lived connect token.
Required Claims
| Field | Meaning |
|---|---|
sub | Subject identity for this request |
scope | Authorized target scope; a common form is connect:<remote_id> |
iss | Your application access_id |
iat | Issued-at timestamp |
exp | Expiration timestamp |
nonce | A fresh random value for each issuance |
Public Custom Signing Algorithm
The final token string sent to the client has this form:
token = "v1.<payload_b64>.<app_sig>"Compute it like this:
payload_json = {
"sub": <subject>,
"scope": "connect:" + <remote_id>,
"iss": <access_id>,
"iat": <issued_at>,
"exp": <expires_at>,
"nonce": <random_nonce>
}
payload_b64 = base64url(payload_json)
app_sig = HMAC_SHA256(secret_key, payload_b64)
token = "v1." + payload_b64 + "." + app_sigKeep two boundaries clear:
secret_keyanddevice_secret_keystay in controlled backend or device environments. Do not send them to the client.- The client only consumes the final
token. It does not participate in signing.
Go Example
package tirtc
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"time"
)
type ConnectTokenClaims struct {
Subject string `json:"sub"`
Scope string `json:"scope"`
Issuer string `json:"iss"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
Nonce string `json:"nonce"`
}
func hmacB64(key, content string) string {
h := hmac.New(sha256.New, []byte(key))
_, _ = h.Write([]byte(content))
return base64.RawURLEncoding.EncodeToString(h.Sum(nil))
}
func randomNonce() (string, error) {
buf := make([]byte, 16)
if _, err := rand.Read(buf); err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
func GenerateConnectToken(accessID, secretKey, remoteID, subject string, ttlSeconds int64) (string, error) {
now := time.Now().Unix()
nonce, err := randomNonce()
if err != nil {
return "", err
}
claims := ConnectTokenClaims{
Subject: subject,
Scope: "connect:" + remoteID,
Issuer: accessID,
IssuedAt: now,
ExpiresAt: now + ttlSeconds,
Nonce: nonce,
}
payloadJSON, err := json.Marshal(claims)
if err != nil {
return "", err
}
payloadB64 := base64.RawURLEncoding.EncodeToString(payloadJSON)
appSig := hmacB64(secretKey, payloadB64)
return "v1." + payloadB64 + "." + appSig, nil
}Step 3: Connect from the Client
The remote_id used here must match the target bound during token issuance. In the common case, this is the target device device_id. On Android, HarmonyOS, iOS, and Flutter, register the connection state callback before calling connect(...). Web reports the result through Promise resolve/reject.
val conn = TiRtcConn()
conn.onStateChanged = TiRtcConnStateListener { state, errorCode ->
when (state) {
TiRtcConnState.CONNECTED -> Log.i("TiRTC", "connected")
TiRtcConnState.DISCONNECTED -> Log.w("TiRTC", "disconnected errorCode=$errorCode")
else -> Log.d("TiRTC", "state=$state errorCode=$errorCode")
}
}
conn.connect(
remoteId = remoteId,
token = token,
)import { TiRtcConn, TiRtcConnState } from 'tirtc-av/Index';
const conn = new TiRtcConn();
conn.onStateChanged = (state: TiRtcConnState, errorCode: number): void => {
if (state === TiRtcConnState.connected) {
console.info('connected');
return;
}
if (state === TiRtcConnState.disconnected) {
console.warn(`disconnected errorCode=${errorCode}`);
return;
}
console.info(`state=${state} errorCode=${errorCode}`);
};
conn.connect({
remoteId,
token,
});final class ConnectionObserver: NSObject, TiRtcConnDelegate {
func conn(_ conn: TiRtcConn, didChangeState state: TiRtcConnState, errorCode: Int32) {
switch state {
case .connected:
print("connected")
case .disconnected:
print("disconnected: \(errorCode)")
default:
print("state: \(state), errorCode: \(errorCode)")
}
}
}
let observer = ConnectionObserver()
let conn = TiRtcConn(delegate: observer)
conn.connect(remoteId: remoteId, token: token)final TiRtcConn conn = TiRtcConn();
conn.onStateChanged = (TiRtcConnState state, int errorCode) {
if (state == TiRtcConnState.connected) {
debugPrint('connected');
return;
}
if (state == TiRtcConnState.disconnected) {
debugPrint('disconnected errorCode=$errorCode');
return;
}
debugPrint('state=$state errorCode=$errorCode');
};
conn.connect(remoteId: remoteId, token: token);const conn = new TiRtcConn();
try {
await conn.connect({
deviceId: remoteId,
token,
});
// Promise resolve means the connection is established.
} catch (error) {
console.error('connect failed:', error);
}Playback, command messaging, and stream messaging all build on this connection.
Alignment Checks
- Treat
TiEVENT_SYS_STARTEDas the device-started signal. - The target bound during backend signing and the client-side
remote_idmust identify the same target. - If your current backend still uses the field name
peer_id, keep it equal to the same target string used asremote_idhere. - Treat
tokenas an opaque string on the client side. Do not assemble, parse, or rewrite it there.