Flutter¶
Media runs on the livekit_client package. The Tele part is one HTTP call, shown below.
Prebuilt package
tele-flutter wraps all of this: an API client that acts as the signed-in user, a call controller that handles ICE servers, chat, reconnects and rejoins, and a native TeleCallScreen. Add it as a git dependency at ref: v0.2.0. This page shows what it does underneath, if you'd rather build your own.
Install¶
dependencies:
livekit_client: ^2.13.1 # ≥ 2.12.0 is required for Bangla/emoji chat
http: ^1.2.0
app_links: ^6.0.0 # if you open calls from invite links
Platform setup¶
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
To keep a call alive in the background on Android 14+, you need a foreground service of type microphone|camera.
Join and connect¶
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:livekit_client/livekit_client.dart';
const teleApi = 'https://tele-api.cloudguys.io';
Future<Room> joinCall(String inviteCode, Future<String> Function() teleToken) async {
final res = await http.post(
Uri.parse('$teleApi/v1/invites/${Uri.encodeComponent(inviteCode)}/join'),
headers: {'Authorization': 'Bearer ${await teleToken()}', 'X-Client-Platform': 'flutter'},
);
final body = jsonDecode(res.body) as Map<String, dynamic>;
if (res.statusCode != 200) {
throw TeleError(body['error']['code'], body['error']['message']);
}
final ice = (body['ice_servers'] as List?)
?.map((s) => RTCIceServer(
urls: List<String>.from(s['urls']),
username: s['username'],
credential: s['credential'],
))
.toList();
final room = Room(roomOptions: const RoomOptions(adaptiveStream: true, dynacast: true));
await room.connect(
body['ws_url'],
body['token'],
connectOptions: ConnectOptions(
// Tele's TURN servers replace the SDK defaults; omit when null.
rtcConfiguration: ice == null ? const RTCConfiguration() : RTCConfiguration(iceServers: ice),
),
);
await room.localParticipant?.setMicrophoneEnabled(true);
await room.localParticipant?.setCameraEnabled(true);
return room;
}
class TeleError implements Exception {
TeleError(this.code, this.message);
final String code, message;
}
Render video with VideoTrackRenderer from livekit_client, or with the widgets in livekit_components.
Invite links¶
Handle your own invite URL (for example https://yourapp.example/call/{code}) with app_links, extract the code, preview it with GET /v1/invites/{code}, then call joinCall. See Scheduling & invites.
Chat¶
Use text streams on lk.chat with your own small chat UI. See Chat. Don't use the prebuilt chat widget in livekit_components; it uses an older message format.