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

1 回复

更多关于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>
回到顶部