Flutter视频会议插件jitsi_meet_wrapper的使用
Flutter视频会议插件jitsi_meet_wrapper的使用
jitsi_meet_wrapper
是一个用于Flutter的Jitsi Meet插件,封装了JitsiMeetSDK以支持Android和iOS平台。本文将介绍如何在Flutter项目中使用该插件进行视频会议,并提供完整的示例代码。
目录
加入会议
要加入会议,首先需要创建会议选项并启动会议:
var options = JitsiMeetingOptions(roomName: "my-room");
await JitsiMeetWrapper.joinMeeting(options);
更多选项请参考 JitsiMeetingOptions
。
配置
iOS
按照 官方文档 进行配置。
屏幕共享
要在iOS上启用屏幕共享,请添加“Broadcast Upload Extension”到您的应用中。具体步骤如下:
- 添加一个新的“Broadcast Upload Extension”目标。
- 将文件从
jitsi_meet_wrapper/example/ios/Broadcast Extension/
复制到新创建的目标文件夹中。 - 设置扩展目标的部署版本为14或更高。
- 创建一个App Group并将“Runner”目标和扩展目标添加到该组中。
- 在
SampleHandler.swift
中替换appGroupIdentifier
为您创建的App Group名称。 - 在“Runner”的
Info.plist
中添加键值对RTCAppGroupIdentifier
和RTCScreenSharingExtension
。 - 启用Dart代码中的功能标志
ios.screensharing.enabled
。 - 确保“Runner”的
Info.plist
中设置了UIBackgroundModes
为voip
。
Android
AndroidManifest.xml
修改 AndroidManifest.xml
以避免与JitsiMeetSDK冲突:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="your.package.name">
<application tools:replace="android:label"
android:name="your.app.name"
android:label="your app name"
android:icon="@mipmap/ic_launcher">
...
</application>
...
</manifest>
build.gradle
更新最低SDK版本至24:
android {
...
defaultConfig {
...
minSdkVersion 24 // 修改此处
...
}
...
}
监听会议事件
您可以监听会议的各种事件,例如:
await JitsiMeetWrapper.joinMeeting(
options: options,
listener: JitsiMeetingListener(
onConferenceWillJoin: (url) => print("onConferenceWillJoin: url: $url"),
onConferenceJoined: (url) => print("onConferenceJoined: url: $url"),
onConferenceTerminated: (url, error) => print("onConferenceTerminated: url: $url, error: $error"),
),
);
更多事件请参考 jitsi_meeting_listener.dart
。
已知问题
- 画面共享时无法使用画中画模式。
- 默认情况下摄像头方向为纵向。
- 使用
wakelock
插件时屏幕可能会变暗。
完整示例代码
以下是一个完整的Flutter应用程序示例,演示如何使用 jitsi_meet_wrapper
插件:
import 'package:flutter/material.dart';
import 'package:jitsi_meet_wrapper/jitsi_meet_wrapper.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Meeting(),
);
}
}
class Meeting extends StatefulWidget {
const Meeting({Key? key}) : super(key: key);
@override
_MeetingState createState() => _MeetingState();
}
class _MeetingState extends State<Meeting> {
final serverText = TextEditingController();
final roomText = TextEditingController(text: "jitsi-meet-wrapper-test-room");
final subjectText = TextEditingController(text: "My Plugin Test Meeting");
final tokenText = TextEditingController();
final userDisplayNameText = TextEditingController(text: "Plugin Test User");
final userEmailText = TextEditingController(text: "fake@email.com");
final userAvatarUrlText = TextEditingController();
bool isAudioMuted = true;
bool isAudioOnly = false;
bool isVideoMuted = true;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Jitsi Meet Wrapper Test')),
body: Container(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: buildMeetConfig(),
),
),
);
}
Widget buildMeetConfig() {
return SingleChildScrollView(
child: Column(
children: <Widget>[
const SizedBox(height: 16.0),
_buildTextField(
labelText: "Server URL",
controller: serverText,
hintText: "Hint: Leave empty for meet.jitsi.si",
),
const SizedBox(height: 16.0),
_buildTextField(labelText: "Room", controller: roomText),
const SizedBox(height: 16.0),
_buildTextField(labelText: "Subject", controller: subjectText),
const SizedBox(height: 16.0),
_buildTextField(labelText: "Token", controller: tokenText),
const SizedBox(height: 16.0),
_buildTextField(
labelText: "User Display Name",
controller: userDisplayNameText,
),
const SizedBox(height: 16.0),
_buildTextField(
labelText: "User Email",
controller: userEmailText,
),
const SizedBox(height: 16.0),
_buildTextField(
labelText: "User Avatar URL",
controller: userAvatarUrlText,
),
const SizedBox(height: 16.0),
CheckboxListTile(
title: const Text("Audio Muted"),
value: isAudioMuted,
onChanged: _onAudioMutedChanged,
),
const SizedBox(height: 16.0),
CheckboxListTile(
title: const Text("Audio Only"),
value: isAudioOnly,
onChanged: _onAudioOnlyChanged,
),
const SizedBox(height: 16.0),
CheckboxListTile(
title: const Text("Video Muted"),
value: isVideoMuted,
onChanged: _onVideoMutedChanged,
),
const Divider(height: 48.0, thickness: 2.0),
SizedBox(
height: 64.0,
width: double.maxFinite,
child: ElevatedButton(
onPressed: () => _joinMeeting(),
child: const Text(
"Join Meeting",
style: TextStyle(color: Colors.white),
),
style: ButtonStyle(
backgroundColor:
MaterialStateColor.resolveWith((states) => Colors.blue),
),
),
),
const 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<String, Object> featureFlags = {};
var options = JitsiMeetingOptions(
roomNameOrUrl: roomText.text,
serverUrl: serverUrl,
subject: subjectText.text,
token: tokenText.text,
isAudioMuted: isAudioMuted,
isAudioOnly: isAudioOnly,
isVideoMuted: isVideoMuted,
userDisplayName: userDisplayNameText.text,
userEmail: userEmailText.text,
featureFlags: featureFlags,
);
debugPrint("JitsiMeetingOptions: $options");
await JitsiMeetWrapper.joinMeeting(
options: options,
listener: JitsiMeetingListener(
onOpened: () => debugPrint("onOpened"),
onConferenceWillJoin: (url) {
debugPrint("onConferenceWillJoin: url: $url");
},
onConferenceJoined: (url) {
debugPrint("onConferenceJoined: url: $url");
},
onConferenceTerminated: (url, error) {
debugPrint("onConferenceTerminated: url: $url, error: $error");
},
onAudioMutedChanged: (isMuted) {
debugPrint("onAudioMutedChanged: isMuted: $isMuted");
},
onVideoMutedChanged: (isMuted) {
debugPrint("onVideoMutedChanged: isMuted: $isMuted");
},
onScreenShareToggled: (participantId, isSharing) {
debugPrint(
"onScreenShareToggled: participantId: $participantId, "
"isSharing: $isSharing",
);
},
onParticipantJoined: (email, name, role, participantId) {
debugPrint(
"onParticipantJoined: email: $email, name: $name, role: $role, "
"participantId: $participantId",
);
},
onParticipantLeft: (participantId) {
debugPrint("onParticipantLeft: participantId: $participantId");
},
onParticipantsInfoRetrieved: (participantsInfo, requestId) {
debugPrint(
"onParticipantsInfoRetrieved: participantsInfo: $participantsInfo, "
"requestId: $requestId",
);
},
onChatMessageReceived: (senderId, message, isPrivate) {
debugPrint(
"onChatMessageReceived: senderId: $senderId, message: $message, "
"isPrivate: $isPrivate",
);
},
onChatToggled: (isOpen) => debugPrint("onChatToggled: isOpen: $isOpen"),
onClosed: () => debugPrint("onClosed"),
),
);
}
Widget _buildTextField({
required String labelText,
required TextEditingController controller,
String? hintText,
}) {
return TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: labelText,
hintText: hintText),
);
}
}
通过上述示例代码,您可以在Flutter应用中轻松集成Jitsi Meet视频会议功能。希望这些信息对您有所帮助!
更多关于Flutter视频会议插件jitsi_meet_wrapper的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter视频会议插件jitsi_meet_wrapper的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中集成和使用jitsi_meet_wrapper
插件进行视频会议的基本代码示例。这个插件允许你嵌入Jitsi Meet视频会议到你的Flutter应用中。
第一步:添加依赖
首先,你需要在你的pubspec.yaml
文件中添加jitsi_meet_wrapper
依赖:
dependencies:
flutter:
sdk: flutter
jitsi_meet_wrapper: ^0.x.x # 请使用最新版本号
替换^0.x.x
为当前最新版本号,可以通过pub.dev网站查找最新版本。
第二步:导入包
在你的Flutter项目的Dart文件中,导入jitsi_meet_wrapper
包:
import 'package:jitsi_meet_wrapper/jitsi_meet_wrapper.dart';
第三步:使用JitsiMeetView
接下来,你可以在你的Flutter应用中使用JitsiMeetView
来显示Jitsi Meet视频会议。以下是一个简单的示例:
import 'package:flutter/material.dart';
import 'package:jitsi_meet_wrapper/jitsi_meet_wrapper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Jitsi Meet Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () => _launchJitsiMeet(),
child: Text('Join Meeting'),
),
),
),
);
}
void _launchJitsiMeet() async {
final options = JitsiMeetViewOptions(
room: 'YourRoomName', // 替换为你的会议室名称
url: 'https://meet.jit.si/YourRoomName', // 替换为你的Jitsi Meet URL
userInfo: JitsiMeetUserInfo(
displayName: 'YourDisplayName', // 替换为你的显示名称
email: 'your.email@example.com', // 替换为你的邮箱
),
interfaceConfig: JitsiMeetInterfaceConfig(
// 可选的界面配置
TOOLBAR_BUTTONS: [JitsiMeetToolbarButton.MICROPHONE, JitsiMeetToolbarButton.CAMERA],
),
);
await JitsiMeetWrapper.launch(options);
}
}
第四步:配置Android和iOS项目
Android
在android/app/src/main/AndroidManifest.xml
中,确保你有互联网权限:
<uses-permission android:name="android.permission.INTERNET" />
iOS
在ios/Runner/Info.plist
中,确保你有以下配置(如果需要的话,根据你的应用需求调整):
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
注意事项
- 确保你已经正确设置了Jitsi Meet服务器的URL(如示例中的
https://meet.jit.si
)。 - 你可以在
JitsiMeetViewOptions
中配置更多的选项来满足你的需求,比如是否显示工具栏按钮、是否启用视频或音频等。 - 根据你的项目需求,可能还需要处理一些权限请求,比如相机和麦克风权限。
以上代码提供了一个基本的框架,你可以在此基础上进行扩展和定制,以满足你的特定需求。