What you build
A video room is the one building block behind video calls, group calls and meetings. Everyone who joins the same room name sees and hears each other. Two people in a room is a video call; more people is a group call or a meeting.
Integrating it takes three steps, and the same flow works on every platform:
Install the SDK
Flutter for Android and iOS apps, Web for React and browsers, or the native Android SDK for Kotlin and Java.
Issue a token from your server
Your backend signs a short-lived token for one user and one room. The App Certificate never ships in an app.
Join and publish
The app asks your endpoint for a token, joins the room with it, and turns on the camera and microphone.
1. Install the SDK
flutter pub add gravix_rtcnpm install gravix-rtcimplementation("com.gravixcloud:gravix-rtc-android:0.2.0")Full platform requirements and permissions are on the SDKs page.
2. Get a token from your server
Sign up to get your App ID, App Certificate and region URL. Your backend uses them to sign a token for each signed-in user. The App Certificate stays on your server. Read more in access tokens.
// server.ts (your backend, Node 18+). This file never ships inside an app.
import express from 'express';
import { createToken } from 'gravix-rtc-token';
const APP_ID = process.env.GRAVIX_APP_ID!; // from your dashboard
const APP_CERTIFICATE = process.env.GRAVIX_APP_CERTIFICATE!; // server only, never in an app
const REGION_URL = process.env.GRAVIX_URL!; // wss://YOUR-REGION-URL
const app = express();
app.use(express.json());
// requireUser is your own login check: the identity comes from YOUR session,
// never from the request body.
app.post('/rtc/token', requireUser, async (req, res) => {
const room = req.body?.room;
if (typeof room !== 'string' || !/^[\w.-]{1,64}$/.test(room)) {
return res.status(400).json({ error: 'invalid room' });
}
const token = await createToken(APP_ID, APP_CERTIFICATE, {
room, // the library adds your App ID prefix
identity: req.user.id,
name: req.user.name,
canPublish: true, // false = listen only; decide it here, not in the app
ttlSeconds: 60 * 60, // short-lived: 1 hour
});
res.json({ token, url: REGION_URL });
});
app.listen(3000);3. Join the room
Join with the token and turn on the camera and microphone. Remote video arrives as tracks you attach to a view. Use the same room name for everyone who should see each other.
import 'package:gravix_rtc/gravix_rtc.dart';
// One provider per signed-in user. It caches each token until it is about to expire.
final tokens = GravixTokenProvider.endpoint(
Uri.parse('https://YOUR-BACKEND/rtc/token'),
headers: {'Authorization': 'Bearer $sessionJwt'}, // your app's own login session
);
final room = GravixRoomService();
Future<void> join(String roomId, String userId, String name) async {
room.onRemoteVideoTrack = (uid, track) {
// show it with VideoTrackRenderer(track)
};
final ok = await room.connectWithTokenProvider(
tokenProvider: tokens,
request: GravixTokenRequest(
room: roomId,
identity: userId,
name: name,
canPublish: true,
),
publishMic: true,
enableVideo: true,
);
if (!ok) {
// room.lastTokenError is set when your token endpoint failed
}
}
Future<void> leave() => room.disconnect();import { Room, RoomEvent, GravixTokenProvider, connectWithTokenProvider } from 'gravix-rtc';
// Calls YOUR backend with the user's session cookie. Tokens are cached until they expire.
const tokens = GravixTokenProvider.endpoint('https://YOUR-BACKEND/rtc/token', {
credentials: 'include',
});
const room = new Room();
const stage = document.getElementById('stage')!;
room
.on(RoomEvent.TrackSubscribed, (track) => {
stage.appendChild(track.attach()); // a <video> or <audio> element
})
.on(RoomEvent.TrackUnsubscribed, (track) => {
track.detach().forEach((el) => el.remove());
});
export async function join(roomId: string, userId: string, name: string) {
await connectWithTokenProvider(room, {
tokenProvider: tokens,
request: { room: roomId, identity: userId, name },
});
await room.localParticipant.setMicrophoneEnabled(true);
await room.localParticipant.setCameraEnabled(true);
}
export const leave = () => room.disconnect();import com.gravixcloud.rtc.GravixRtc
import com.gravixcloud.rtc.events.RoomEvent
import com.gravixcloud.rtc.events.collect
import com.gravixcloud.rtc.regions.GravixRegionMeasurements
import com.gravixcloud.rtc.renderer.SurfaceViewRenderer
import com.gravixcloud.rtc.room.track.VideoTrack
class CallActivity : AppCompatActivity() {
private val room by lazy { GravixRtc.create(applicationContext) }
// url and token come from YOUR backend: POST https://YOUR-BACKEND/rtc/token -> { token, url }.
// Call after RECORD_AUDIO and CAMERA are granted.
private fun join(url: String, token: String, renderer: SurfaceViewRenderer) {
room.initVideoRenderer(renderer)
lifecycleScope.launch {
launch {
room.events.collect { event ->
when (event) {
is RoomEvent.TrackSubscribed -> (event.track as? VideoTrack)?.addRenderer(renderer)
is RoomEvent.Resuming, is RoomEvent.Reconnecting -> showReconnecting(true)
is RoomEvent.Resumed, is RoomEvent.Reconnected -> showReconnecting(false)
else -> Unit
}
}
}
// the measured nearest region when one was measured, else url unchanged
room.connect(GravixRegionMeasurements.resolveConnectUrl(url), token)
room.localParticipant.setMicrophoneEnabled(true)
room.localParticipant.setCameraEnabled(true)
}
}
private fun showReconnecting(on: Boolean) {
title = if (on) "Reconnecting..." else "In call"
}
override fun onDestroy() {
room.disconnect()
super.onDestroy()
}
}Next steps
- One-to-one calls: a complete two-person video call.
- Meetings and group calls: a participant grid, mute, active speaker and screen sharing.
- Classrooms: a teacher with camera and slides, and students who join to watch.
- Video call and meeting SDK: what the product does and what it costs.
Stuck? Talk to a person.
