Skip to main content
Version: v3

RTC Guide

Install the SDK

Download the SDK from the Downloads page and import the TapRTC module:

Make sure git-lfs is installed on your system, then add the dependencies via UPM:

"dependencies":{
...
"com.taptap.tds.rtc":"https://github.com/TapTap/TapRTC-Unity.git#3.28.3",
}

Caution

  • RTC must be configured in the Developer Center before use.
  • You need to implement the appropriate signature authentication service on your own servers.
  • C# SDK must periodically call the TapRTC.Poll interface to trigger relevant event callbacks.

On Android, you need to apply for network and audio-related permissions:

<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

For iOS, you need to request microphone permission (Privacy - Microphone Usage Description).

Games usually have no need for background calls; players usually play the game while the voice is playing, and the game stays in the foreground. If the type of game is special and needs to support background calls, then you must also request background play permission: Configure Capability > Background Modes > Audio, AirPlay, and Picture in Picture in target.

Core Interfaces

The RTC is initialized and configured with TapRTCConfig. The initialization process is asynchronous and you must wait for the initialization result to be returned before proceeding to the next step.

  • For ClientId, ClientToken, and ServerUrl, see the note about application credentials.
  • UserId: Developer-defined user ID for tagging players.
  • DeviceId: Developer-defined device ID used to tag the device.
  • AudioPerfProfile: Audio quality profile (LOW, MID, or HIGH; default is MID).
using TapTap.RTC;

var config = new TapRTCConfig.Builder()
.ClientID("ClientId")
.ClientToken("ClientToken")
.ServerUrl("ServerUrl")
.UserId("UserId")
.DeviceId("DeviceId")
.AudioProfile(AudioPerfProfile.MID)
.ConfigBuilder();

ResultCode code = await TapRTC.Init(config);

if (code == ResultCode.OK) {
// Initialized successfully
} else {
// Failed
}
// In the RTC module of the SDK, interfaces that return ResultCode indicate success with ResultCode.OK.

Trigger Callback Events

For the C# SDK, you must call the Poll method in the Update method to trigger the event callback. Failure to call this method will cause the SDK to throw exceptions.

public void Update()
{
ResultCode code = TapRTC.Poll();
if (code == ResultCode.OK) {
// Triggered callback successfully
} else {
// Failed
}
}

For the Java SDK and the Objective-C SDK, you do not need to call the Poll method regularly.

Resume

ResultCode code = TapRTC.Resume();
if (code == ResultCode.OK) {
// Resumed
} else {
// Failed
}

Pause

ResultCode code = TapRTC.Pause();
if (code == ResultCode.OK) {
// Paused
} else {
// Failed
}

Create Rooms

After successful initialization, the SDK can make live voice calls only after creating a room. The room number (roomId) must be specified when creating the room. Whether to enable range audio must also be set when creating the room. The C# SDK does not enable it by default. For the Java SDK, you must specify whether to enable range audio when creating the room. The Objective-C SDK uses a separate interface to create rooms with range audio.

bool enableRangeAudio = false;
var room = await TapRTC.AcquireRoom("roomId", enableRangeAudio);
room.RegisterEventAction(new TapRTCEvent()
{
OnDisconnect = (code, message) => { label.text += "\n" + $"Disconnected code:{code} msg:{e}"; },
OnEnterFailure = s => { label.text += "\n" + $"Failed to enter the room:{s}"; },
OnEnterSuccess = () => { label.text += "\n" + $"Entered the room"; },
OnExit = () => { label.text += "\n" + $"Left the room"; },
OnUserEnter = userId => { label.text += "\n" + $"{userId} entered the room"; },
OnUserExit = userId => { label.text += "\n" + $"{userId} left the room"; },
OnUserSpeaker = (userId, volume) => { label.text += "\n" + $"{userId} is speaking in the room; the volume is {volume}"; },
OnUserSpeakEnd = userId => { label.text += "\n" + $"{userId} stopped speaking"; },
// Returns the audio quality after the switch; see the section "Switch Audio Quality" below
OnRoomTypeChanged = (i) => { label.text += "\n" + $"The audio quality is now {i}"; },
OnRoomQualityChanged = (weight, loss, delay) =>
{
Debug.Log($"Audio quality:{weight} Packet loss:{loss}% Delay:{delay}ms");
},

});

Join a Room

Enter a room using the server-side generated authentication information.

After successfully entering a room, a callback is made via OnEnterSuccess in TapRTCEvent.

ResultCode code = await room.Join("authBuffer");
if (code == ResultCode.OK) {
// Successfully joined the room
}
if (code == ResultCode.ERROR_ALREADY_IN_ROOM) {
// The player is already in the room
}

The `authBuffer' is the authentication information generated on the server side, as described in the Server-Side Authentication section below.

Exit a Room

After leaving a room, a callback is made via OnExit in TapRTCEvent.

ResultCode code = room.Exit();

Listen to Someone's Voice (Enabled by Default)

ResultCode code = room.EnableUserAudio("userId");
if (code == ResultCode.OK) {
// Success
}
if (code == ResultCode.ERROR_USER_NOT_EXIST) {
// The player does not exist
}

Disable Someone's Voice

ResultCode code = room.DisableUserAudio("userId");
if (code == ResultCode.OK) {
// Success
}
if (code == ResultCode.ERROR_USER_NOT_EXIST) {
// The player does not exist
}

Enable/Disable Voice

This interface sets whether or not to receive audio. In general, it is recommended that games use the interface to turn on/off speakers.

// Enable
ResultCode code = room.EnableAudioReceiver(true);

// Disable
ResultCode code = room.EnableAudioReceiver(false);

Switch Audio Quality

There are three levels of audio quality: LOW, MID, and HIGH.

You can change the audio quality after you enter the room.

room.ChangeRoomType(AudioPerfProfile.LOW);
room.ChangeRoomType(AudioPerfProfile.MID);
room.ChangeRoomType(AudioPerfProfile.HIGH);

Changing the audio quality triggers the OnRoomTypeChanged callback.

Get the Users in the Room

HashSet<string> userIdList = room.Users;

Enable/Disable Microphone

// Enable
ResultCode code = TapRTC.GetAudioDevice().EnableMic(true);

// Disable
ResultCode code = TapRTC.GetAudioDevice().EnableMic(false);

Enable/Disable Speaker

// Enable
ResultCode code = TapRTC.GetAudioDevice().EnableSpeaker(true);

// Disable
ResultCode code = TapRTC.GetAudioDevice().EnableSpeaker(false);

Set/Get Volume

Volume is an integer from 0 to 100.

int vol = 60;

// Set microphone volume
ResultCode code = TapRTC.GetAudioDevice().SetMicVolume(vol);
// Set speaker volume
ResultCode code = TapRTC.GetAudioDevice().SetSpeakerVolume(vol);

// Get microphone volume
int micVolume = TapRTC.GetAudioDevice().GetMicVolume();
// Get speaker volume
int speakerVolume = TapRTC.GetAudioDevice().GetSpeakerVolume();

Enable/Disable Audio Play

// Enable
ResultCode code = TapRTC.GetAudioDevice().EnableAudioPlay(true);
// Disable
ResultCode code = TapRTC.GetAudioDevice().EnableAudioPlay(false);

Enable/Disable Loopback

// Enable
ResultCode code = TapRTC.GetAudioDevice().EnableLoopback(true);
// Disable
ResultCode code = TapRTC.GetAudioDevice().EnableLoopback(false);

Range Audio

The range audio feature can support the following functions:

  • Allowing other team members within a certain range of the player to hear the player's voice;
  • Support for a large number of users to turn on the microphone at the same time for voice calls in the same room.

To use range audio, you must first specify that range audio is enabled when you create a room:

bool enableRangeAudio = true;
var room = await TapRTC.AcquireRoom("roomId", enableRangeAudio);

Also, before players enter the room, they must change the audio quality to LOW:

room.ChangeRoomType(AudioPerfProfile.LOW);

Next, set the team number and voice mode:

  • World Mode: Other team members within [a certain range] (#set-audio-reception-range) of the current player can hear the player's voice;
  • Team Mode: Only team members can talk to each other.

In both modes, team members can talk to each other regardless of distance.

int teamId = 12345678;
ResultCode code = room.GetRtcRangeAudioCtrl().SetRangeAudioTeam(teamId);
if (code == ResultCode.OK) {
// Success
}
if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
// Range audio is not enabled for this room
}

// World Mode
ResultCode resultCode = room.GetRtcRangeAudioCtrl().SetRangeAudioMode(RangeAudioMode.WORLD);
if (resultCode == ResultCode.OK) {
// Success
}
if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
// Range audio is not enabled for this room
}
// Team Mode
ResultCode resultCode = room.GetRtcRangeAudioCtrl().SetRangeAudioMode(RangeAudioMode.TEAM);
if (resultCode == ResultCode.OK) {
// Success
}
if (resultCode == ResultCode.ERROR_NOT_RANGE_ROOM) {
// Range audio is not enabled for this room
}

Then enter the room and Set Audio Reception Range and Update Source Orientation to make the range audio effective.

If you want to change the voice mode after entering the room, you can call SetRangeAudioMode again.

Set Audio Reception Range

The audio reception range controls whether or not other team members can hear your voice in world mode, and is invoked after you enter the room and usually only needs to be set once.

int range = 300;
ResultCode code = room.GetRtcRangeAudioCtrl().UpdateAudioReceiverRange(range);
if (code == ResultCode.OK) {
// Success
}
if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
// Range audio is not enabled for this room
}

Other team members who are out of range will not be able to hear the player.

If 3D voice is also enabled, the distance will also affect the volume level:

DistanceVolume decay
N < range/101.0 (No decay)
N >= range/10range/10/N

Update Source Orientation

After successfully entering the room, you must call this interface in Unity's Update method to update the orientation and direction of the sound source for the range audio to take effect. The orientation is specified by the front, right, and top coordinates of the world coordinate system, and the direction is specified by the unit vector of the front, right, and top axes of its own coordinate system.

int x = 1;
int y = 2;
int z = 3;
Position position = new Position(x, y, z);

float[] axisForward = new float[3] {1.0, 0.0, 0.0};
float[] axisRight = new float[3] {0.0, 1.0, 0.0};
float[] axisUp = new float[3] {0.0, 0.0, 1.0};
Forward forward = new Forward(axisForward, axisRight, axisUp);
ResultCode code = room.GetRtcRangeAudioCtrl().UpdateSelfPosition(position, forward);
if (code == ResultCode.OK) {
// Success
}
if (code == ResultCode.ERROR_NOT_RANGE_ROOM) {
// Range audio is not enabled for this room
}

The orientation has no effect on whether the voice is heard or not, so if you do not enable 3D Voice, the orientation parameter can be set freely when updating the orientation of the sound source. However, when 3D Voice is enabled, the orientation must be set correctly to get accurate 3D sound effects.

3D Voice

Enabling 3D Voice allows you to convert voices without orientation to voices with source orientation to increase player immersion. This interface takes two parameters, the first specifying whether the current player can hear the 3D sound effect, and the second specifying whether the 3D voice works [within the team] (#range-audio).

bool enable3D = true;
bool applyToTeam = true;
ResultCode code = TapRTC.GetAudioDevice().EnableSpatializer(enable3D, applyToTeam);

Error Codes

Some of the operations in the above document return ResultCode, and the code examples give the error codes corresponding to some common error types. The complete list of error codes is shown below:

namespace TapTap.RTC
{
public enum ResultCode
{
OK = 0,
ERROR_UNKNOWN = 1,
ERROR_UNIMPLEMENTED = 2,
ERROR_NOT_ON_MAIN_THREAD = 3,
ERROR_INVAIDARGS = 4,
ERROR_NOT_INIT = 5,
ERROR_CONFIG_ERROR = 11,
ERROR_NET = 21,
ERROR_NET_TIMEOUT = 22,
ERROR_USER_NOT_EXIST = 101,
ERROR_ROOM_NOT_EXIST = 102,
ERROR_DEVICE_NOT_EXIST = 103,
ERROR_TEAM_ID_NOT_NULL = 104,
ERROR_ALREADY_IN_ROOM = 105,
ERROR_NO_PERMISSION = 106,
ERROR_AUTH_FAILED = 107,
ERROR_LIB_ERROR = 108,
ERROR_NOT_RANGE_ROOM = 109,
}
}

Server Side

To secure the chat channel, the RTC service must be used with the game's own authentication server. In addition, the game's own server is used to respond to compliance callbacks and to invoke the player removal interface.

Server-Side Authentication

Before a client joins a room, it must obtain a signature from your own authentication server, after which the RTC cloud verifies the signature, and only requests with valid signatures are executed, and illegal requests are blocked.

sequenceDiagram Client->>Authentication Server: 1. Require signature before entering room Authentication Server-->>Client: 2. Generate signature to return to client Client->>RTC Cloud: 3. Encode the signature in the request and send it to the real-time voice server RTC Cloud-->>Client: 4. Verify the content of the request and the signature and perform the subsequent operations
  1. The client requests a signature from the game's authentication server before entering the room;
  2. The authentication server generates a signature to return to the client based on the authentication key algorithm described below;
  3. The client receives the signature, encrypts it in the request, and sends it to the RTC server;
  4. The RTC server performs a verification of the request content and signature, and performs the subsequent actual operation after passing the verification.

Signatures use a combination of the HMAC-SHA1 algorithm and Base64. For different requests, your app generates different signatures (see format descriptions below). Overall, signing is the process of signing player and room information using a specific key (in this case, we use the application's Master Key).

Authentication Key Algorithm

The signature generation process used for authentication involves plaintext, key, and encryption algorithm.

Plaintext

The plaintext is a json string consisting of the following fields (in any order)

FieldType/LengthDescription
userIdstringAn identifier of the user entering the room
appIdstringThe game's Client ID
expireAtunsigned int/4Expiration time (current time + expiration date (in seconds, recommended value: 300s))
roomIdstringRoom ID
Key

The Master Key (i.e. Server Secret) of the game. Both the Client ID and Server Secret can be viewed in Developer Center > Your game > Game Services > Configuration.

Encryption Algorithm

The encryption algorithm uses a combination of HMAC-SHA1 algorithm and Base64, similar to the JWT format. The generated result contains two parts: payload (plaintext) and sign (encrypted string).

  1. Construct the JSON string according to the fields in the table above.

  2. Base64-encode the JSON string from the previous step to obtain the payload.

  3. Generate the sign using HMAC-SHA1 on the payload with key.

  4. Use . to join the payload and the token.

Note: The JSON string itself is field-order independent, but the spliced payload and the payload used to generate the sign must be in the same field order or they will not pass the checksum in the RTC cloud.

The following sample code in Java and Go is provided for reference:

Java example
import com.google.gson.Gson;
import org.junit.Test;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;

import java.time.Instant;
import java.util.Base64;

public class JUnitTestSuite {

private static final String MAC_NAME = "HmacSHA1";

@Test
public void testToken() throws Exception {
String masterKey = "masterKey";
Token t = new Token();
t.appId = "appId";
t.userId ="user_test";
t.roomId ="room_test";;

int expTime = (int) Instant.now().getEpochSecond() + 5 * 60;
t.expireAt = expTime;

// server authBuff to your SDK Client
String authBuff = genToken(t, masterKey);
assertNotNull(authBuff);
}

private String genToken(Token token, String key) throws Exception {
Gson gson = new Gson();
String t = gson.toJson(token);
String payload = Base64.getEncoder().encodeToString(t.getBytes(StandardCharsets.UTF_8));
byte[] pEncryptOutBuf = hmacSHA1Encrypt(payload.getBytes(StandardCharsets.UTF_8), key);
String sign = Base64.getEncoder().encodeToString(pEncryptOutBuf);
return payload + "." + sign;
}


byte[] hmacSHA1Encrypt(byte[] text, String key) throws Exception {
byte[] data = key.getBytes(StandardCharsets.UTF_8);
SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
Mac mac = Mac.getInstance(MAC_NAME);
mac.init(secretKey);
return mac.doFinal(text);
}

class Token {
String userId;
String appId;
String roomId;
long expireAt;
}
}
Go example
package configs

import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/assert"
)


func TestToken(t *testing.T) {
assert := assert.New(t)
t1 := &Token{
UserId: "appId",
AppId: "user_test",
RoomId: "roomId_test",
ExpireAt: time.Now().Unix() + 5*60,
}
authBuff := GenToken(t1, "masterKey")
assert.NotEmpty(authBuff)
fmt.Println(authBuff)
}


const (
sep = "."
)

func GenToken(t *Token, masterKey string) string {
b, err := json.Marshal(t)
if err != nil {
return ""
}
payload := base64.StdEncoding.EncodeToString(b)
sign := base64.StdEncoding.EncodeToString(HmacSHA1(masterKey, payload))
return payload + sep + sign

}


func HmacSHA1(key string, data string) []byte {
mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(data))
return mac.Sum(nil)
}

type Token struct {
UserId string `json:"userId,omitempty"`
AppId string `json:"appId,omitempty"`
RoomId string `json:"roomId,omitempty"`
ExpireAt int64 `json:"expireAt,omitempty"`
}

Deployment Method

Since the encryption key uses Server Secret, the logic of the encryption algorithm must be implemented on the server side. Do not implement the encryption logic on the client side.

Usage

The game's own authentication server generates the encryption string and sends it to the client. The client then passes the appropriate authentication information when calling the join room interface.

The C# SDK also provides a GenToken method for testing when integrating the SDK on the client side. For example, client-side developers can test the functionality of adding rooms on the client side before waiting for server-side developers to implement and deploy the appropriate interfaces. Another example is that the client developer can compare the encrypted string generated by the SDK's own GenToken with the encrypted string generated by the server to verify that the server has implemented the encryption algorithm correctly.

var authBuffer = AuthBufferHelper.GenToken(appId, roomId, userId, masterKey);

Note that since this method requires Server Secret to be passed as a parameter, it is intended for internal testing and development only, and should not be used in published code or installation packages. If you are concerned about Server Secret being leaked into external code or installation packages due to human error, or if you want to minimize the number of internal developers having access to Server Secret for security reasons, it is recommended that you do not use the GenToken method provided by the SDK, and that you generate the encrypted string using the game's own authentication server for internal testing as well.

Compliance Callback

After you set the callback address in the RTC dashboard (Developer Center > Your game > Game Services > RTC > Settings), the set callback address will be called if the voice content is illegal.

The callback address should be a URL of the HTTP(S) protocol interface, support the POST method, and use UTF-8 for data encoding.

Example POST body for callback:

{
"HitFlag":true,
"Msg":"Illegal message",
"ScanFinishTime":1634893736,
"ScanStartTime":1634893734,
"Scenes":[
"default"
],
"VoiceFilterPiece":[
{
"Duration":14000,
"HitFlag":true,
"Info":"Illegal message",
"MainType":"abuse",
"Offset":0,
"PieceStartTime":1634893734,
"RoomId":"1234",
"UserId":"123456",
"VoiceFilterDetail":[
{
"EndTime":0,
"KeyWord":"Illegal keyword",
"Label":"abuse",
"Rate":"0.00",
"StartTime":0
}
]
}
]
}

You can get the sign field in the callback header to verify that the request is coming from the RTC cloud.

Compliance Callback Verification Algorithm

  1. Append the POST prefix to the POST body of the callback to get the payload:

    POST{"HitFlag":true,"Msg":"Illegal message",/* Other logic */}

    Note:

    • Please read the JSON content directly from the HTTP request body**. Deserializing to the programming language data structure may change the order of the fields, resulting in a validation failure.
    • POST and body are directly connected without spaces in between.
  2. Perform HMAC-SHA1 encryption on the payload. The key is the game's Server Secret.

  3. BASE64-encode the result of the previous step to get the sign.

  4. Compare it with the value of the sign field in the HTTP header of the callback. If it is the same, then the request is coming from the RTC cloud.

Here is a sample Go code for reference:

Go example
package main

import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"github.com/labstack/echo/v4"
"io/ioutil"
"net/http"
)


func testCallback(c echo.Context) error {
sign := c.Request().Header.Get("sign")
body, _ := ioutil.ReadAll(c.Request().Body)
checkGMESign(sign, "yourMasterKey", string(body))
return c.NoContent(http.StatusOK)
}


func checkGMESign(signature, secretKey, body string) bool {
sign := genSign(secretKey, body)
return sign == signature
}

func genSign(secretKey, body string) string {
content := "POST" + body
a := hmacSHA1(secretKey, content)
return base64.StdEncoding.EncodeToString(a)
}

func hmacSHA1(key string, data string) []byte {
mac := hmac.New(sha1.New, []byte(key))
mac.Write([]byte(data))
return mac.Sum(nil)
}

Remove Players

In some scenarios, the game may need to kick players out of a room, such as when illegal content is involved. You can call the RTC service's REST API from your own server to fulfill this need.

Request Format

For POST and PUT requests, the request body must be in JSON format and the Content-Type of the HTTP header must be set to application/json.

The request is authenticated by the key/value pairs contained in the HTTP header, with the following parameters:

KeyValueMeaningSource
X-LC-Id{{appid}}The App Id (Client Id) of the current applicationCan be viewed in the console
X-LC-Key{{masterkey}},masterThe Master Key (Server Secret) of the current applicationCan be viewed in the console

Base URL

The Base URL for REST API requests (the {{host}} in the curl examples) is the custom API domain of your app. You can update or find it on the Developer Center. See Domain for more details.

REST API

curl -X DELETE \
-H "Content-Type: application/json" \
-H "X-LC-Id: {{appId}}" \
-H "X-LC-Key: {{masterKey}},master" \
-d '{"roomId":"YOUR-ROOM-ID", "userId":"YOUR-USER-ID"}' \
https://{{host}}/rtc/v1/room/member

The HTTP status code in response to a successful removal is 200, and the HTTP status code in response to an error is the appropriate error code, e.g. 401 if you do not have permission.

Note that after a player has been removed, the player can rejoin the room and speak again after joining the room. The game must also implement the appropriate blocking logic on its own authentication server so that no signature is issued when the player rejoins the room, preventing players from circumventing the restriction by rejoining the room.