VoxaRTC
Documentation menu
Start here

Quickstart

Ten minutes to a working call: add the package, point it at a gateway, mint a token, join a channel. If you already have an Agora app, read the migration guide instead — it is shorter.

You need a VoxaRTC endpoint and a project on it. The managed endpoint is https://gate.zamansheikh.com; your App ID, certificate and test tokens come from the console.

1

Add the package

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  voxa_rtc_engine: ^0.2.8
bash
flutter pub get
2

Point the SDK at your gateway

This is the only non-Agora line your app will contain. It lives in a separate import so the main barrel stays byte-compatible with Agora's.

lib/main.dart
import 'package:flutter/material.dart';
import 'package:voxa_rtc_engine/voxa.dart';

void main() {
  VoxaRtc.serverUrl = 'https://gate.zamansheikh.com';
  runApp(const MyApp());
}
3

Declare platform permissions

Android needs nothing: the package's own manifest contributes CAMERA, RECORD_AUDIO, MODIFY_AUDIO_SETTINGS, INTERNET, ACCESS_NETWORK_STATE and the Bluetooth permissions, exactly as agora_rtc_engine does. You still request them at runtime as usual.

iOS and macOS need the usual usage descriptions:

ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>Camera access is used for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is used for calls.</string>
4

Create a project and mint a test token

Every channel join is authenticated. In the console, create a project — it gives you an App ID and an app certificate — then mint a short-lived publisher token for a test channel from the Tokens tab.

5

Join a channel

From here on the code is ordinary Agora code. If you have written an Agora integration before, nothing below will surprise you.

lib/call_page.dart
import 'package:flutter/material.dart';
import 'package:voxa_rtc_engine/voxa_rtc_engine.dart';

class CallPage extends StatefulWidget {
  const CallPage({super.key});
  @override
  State<CallPage> createState() => _CallPageState();
}

class _CallPageState extends State<CallPage> {
  late final RtcEngine _engine;
  final _remoteUids = <int>[];

  @override
  void initState() {
    super.initState();
    _init();
  }

  Future<void> _init() async {
    _engine = createAgoraRtcEngine();
    await _engine.initialize(const RtcEngineContext(
      appId: '<your appId>',
      channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
    ));

    _engine.registerEventHandler(RtcEngineEventHandler(
      onJoinChannelSuccess: (connection, elapsed) {
        debugPrint('joined ${connection.channelId}');
      },
      onUserJoined: (connection, remoteUid, elapsed) {
        setState(() => _remoteUids.add(remoteUid));
      },
      onUserOffline: (connection, remoteUid, reason) {
        setState(() => _remoteUids.remove(remoteUid));
      },
    ));

    await _engine.setClientRole(
      role: ClientRoleType.clientRoleBroadcaster,
    );
    await _engine.enableVideo();
    await _engine.startPreview();

    await _engine.joinChannel(
      token: '<token from step 4>',
      channelId: 'demo',
      uid: 0,                // 0 = let the token/server assign
      options: const ChannelMediaOptions(),
    );
  }

  @override
  void dispose() {
    _engine.leaveChannel();
    _engine.release();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Stack(children: [
      AgoraVideoView(
        controller: VideoViewController(
          rtcEngine: _engine,
          canvas: const VideoCanvas(uid: 0),   // local preview
        ),
      ),
      for (final uid in _remoteUids)
        AgoraVideoView(
          controller: VideoViewController.remote(
            rtcEngine: _engine,
            canvas: VideoCanvas(uid: uid),
            connection: const RtcConnection(channelId: 'demo'),
          ),
        ),
    ]);
  }
}
6

Run it on two devices

Mint a second token with a different uid for the same channel, and run the app on a second device. Each should fire onUserJoined for the other and render its video.

Next