Flutter视频会议插件custom_jitsi_meet_testtt的使用
介绍
Jitsi Meet 插件为 Flutter 提供了支持视频会议的功能,适用于 Android、iOS 和 Web 平台。
Jitsi Meet 是一个开源(Apache 许可)的基于 WebRTC 的 JavaScript 应用程序,通过 Jitsi Videobridge 提供高质量、安全且可扩展的视频会议服务。
目录
配置
IOS
注意: 示例代码可以在 XCode 12.2 和 Flutter 1.22.4 下编译。
Podfile
确保在 Podfile 中添加以下内容,声明平台版本为 11.0 或更高,并禁用 BITCODE。
platform :ios, '11.0'
...
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
Info.plist
在 Info.plist 文件中添加以下内容以获取相机和麦克风权限。
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) 需要访问您的摄像头进行会议。</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) 需要访问您的麦克风进行会议。</string>
Android
Gradle
将构建工具的依赖版本设置为最低 3.6.3:
dependencies {
classpath 'com.android.tools.build:gradle:3.6.3' <!-- 升级此版本 -->
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
将 Gradle Wrapper 的版本设置为最低 5.6.4:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip <!-- 升级此版本 -->
AndroidManifest.xml
Jitsi Meet 的 AndroidManifest.xml 会与您的项目冲突,尤其是 application:label
字段。解决方法是在 android/app/src/main/AndroidManifest.xml
中添加工具库并使用 tools:replace="android:label"
。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="yourpackage.com"
xmlns:tools="http://schemas.android.com/tools">
<application
tools:replace="android:label"
android:name="your.application.name"
android:label="My Application"
android:icon="@mipmap/ic_launcher">
...
</application>
...
</manifest>
最低 SDK 版本 23
在 android/app/build.gradle
中更新最低 SDK 版本为 23。
defaultConfig {
applicationId "com.gunschu.jitsi_meet_example"
minSdkVersion 23 // 必须为 Jitsi 提供支持
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Proguard
启用 ProGuard 后,如果没有 proguard-rules.pro
文件,发布 APK 将缺少 Flutter 包裹和 React Native 代码。在 android/app/build.gradle
中添加 ProGuard 支持。
buildTypes {
release {
signingConfig signingConfigs.debug
// 添加以下三行以启用 ProGuard
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
然后在同一目录下创建 proguard-rules.pro
文件,并复制官方示例文件的内容。
Web
在 Web 端实现时,需要在 index.html
中包含 Jitsi JS 库。
<script src="https://meet.jit.si/external_api.js" type="application/javascript"></script>
示例代码:
<body>
<!-- 此脚本安装 service_worker.js 以提供 PWA 功能 -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/flutter_service_worker.js');
});
}
</script>
<script src="https://meet.jit.si/external_api.js" type="application/javascript"></script>
<script src="main.dart.js" type="application/javascript"></script>
</body>
加入会议
示例代码如下:
_joinMeeting() async {
try {
FeatureFlag featureFlag = FeatureFlag();
featureFlag.welcomePageEnabled = false;
featureFlag.resolution = FeatureFlagVideoResolution.MD_RESOLUTION; // 限制视频分辨率为 360p
var options = JitsiMeetingOptions()
..room = "myroom" // 必填,空格会被移除
..serverURL = "https://someHost.com"
..subject = "Meeting with Gunschu"
..userDisplayName = "My Name"
..userEmail = "myemail@email.com"
..userAvatarURL = "https://someimageurl.com/image.jpg" // 或 .png
..audioOnly = true
..audioMuted = true
..videoMuted = true
..featureFlag = featureFlag;
await JitsiMeet.joinMeeting(options);
} catch (error) {
debugPrint("error: $error");
}
}
JitsiMeetingOptions 参数说明
字段 | 是否必填 | 默认值 | 描述 |
---|---|---|---|
room | 是 | N/A | 唯一房间名称,将附加到 serverURL 上。有效字符:字母数字、破折号和下划线。 |
subject | 否 | $room | 会议名称显示在顶部。如果为空,则默认为房间名称,破折号和下划线替换为空格,首字母大写。 |
userDisplayName | 否 | “Fellow Jitster” | 用户显示名称。 |
userEmail | 否 | none | 用户电子邮件地址。 |
audioOnly | 否 | false | 开始会议时是否仅音频。 |
audioMuted | 否 | false | 开始会议时是否静音。 |
videoMuted | 否 | false | 开始会议时是否关闭视频。 |
serverURL | 否 | meet.jitsi.si | 指定自己的托管服务器。必须是有效的绝对 URL,格式为 <scheme>://<host>[/<path>] 。 |
userAvatarURL | 否 | none | 用户头像 URL。 |
token | 否 | none | JWT 令牌用于身份验证。 |
featureFlag | 否 | 见下方 | FeatureFlag 对象,用于启用/禁用功能并设置 Jitsi Meet SDK 的视频分辨率。 |
FeatureFlag 参数说明
标志 | 默认 (Android) | 默认 (iOS) | 描述 |
---|---|---|---|
addPeopleEnabled | true | true | 启用“添加人员”按钮。 |
calendarEnabled | true | auto | 启用日历集成。 |
callIntegrationEnabled | true | true | 启用呼叫集成(iOS 的 CallKit,Android 的 ConnectionService)。 |
closeCaptionsEnabled | true | true | 启用字幕选项。 |
conferenceTimerEnabled | true | true | 启用会议计时器。 |
… | … | … | … |
监听会议事件
监听每场会议的事件:
await JitsiMeet.joinMeeting(options,
listener: JitsiMeetingListener(
onConferenceWillJoin: ({message}) {
debugPrint("${options.room} will join with message: $message");
},
onConferenceJoined: ({message}) {
debugPrint("${options.room} joined with message: $message");
},
onConferenceTerminated: ({message}) {
debugPrint("${options.room} terminated with message: $message");
},
onPictureInPictureWillEnter: ({message}) {
debugPrint("${options.room} entered PIP mode with message: $message");
},
onPictureInPictureTerminated: ({message}) {
debugPrint("${options.room} exited PIP mode with message: $message");
},
onError: (error) {
debugPrint("_onError broadcasted: $error");
}
));
监听全局会议事件:
[@override](/user/override)
void initState() {
super.initState();
JitsiMeet.addListener(JitsiMeetingListener(
onConferenceWillJoin: _onConferenceWillJoin,
onConferenceJoined: _onConferenceJoined,
onConferenceTerminated: _onConferenceTerminated,
onPictureInPictureWillEnter: _onPictureInPictureWillEnter,
onPictureInPictureTerminated: _onPictureInPictureTerminated,
onError: _onError));
}
[@override](/user/override)
void dispose() {
super.dispose();
JitsiMeet.removeAllListeners();
}
_onConferenceWillJoin({message}) {
debugPrint("_onConferenceWillJoin broadcasted");
}
_onConferenceJoined({message}) {
debugPrint("_onConferenceJoined broadcasted");
}
_onConferenceTerminated({message}) {
debugPrint("_onConferenceTerminated broadcasted");
}
_onPictureInPictureWillEnter({message}) {
debugPrint("_onPictureInPictureWillEnter broadcasted with message: $message");
}
_onPictureInPictureTerminated({message}) {
debugPrint("_onPictureInPictureTerminated broadcasted with message: $message");
}
_onError(error) {
debugPrint("_onError broadcasted");
}
程序化关闭会议
JitsiMeet.closeMeeting();
贡献
发送带有详细信息的拉取请求,清晰描述问题或功能。保持更改小且一次只针对一个问题。
示例代码
完整示例代码如下:
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:jitsi_meet/jitsi_meet.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(debugShowCheckedModeBanner: false, home: Meeting());
}
}
class Meeting extends StatefulWidget {
[@override](/user/override)
_MeetingState createState() => _MeetingState();
}
class _MeetingState extends State<Meeting> {
final serverText = TextEditingController();
final roomText = TextEditingController(text: "plugintestroom");
final subjectText = TextEditingController(text: "My Plugin Test Meeting");
final nameText = TextEditingController(text: "Plugin Test User");
final emailText = TextEditingController(text: "fake@email.com");
final iosAppBarRGBAColor =
TextEditingController(text: "#0080FF80"); // 透明蓝色
bool? isAudioOnly = true;
bool? isAudioMuted = true;
bool? isVideoMuted = true;
[@override](/user/override)
void initState() {
super.initState();
JitsiMeet.addListener(JitsiMeetingListener(
onConferenceWillJoin: _onConferenceWillJoin,
onConferenceJoined: _onConferenceJoined,
onConferenceTerminated: _onConferenceTerminated,
onError: _onError));
}
[@override](/user/override)
void dispose() {
super.dispose();
JitsiMeet.removeAllListeners();
}
[@override](/user/override)
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: kIsWeb
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
width: width * 0.30,
child: meetConfig(),
),
Container(
width: width * 0.60,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
color: Colors.white54,
child: SizedBox(
width: width * 0.60 * 0.70,
height: width * 0.60 * 0.70,
child: JitsiMeetConferencing(
extraJS: [
// 示例额外 JS 设置
'<script>function echo(){console.log("echo!!!")};</script>',
'<script src="https://code.jquery.com/jquery-3.5.1.slim.js" integrity="sha256-DrT5NfxfbHvMHux31Lkhxg42LY6of8TaYyK50jnxRnM=" crossorigin="anonymous"></script>'
],
),
)),
))
],
)
: meetConfig(),
),
),
);
}
Widget meetConfig() {
return SingleChildScrollView(
child: Column(
children: <Widget>[
SizedBox(height: 16.0),
TextField(
controller: serverText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Server URL",
hintText: "提示:留空使用 meet.jitsi.si"),
),
SizedBox(height: 14.0),
TextField(
controller: roomText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Room",
),
),
SizedBox(height: 14.0),
TextField(
controller: subjectText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Subject",
),
),
SizedBox(height: 14.0),
TextField(
controller: nameText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Display Name",
),
),
SizedBox(height: 14.0),
TextField(
controller: emailText,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Email",
),
),
SizedBox(height: 14.0),
TextField(
controller: iosAppBarRGBAColor,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "AppBar Color(IOS only)",
hintText: "提示:必须是 HEX RGBA 格式"),
),
SizedBox(height: 14.0),
CheckboxListTile(
title: Text("Audio Only"),
value: isAudioOnly,
onChanged: _onAudioOnlyChanged,
),
SizedBox(height: 14.0),
CheckboxListTile(
title: Text("Audio Muted"),
value: isAudioMuted,
onChanged: _onAudioMutedChanged,
),
SizedBox(height: 14.0),
CheckboxListTile(
title: Text("Video Muted"),
value: isVideoMuted,
onChanged: _onVideoMutedChanged,
),
Divider(height: 48.0, thickness: 2.0),
SizedBox(
height: 64.0,
width: double.maxFinite,
child: ElevatedButton(
onPressed: () {
_joinMeeting();
},
child: Text(
"Join Meeting",
style: TextStyle(color: Colors.white),
),
style: ButtonStyle(
backgroundColor:
MaterialStateColor.resolveWith((states) => Colors.blue)),
),
),
SizedBox(height: 48.0),
],
),
);
}
_onAudioOnlyChanged(bool? value) {
setState(() {
isAudioOnly = value;
});
}
_onAudioMutedChanged(bool? value) {
setState(() {
isAudioMuted = value;
});
}
_onVideoMutedChanged(bool? value) {
setState(() {
isVideoMuted = value;
});
}
_joinMeeting() async {
String? serverUrl = serverText.text.trim().isEmpty ? null : serverText.text;
// 启用或禁用任何功能标志
Map<FeatureFlagEnum, bool> featureFlags = {
FeatureFlagEnum.WELCOME_PAGE_ENABLED: false,
};
if (!kIsWeb) {
if (Platform.isAndroid) {
featureFlags[FeatureFlagEnum.CALL_INTEGRATION_ENABLED] = false;
} else if (Platform.isIOS) {
featureFlags[FeatureFlagEnum.PIP_ENABLED] = false;
}
}
var options = JitsiMeetingOptions(room: roomText.text)
..serverURL = serverUrl
..subject = subjectText.text
..userDisplayName = nameText.text
..userEmail = emailText.text
..iosAppBarRGBAColor = iosAppBarRGBAColor.text
..audioOnly = isAudioOnly
..audioMuted = isAudioMuted
..videoMuted = isVideoMuted
..featureFlags.addAll(featureFlags)
..webOptions = {
"roomName": roomText.text,
"width": "100%",
"height": "100%",
"enableWelcomePage": false,
"chromeExtensionBanner": null,
"userInfo": {"displayName": nameText.text}
};
debugPrint("JitsiMeetingOptions: $options");
await JitsiMeet.joinMeeting(
options,
listener: JitsiMeetingListener(
onConferenceWillJoin: (message) {
debugPrint("${options.room} will join with message: $message");
},
onConferenceJoined: (message) {
debugPrint("${options.room} joined with message: $message");
},
onConferenceTerminated: (message) {
debugPrint("${options.room} terminated with message: $message");
},
genericListeners: [
JitsiGenericListener(
eventName: 'readyToClose',
callback: (dynamic message) {
debugPrint("readyToClose callback");
}),
]),
);
}
void _onConferenceWillJoin(message) {
debugPrint("_onConferenceWillJoin broadcasted with message: $message");
}
void _onConferenceJoined(message) {
debugPrint("_onConferenceJoined broadcasted with message: $message");
}
void _onConferenceTerminated(message) {
debugPrint("_onConferenceTerminated broadcasted with message: $message");
}
_onError(error) {
debugPrint("_onError broadcasted: $error");
}
}
更多关于Flutter视频会议插件custom_jitsi_meet_testtt的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
custom_jitsi_meet_testtt
是一个用于在 Flutter 应用中集成视频会议功能的插件。它基于 Jitsi Meet,这是一个开源的视频会议解决方案。使用这个插件,你可以轻松地在你的 Flutter 应用中添加视频会议功能。
以下是如何在 Flutter 项目中使用 custom_jitsi_meet_testtt
插件的步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 custom_jitsi_meet_testtt
插件的依赖。
dependencies:
flutter:
sdk: flutter
custom_jitsi_meet_testtt: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
2. 配置 Android 和 iOS 项目
Android
在 android/app/src/main/AndroidManifest.xml
文件中添加以下权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
iOS
在 ios/Runner/Info.plist
文件中添加以下权限:
<key>NSCameraUsageDescription</key>
<string>We need access to your camera for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need access to your microphone for audio calls.</string>
3. 使用插件
在你的 Dart 代码中,你可以使用 custom_jitsi_meet_testtt
插件来启动视频会议。
import 'package:flutter/material.dart';
import 'package:custom_jitsi_meet_testtt/custom_jitsi_meet_testtt.dart';
class VideoCallScreen extends StatefulWidget {
@override
_VideoCallScreenState createState() => _VideoCallScreenState();
}
class _VideoCallScreenState extends State<VideoCallScreen> {
late JitsiMeet _jitsiMeet;
@override
void initState() {
super.initState();
_jitsiMeet = JitsiMeet();
}
void _startMeeting() async {
var options = JitsiMeetingOptions(
roomName: "your_room_name", // 会议室名称
isAudioMuted: false,
isVideoMuted: false,
);
await _jitsiMeet.joinMeeting(options);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Video Call'),
),
body: Center(
child: ElevatedButton(
onPressed: _startMeeting,
child: Text('Start Meeting'),
),
),
);
}
}
4. 运行项目
确保你已经连接了 Android 或 iOS 设备,然后运行 flutter run
来启动你的应用。
5. 处理会议事件
你可以监听会议事件,例如会议结束、用户加入或离开等。
_jitsiMeet.onConferenceWillJoin.listen((event) {
print("Conference will join: $event");
});
_jitsiMeet.onConferenceJoined.listen((event) {
print("Conference joined: $event");
});
_jitsiMeet.onConferenceTerminated.listen((event) {
print("Conference terminated: $event");
});
6. 自定义 Jitsi Meet 界面
你可以通过 JitsiMeetingOptions
来定制 Jitsi Meet 的界面,例如隐藏工具栏、设置背景颜色等。
var options = JitsiMeetingOptions(
roomName: "your_room_name",
isAudioMuted: false,
isVideoMuted: false,
featureFlags: {
"toolbox.enabled": false,
"filmstrip.enabled": false,
},
);
7. 结束会议
你可以通过调用 _jitsiMeet.closeMeeting()
来结束会议。
void _endMeeting() async {
await _jitsiMeet.closeMeeting();
}
8. 处理错误
确保你处理了可能的错误,例如网络问题或权限问题。
try {
await _jitsiMeet.joinMeeting(options);
} catch (e) {
print("Error joining meeting: $e");
}