Flutter视频会议插件jitsi_meet_wrapper的使用

发布于 1周前 作者 eggper 来自 Flutter

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”到您的应用中。具体步骤如下:

  1. 添加一个新的“Broadcast Upload Extension”目标。
  2. 将文件从 jitsi_meet_wrapper/example/ios/Broadcast Extension/ 复制到新创建的目标文件夹中。
  3. 设置扩展目标的部署版本为14或更高。
  4. 创建一个App Group并将“Runner”目标和扩展目标添加到该组中。
  5. SampleHandler.swift 中替换 appGroupIdentifier 为您创建的App Group名称。
  6. 在“Runner”的 Info.plist 中添加键值对 RTCAppGroupIdentifierRTCScreenSharingExtension
  7. 启用Dart代码中的功能标志 ios.screensharing.enabled
  8. 确保“Runner”的 Info.plist 中设置了 UIBackgroundModesvoip

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

1 回复

更多关于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中配置更多的选项来满足你的需求,比如是否显示工具栏按钮、是否启用视频或音频等。
  • 根据你的项目需求,可能还需要处理一些权限请求,比如相机和麦克风权限。

以上代码提供了一个基本的框架,你可以在此基础上进行扩展和定制,以满足你的特定需求。

回到顶部