Flutter视频会议插件custom_omni_jitsi_meet_new的使用
Flutter视频会议插件custom_omni_jitsi_meet_new的使用
简介
custom_omni_jitsi_meet_new 是一个用于 Flutter 的视频会议插件,支持 Android、iOS 和 Web 平台。Jitsi Meet 是一个开源的基于 WebRTC 的视频会议解决方案,能够提供高质量、安全且可扩展的视频会议服务。
使用步骤
配置
iOS
确保在 Podfile 中声明平台版本为 12.0 或更高,并禁用 BITCODE。
platform :ios, '12.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 文件中添加以下内容:
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) MyApp needs access to your camera for meetings.</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) MyApp needs access to your microphone for meetings.</string>
Android
在 Gradle 文件中设置构建工具版本和分布版本。
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
在 AndroidManifest.xml
文件中添加工具库并替换应用标签的 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 版本更新为 24。
minSdkVersion 24
启用 ProGuard 支持。
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
创建 proguard-rules.pro
文件,避免程序崩溃。
Web
在 Web 部分的 index.html
文件中引入 Jitsi JavaScript 库。
<script src="https://meet.jit.si/external_api.js" type="application/javascript"></script>
加入会议
通过 JitsiMeetingOptions
配置会议参数。
_joinMeeting() async {
try {
FeatureFlag featureFlag = FeatureFlag();
featureFlag.resolution = FeatureFlagVideoResolution.MD_RESOLUTION; // 限制视频分辨率为 360p
final options = JitsiMeetingOptions(
room: 'myroom',
serverURL: 'https://someHost.com',
subject: 'Meeting with thorito',
userDisplayName: 'My Name',
userEmail: 'myemail@email.com',
userAvatarURL: 'https://someimageurl.com/image.jpg',
audioOnly: false,
audioMuted: false,
videoMuted: false,
featureFlags: featureFlag,
webOptions: {
'roomName': 'myroom',
'width': '100%',
'height': '100%',
'enableWelcomePage': false,
'enableNoAudioDetection': true,
'enableLobbyChat': false,
'enableNoisyMicDetection': true,
'enableClosePage': false,
'disableRemoveRaisedHandOnFocus': false,
'disableReactions': true,
'prejoinPageEnabled': false,
'hideDisplayName': true,
'hideConferenceSubject': true,
'hideConferenceTimer': true,
'hideParticipantsStats': true,
'hideLobbyButton': true,
'disableInviteFunctions': true,
'chromeExtensionBanner': null,
'readOnlyName': true,
'participantsPane': {
'hideModeratorSettingsTab': true,
'hideMoreActionsButton': true,
'hideMuteAllButton': true,
},
'hideAddRoomButton': true,
'breakoutRooms': {
'hideAddRoomButton': true,
'hideAutoAssignButton': true,
'hideJoinRoomButton': true,
'hideModeratorSettingsTab': true,
'hideMoreActionsButton': true,
'hideMuteAllButton': true,
},
'lang': 'en',
'configOverwrite': {
'doNotFlipLocalVideo': true,
'prejoinPageEnabled': false,
'disableDeepLinking': true,
'enableLobbyChat': false,
'enableClosePage': false,
'hideDisplayName': true,
'hideConferenceTimer': true,
'hideParticipantsStats': true,
'hideLobbyButton': true,
'readOnlyName': true,
'giphy': {
'enable': false,
},
'participantsPane': {
'hideModeratorSettingsTab': true,
'hideMoreActionsButton': true,
'hideMuteAllButton': true,
},
'hideAddRoomButton': true,
'breakoutRooms': {
'hideAddRoomButton': true,
'hideAutoAssignButton': true,
'hideJoinRoomButton': true,
'hideModeratorSettingsTab': true,
'hideMoreActionsButton': true,
'hideMuteAllButton': true,
},
'toolbarButtons': [
'camera',
'desktop',
'download',
'filmstrip',
'hangup',
'highlight',
'microphone',
'noisesuppression',
'select-background',
'tileview',
'toggle-camera',
'whiteboard'
]
},
'userInfo': {
'displayName': 'My Name',
'email': 'myemail@email.com',
}
});
} catch (error) {
debugPrint("error: $error");
}
}
监听会议事件
通过 JitsiMeetingListener
监听会议事件。
final JitsiMeetingResponse response = await JitsiMeet.joinMeeting(
options,
listener: JitsiMeetingListener(
onOpened: () {
debugPrint("JitsiMeetingListener - onOpened");
},
onClosed: () {
debugPrint("JitsiMeetingListener - onClosed");
},
onError: (error) {
debugPrint("JitsiMeetingListener - onError: error: $error");
},
onConferenceWillJoin: (url) {
debugPrint(
"JitsiMeetingListener - onConferenceWillJoin: url: $url");
},
onConferenceJoined: (url) {
debugPrint("JitsiMeetingListener - onConferenceJoined: url:$url");
},
onConferenceTerminated: (url, error) {
debugPrint(
"JitsiMeetingListener - onConferenceTerminated: url: $url, error: $error");
},
onParticipantLeft: (participantId) {
debugPrint(
"JitsiMeetingListener - onParticipantLeft: $participantId");
},
onParticipantJoined: (email, name, role, participantId) {
debugPrint("JitsiMeetingListener - onParticipantJoined: "
"email: $email, name: $name, role: $role, "
"participantId: $participantId");
},
onAudioMutedChanged: (muted) {
debugPrint(
"JitsiMeetingListener - onAudioMutedChanged: muted: $muted");
},
onVideoMutedChanged: (muted) {
debugPrint(
"JitsiMeetingListener - onVideoMutedChanged: muted: $muted");
},
onScreenShareToggled: (participantId, isSharing) {
debugPrint("JitsiMeetingListener - onScreenShareToggled: "
"participantId: $participantId, isSharing: $isSharing");
},
genericListeners: [
JitsiGenericListener(
eventName: 'readyToClose',
callback: (dynamic message) {
debugPrint("JitsiMeetingListener - readyToClose callback");
}),
]),
);
关闭会议
通过编程方式关闭会议。
JitsiMeet.hangUp();
完整示例代码
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:omni_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: "omni_room_sample_1234");
final subjectText = TextEditingController(text: "Subject1");
final nameText = TextEditingController(text: "User1");
final emailText = TextEditingController(text: "fake1@email.com");
final iosAppBarRGBAColor =
TextEditingController(text: "#0080FF80"); // transparent blue
bool? isAudioOnly = true;
bool? isAudioMuted = true;
bool? isVideoMuted = true;
[@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: [
// extraJs setup example
'<script src="https://code.jquery.com/jquery-3.6.3.slim.js" integrity="sha256-DKU1CmJ8kBuEwumaLuh9Tl/6ZB6jzGOBV/5YpNE2BWc=" 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: "Hint: Leave empty for 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: "Hint: This HAS to be in HEX RGBA format"),
),
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 {
final String? serverUrl =
serverText.text.trim().isEmpty ? null : serverText.text;
final featureFlags = {
FeatureFlagEnum.RESOLUTION: FeatureFlagVideoResolution.MD_RESOLUTION,
FeatureFlagEnum.WELCOME_PAGE_ENABLED: false,
};
if (!kIsWeb && Platform.isAndroid) {
featureFlags[FeatureFlagEnum.CALL_INTEGRATION_ENABLED] = false;
}
// Define meetings options here
final 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: featureFlags,
webOptions: {
"roomName": roomText.text,
"width": "100%",
"height": "100%",
"enableWelcomePage": false,
"enableNoAudioDetection": true,
"enableNoisyMicDetection": true,
"enableClosePage": false,
"prejoinPageEnabled": false,
"hideConferenceTimer": true,
"disableInviteFunctions": true,
"chromeExtensionBanner": null,
"configOverwrite": {
"prejoinPageEnabled": false,
"disableDeepLinking": true,
"enableLobbyChat": false,
"enableClosePage": false,
"chromeExtensionBanner": null,
},
"userInfo": {"email": emailText.text, "displayName": nameText.text}
});
await JitsiMeet.joinMeeting(
options,
listener: JitsiMeetingListener(
onOpened: () {
debugPrint("JitsiMeetingListener - onOpened");
},
onClosed: () {
debugPrint("JitsiMeetingListener - onClosed");
},
onError: (error) {
debugPrint("JitsiMeetingListener - onError: error: $error");
},
onConferenceWillJoin: (url) {
debugPrint(
"JitsiMeetingListener - onConferenceWillJoin: url: $url");
},
onConferenceJoined: (url) {
debugPrint("JitsiMeetingListener - onConferenceJoined: url:$url");
},
onConferenceTerminated: (url, error) {
debugPrint(
"JitsiMeetingListener - onConferenceTerminated: url: $url, error: $error");
},
onParticipantLeft: (participantId) {
debugPrint(
"JitsiMeetingListener - onParticipantLeft: $participantId");
},
onParticipantJoined: (email, name, role, participantId) {
debugPrint("JitsiMeetingListener - onParticipantJoined: "
"email: $email, name: $name, role: $role, "
"participantId: $participantId");
},
onAudioMutedChanged: (muted) {
debugPrint(
"JitsiMeetingListener - onAudioMutedChanged: muted: $muted");
},
onVideoMutedChanged: (muted) {
debugPrint(
"JitsiMeetingListener - onVideoMutedChanged: muted: $muted");
},
onScreenShareToggled: (participantId, isSharing) {
debugPrint("JitsiMeetingListener - onScreenShareToggled: "
"participantId: $participantId, isSharing: $isSharing");
},
genericListeners: [
JitsiGenericListener(
eventName: 'readyToClose',
callback: (dynamic message) {
debugPrint("JitsiMeetingListener - readyToClose callback");
}),
]),
);
}
}
更多关于Flutter视频会议插件custom_omni_jitsi_meet_new的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter视频会议插件custom_omni_jitsi_meet_new的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
custom_omni_jitsi_meet_new
是一个 Flutter 插件,用于集成 Jitsi Meet 视频会议功能到你的 Flutter 应用中。Jitsi Meet 是一个开源的视频会议解决方案,支持高质量的视频、音频和屏幕共享功能。
以下是如何使用 custom_omni_jitsi_meet_new
插件的步骤:
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 custom_omni_jitsi_meet_new
插件的依赖:
dependencies:
flutter:
sdk: flutter
custom_omni_jitsi_meet_new: ^1.0.0 # 请检查最新的版本号
然后运行 flutter pub get
来获取插件。
2. 配置 Android 和 iOS 项目
Android
在 android/app/build.gradle
文件中,确保 minSdkVersion
至少为 21:
android {
defaultConfig {
minSdkVersion 21
...
}
...
}
iOS
在 ios/Podfile
中,确保 platform
设置为 11.0
或更高:
platform :ios, '11.0'
然后运行 pod install
来安装 iOS 依赖。
3. 使用插件
在你的 Flutter 代码中,你可以通过以下步骤来使用 custom_omni_jitsi_meet_new
插件:
import 'package:flutter/material.dart';
import 'package:custom_omni_jitsi_meet_new/custom_omni_jitsi_meet_new.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: JitsiMeetDemo(),
);
}
}
class JitsiMeetDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Jitsi Meet Demo'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
_joinMeeting();
},
child: Text('Join Meeting'),
),
),
);
}
void _joinMeeting() async {
try {
var options = JitsiMeetingOptions(
room: "your-room-name", // 你的会议室名称
serverURL: "https://meet.jit.si", // Jitsi Meet 服务器地址
subject: "Meeting Subject", // 会议主题
userDisplayName: "Your Name", // 用户显示名称
userEmail: "your-email@example.com", // 用户邮箱
audioMuted: false,
videoMuted: false,
);
await CustomOmniJitsiMeetNew.joinMeeting(options);
} catch (error) {
print("Error: $error");
}
}
}
4. 处理会议事件
你可以监听会议中的事件,例如会议结束、用户加入或离开等。你可以通过 CustomOmniJitsiMeetNew
提供的回调来处理这些事件。
CustomOmniJitsiMeetNew.onConferenceWillJoin.listen((event) {
print("Conference will join: $event");
});
CustomOmniJitsiMeetNew.onConferenceJoined.listen((event) {
print("Conference joined: $event");
});
CustomOmniJitsiMeetNew.onConferenceTerminated.listen((event) {
print("Conference terminated: $event");
});
5. 权限配置
确保在 Android 和 iOS 项目中正确配置了摄像头和麦克风的权限。
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.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.INTERNET"/>
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>