Flutter SDK
TgoRTC Flutter SDK,基于 LiveKit 的跨平台音视频通话 SDK。
安装
在 pubspec.yaml 中添加依赖:
dependencies:
tgortcflutter: ^1.0.0
依赖项
SDK 自动包含以下依赖:
livekit_client: ^2.3.6flutter_webrtc: ^0.12.12
常用功能介绍
TgoRTC.instance.roomManager
TgoRTC.instance.participantManager
TgoRTC.instance.audioManager
快速开始
1. 初始化 SDK
import 'package:tgortcflutter/tgortc.dart';
TgoRTC.instance.init(
Options()
..debug = true
..mirror = false,
);
2. 创建房间信息并加入
import 'package:tgortcflutter/entity/const.dart';
import 'package:tgortcflutter/entity/room_info.dart';
final roomInfo = RoomInfo(
'roomName',
'token',
'url',
'loginUID',
'creatorUID',
);
roomInfo.maxParticipants = 9;
roomInfo.rtcType = RTCType.video;
roomInfo.timeout = 30;
await TgoRTC.instance.roomManager.joinRoom(
roomInfo,
micEnabled: true,
cameraEnabled: true,
screenShareEnabled: false,
);
说明:
SDK 当前同时兼容 screenShareEnabled 和旧拼写 scrennShareEnabled,
文档示例统一使用 screenShareEnabled。
3. 监听连接状态
import 'package:tgortcflutter/entity/const.dart';
void handleConnectStatus(String roomName, ConnectStatus status, String reason) {
print('房间 $roomName: $status - $reason');
switch (status) {
case ConnectStatus.connecting:
print('正在连接...');
break;
case ConnectStatus.connected:
print('已连接到房间');
break;
case ConnectStatus.reconnecting:
print('正在重连...');
break;
case ConnectStatus.reconnected:
print('已重新连接到房间');
break;
case ConnectStatus.disconnected:
print('已断开连接');
break;
}
}
TgoRTC.instance.roomManager.addConnectListener(handleConnectStatus);
TgoRTC.instance.roomManager.removeConnectListener(handleConnectStatus);
4. 获取参与者
final local = TgoRTC.instance.participantManager.getLocalParticipant();
final remotes = TgoRTC.instance.participantManager.getRemoteParticipants();
final all = TgoRTC.instance.participantManager.getAllParticipants();
TgoRTC.instance.participantManager.addNewParticipantListener((participant) {
print('新参与者加入: ${participant.uid}');
});
5. 媒体控制和事件
媒体控制仅适用于本地参与者。
import 'package:tgortcflutter/entity/const.dart';
import 'package:tgortcflutter/entity/video_info.dart';
final participant = TgoRTC.instance.participantManager.getLocalParticipant();
await participant?.setMicrophoneEnabled(true);
await participant?.setCameraEnabled(true);
await participant?.setScreenShareEnabled(true);
participant?.switchCamera();
await participant?.setSpeakerphoneOn(true, forceSpeakerOutput: false);
await participant?.toggleSpeakerphone();
final micEnabled = participant?.getMicrophoneEnabled() ?? false;
final cameraEnabled = participant?.getCameraEnabled() ?? false;
final screenShareEnabled = participant?.getScreenShareEnabled() ?? false;
final speakerEnabled = participant?.getSpeakerEnabled() ?? false;
final cameraPosition = participant?.getCameraPosition();
final audioLevel = participant?.getAudioLevel() ?? 0;
final isSpeaking = participant?.getIsSpeaking() ?? false;
final createdAt = participant?.createdAt;
final currentVideoInfo = participant?.currentVideoInfo;
void onMicChange(bool enabled) {
print('麦克风: ${enabled ? "开启" : "关闭"}');
}
void onCameraChange(bool enabled) {
print('摄像头: ${enabled ? "开启" : "关闭"}');
}
void onSpeakerChange(bool enabled) {
print('扬声器: ${enabled ? "开启" : "关闭"}');
}
void onScreenShareChange(bool enabled) {
print('屏幕共享: ${enabled ? "开启" : "关闭"}');
}
void onCameraPositionChange(TgoCameraPosition position) {
print('摄像头位置: $position');
}
void onSpeakingChange(bool speaking) {
print('正在说话: $speaking');
}
void onConnQualityChange(TgoConnectionQuality quality) {
print('连接质量: $quality');
}
void onJoined() {
print('参与者已加入');
}
void onLeave() {
print('参与者已离开');
}
void onTrackPublished() {
print('轨道已发布');
}
void onTrackUnpublished() {
print('轨道已取消发布');
}
void onVideoInfoChange(VideoInfo info) {
print('视频: ${info.resolutionString}, ${info.bitrateString}');
}
participant?.addMicrophoneStatusListener(onMicChange);
participant?.addCameraStatusListener(onCameraChange);
participant?.addSpeakerStatusListener(onSpeakerChange);
participant?.addScreenShareStatusListener(onScreenShareChange);
participant?.addCameraPositionListener(onCameraPositionChange);
participant?.addSpeakingListener(onSpeakingChange);
participant?.addConnQualityListener(onConnQualityChange);
participant?.addJoinedListener(onJoined);
participant?.addLeaveListener(onLeave);
participant?.addTrackPublishedListener(onTrackPublished);
participant?.addTrackUnpublishedListener(onTrackUnpublished);
participant?.addVideoInfoListener(onVideoInfoChange);
participant?.removeMicrophoneStatusListener(onMicChange);
participant?.removeCameraStatusListener(onCameraChange);
participant?.removeSpeakerStatusListener(onSpeakerChange);
participant?.removeScreenShareStatusListener(onScreenShareChange);
participant?.removeCameraPositionListener(onCameraPositionChange);
participant?.removeSpeakingListener(onSpeakingChange);
participant?.removeConnQualityListener(onConnQualityChange);
participant?.removeJoinedListener(onJoined);
participant?.removeLeaveListener(onLeave);
participant?.removeTrackPublishedListener(onTrackPublished);
participant?.removeTrackUnpublishedListener(onTrackUnpublished);
participant?.removeVideoInfoListener(onVideoInfoChange);
6. 渲染视频轨道
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:tgortcflutter/tgortc.dart';
import 'package:tgortcflutter/track/tgo_track_renderer.dart';
class VideoView extends StatefulWidget {
final TgoParticipant participant;
const VideoView({super.key, required this.participant});
State<VideoView> createState() => _VideoViewState();
}
class _VideoViewState extends State<VideoView> {
late final TgoTrackRenderer _renderer;
void initState() {
super.initState();
_renderer = TgoTrackRenderer(
source: TrackSource.camera,
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
mirror: true,
placeholder: const SizedBox.shrink(),
onTrackChange: (hasTrack) {
debugPrint('轨道可用: $hasTrack');
},
);
_renderer.setParticipant(widget.participant);
}
Widget build(BuildContext context) {
return _renderer.build();
}
}
7. 音频管理
final isSpeakerOn = TgoRTC.instance.audioManager.isSpeakerOn;
final canSwitchSpeaker = TgoRTC.instance.audioManager.canSwitchSpeakerphone;
final speakerOn = TgoRTC.instance.audioManager.speakerOn;
final inputDevices = await TgoRTC.instance.audioManager.getAudioInputDevices();
final outputDevices = await TgoRTC.instance.audioManager.getAudioOutputDevices();
final videoDevices = await TgoRTC.instance.audioManager.getDevices('videoinput');
await TgoRTC.instance.audioManager.setSpeakerphoneOn(true);
await TgoRTC.instance.audioManager.toggleSpeakerphone();
if (inputDevices.isNotEmpty) {
await TgoRTC.instance.audioManager.switchAudioInputDevice(
inputDevices.first.deviceId,
);
}
if (outputDevices.isNotEmpty) {
await TgoRTC.instance.audioManager.switchAudioOutputDevice(
outputDevices.first.deviceId,
);
}
void onDeviceChange(List<dynamic> devices) {
print('设备变化: $devices');
}
TgoRTC.instance.audioManager.addDeviceChangeListener(onDeviceChange);
TgoRTC.instance.audioManager.removeDeviceChangeListener(onDeviceChange);
8. 离开房间
await TgoRTC.instance.roomManager.leaveRoom();
API 参考
TgoRTC
| 属性 | 类型 | 说明 |
|---|---|---|
instance | TgoRTC | 单例实例 |
options | Options | SDK 配置 |
roomManager | TgoRoomManager | 房间管理 |
participantManager | TgoParticipantManager | 参与者管理 |
audioManager | TgoAudioManager | 音频管理 |
TgoRoomManager
| 方法 | 说明 |
|---|---|
joinRoom(roomInfo, {micEnabled, cameraEnabled, screenShareEnabled}) | 加入房间 |
leaveRoom() | 离开当前房间 |
addConnectListener(listener) | 添加连接状态监听器 |
removeConnectListener(listener) | 移除连接状态监听器 |
addVideoInfoListener(listener) | 添加本地视频信息监听器 |
removeVideoInfoListener(listener) | 移除本地视频信息监听器 |
| 属性 | 说明 |
|---|---|
room | LiveKit Room 实例 |
currentRoomInfo | 当前房间信息 |
currentVideoInfo | 当前本地视频信息 |
TgoParticipantManager
| 方法 | 说明 |
|---|---|
getLocalParticipant() | 获取本地参与者 |
getRemoteParticipants() | 获取远程参与者 |
getAllParticipants() | 获取所有参与者 |
getPendingParticipantCreatedAt() | 获取未加入参与者的创建时间 |
removeParticipantByUid(uid) | 按 UID 移除未加入参与者 |
removePendingParticipants(roomName, uids) | 移除未加入参与者 |
missed(roomName, uids) | 标记未接听参与者 |
inviteParticipant(uids, {roomName}) | 邀请参与者 |
invite(roomName, uids) | 邀请参与者 |
addNewParticipantListener(listener) | 监听新参与者 |
removeNewParticipantListener(listener) | 移除新参与者监听 |
TgoParticipant
媒体控制方法
| 方法 | 说明 |
|---|---|
setMicrophoneEnabled(enabled) | 启用/禁用麦克风 |
setCameraEnabled(enabled) | 启用/禁用摄像头 |
setScreenShareEnabled(enabled) | 启用/禁用屏幕共享 |
switchCamera() | 切换前后摄像头 |
setSpeakerphoneOn(on, {forceSpeakerOutput}) | 设置扬声器 |
toggleSpeakerphone() | 切换扬声器 |
状态获取方法
| 方法 | 说明 |
|---|---|
getMicrophoneEnabled() | 获取麦克风状态 |
getCameraEnabled() | 获取摄像头状态 |
getScreenShareEnabled() | 获取屏幕共享状态 |
getSpeakerEnabled() | 获取扬声器状态 |
getCameraPosition() | 获取摄像头位置 |
getVideoTrack({source}) | 获取视频轨道 |
getAudioLevel() | 获取当前音量 |
getIsSpeaking() | 是否正在说话 |
事件监听方法
| 方法 | 说明 |
|---|---|
addMicrophoneStatusListener(listener) | 监听麦克风状态变化 |
addCameraStatusListener(listener) | 监听摄像头状态变化 |
addSpeakerStatusListener(listener) | 监听扬声器状态变化 |
addScreenShareStatusListener(listener) | 监听屏幕共享状态变化 |
addCameraPositionListener(listener) | 监听摄像头位置变化 |
addSpeakingListener(listener) | 监听说话状态变化 |
addConnQualityListener(listener) | 监听连接质量变化 |
addJoinedListener(listener) | 监听参与者加入 |
addLeaveListener(listener) | 监听参与者离开 |
addTrackPublishedListener(listener) | 监听轨道发布 |
addTrackUnpublishedListener(listener) | 监听轨道取消发布 |
addVideoInfoListener(listener) | 监听视频信息变化 |
remove...Listener(listener) | 移除对应监听器 |
属性
| 属性 | 说明 |
|---|---|
uid | 参与者 ID |
isLocal | 是否为本地参与者 |
isJoined | 是否已加入 |
createdAt | 创建时间 |
currentVideoInfo | 当前视频信息 |
TgoTrackRenderer
TgoTrackRenderer({
TrackSource source = TrackSource.camera,
RTCVideoViewObjectFit fit = RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
bool? mirror,
Widget? placeholder,
ValueChanged<bool>? onTrackChange,
})
| 方法 | 说明 |
|---|---|
setParticipant(participant) | 设置参与者 |
clear() | 清除参与者 |
build() | 构建视频 Widget |
TgoAudioManager
| 方法 / 属性 | 说明 |
|---|---|
isSpeakerOn | 当前是否使用扬声器 |
canSwitchSpeakerphone | 是否支持切换扬声器 |
speakerOn | 底层硬件扬声器状态 |
currentAudioInputDeviceId | 当前输入设备 ID |
currentAudioOutputDeviceId | 当前输出设备 ID |
addDeviceChangeListener(listener) | 添加设备变化监听 |
removeDeviceChangeListener(listener) | 移除设备变化监听 |
setSpeakerphoneOn(on, {forceSpeakerOutput}) | 设置扬声器 |
toggleSpeakerphone() | 切换扬声器 |
getAudioInputDevices() | 获取音频输入设备 |
getAudioOutputDevices() | 获取音频输出设备 |
getDevices(kind) | 获取指定类型设备 |
switchAudioInputDevice(deviceId, {setAsDefault = true}) | 切换音频输入设备 |
switchAudioOutputDevice(deviceId, {setAsDefault = true}) | 切换音频输出设备 |
枚举类型
ConnectStatus
enum ConnectStatus {
connecting,
connected,
reconnecting,
reconnected,
disconnected,
}
RTCType
enum RTCType {
audio,
video,
}
TgoConnectionQuality
enum TgoConnectionQuality {
unknown,
excellent,
good,
poor,
lost,
}
TgoCameraPosition
enum TgoCameraPosition {
front,
back,
}
RoomInfo 类
class RoomInfo {
String roomName;
String token;
String url;
String loginUID;
String creatorUID;
int maxParticipants;
RTCType rtcType;
bool isP2P;
List<String> uidList;
int timeout;
String getP2PToUID();
bool isCreator();
}
配置选项
class Options {
bool mirror = false;
bool debug = true;
}
最佳实践
保存参与者引用
避免重复调用 getLocalParticipant(),建议在连接成功后保存本地参与者引用,并在销毁时移除监听器。
连接成功后再启用媒体
参考实际 Flutter 集成示例,Android 上为了避免 flutter_webrtc 在部分场景下的状态不同步或崩溃问题,推荐先仅建立房间连接,再在连接成功后顺序启用本地媒体:
await TgoRTC.instance.roomManager.joinRoom(
roomInfo,
micEnabled: false,
cameraEnabled: false,
);
final local = TgoRTC.instance.participantManager.getLocalParticipant();
await local?.setMicrophoneEnabled(true);
await local?.setCameraEnabled(true);
处理未接听参与者
如果业务侧已经知道哪些被邀请成员未接听,可以调用:
TgoRTC.instance.participantManager.removePendingParticipants(
roomInfo.roomName,
['user_b', 'user_c'],
);
平台配置
Android
在 android/app/src/main/AndroidManifest.xml 添加权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
iOS
在 ios/Runner/Info.plist 添加权限描述:
<key>NSCameraUsageDescription</key>
<string>需要访问摄像头进行视频通话</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要访问麦克风进行语音通话</string>