omni_jitsi_meet
Jitsi Meet Plugin for Flutter. Supports Android, iOS, and Web platforms.
"Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses Jitsi Videobridge to provide high quality, secure and scalable video conferences."
Find more information about Jitsi Meet here
Table of Contents
- Configuration
- Join A Meeting
- Listening to Meeting Events
- Closing a Meeting Programmatically
- Contributing
Configuration
IOS
- Note: Example compilable with XCode 12.2 & Flutter 1.22.4.
Podfile
Ensure in your Podfile you have an entry like below declaring platform of 12.0 or above and disable 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
Add NSCameraUsageDescription and NSMicrophoneUsageDescription to your 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
Set dependencies of build tools gradle to minimum 7.3.1:
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1' <!-- Upgrade this -->
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
Set distribution gradle wrapper to minimum 7.4.
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip <!-- Upgrade this -->
AndroidManifest.xml
Jitsi Meet's SDK AndroidManifest.xml will conflict with your project, namely
the application:label field. To counter that, go into
android/app/src/main/AndroidManifest.xml
and add the tools library
and tools:replace="android:label"
to the application tag.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="yourpackage.com"
xmlns:tools="http://schemas.android.com/tools"> <!-- Add this -->
<application tools:replace="android:label" android:name="your.application.name" android:label="My Application" android:icon="@mipmap/ic_launcher">
...
</application>
...
</manifest>
Minimum SDK Version 24
Update your minimum sdk version to 24 in android/app/build.gradle
defaultConfig {
applicationId "com.thorito.jitsi_meet_example"
minSdkVersion 24 // Required for Jitsi
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Proguard
Jitsi's SDK enables proguard, but without a proguard-rules.pro file, your release apk build will be missing the Flutter Wrapper as well as react-native code. In your Flutter project's android/app/build.gradle file, add proguard support
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
// Add below 2 lines for proguard
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
Then add a file in the same directory called proguard-rules.pro. See the example app's proguard-rules.pro file to know what to paste in.
Note
If you do not create the proguard-rules.pro file, then your app will
crash when you try to join a meeting or the meeting screen tries to open
but closes immediately. You will see one of the below errors in logcat.
## App crashes ##
java.lang.RuntimeException: Parcel android.os.Parcel@8530c57: Unmarshalling unknown type code 7536745 at offset 104
at android.os.Parcel.readValue(Parcel.java:2747)
at android.os.Parcel.readSparseArrayInternal(Parcel.java:3118)
at android.os.Parcel.readSparseArray(Parcel.java:2351)
.....
## Meeting won't open and you go to previous screen ##
W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.BV.LinearGradient.LinearGradientManager
W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.uimanager.g
W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.ARTGroupViewManager
W/unknown:ViewManagerPropertyUpdater: Could not find generated setter for class com.facebook.react.views.art.a
.....
WEB
To implement you need to include Jitsi Js library in the index.html of web section
<script src="https://meet.jit.si/external_api.js" type="application/javascript"></script>
Example:
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<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>
</html>
Note See usage example in jitsi_meet plugin
Join A Meeting
_joinMeeting() async {
try {
FeatureFlag featureFlag = FeatureFlag();
featureFlag.resolution = FeatureFlagVideoResolution.MD_RESOLUTION; // Limit video resolution to 360p
final options = JitsiMeetingOptions(
room: 'myroom',
// Required, spaces will be trimmed
serverURL: 'https://someHost.com',
// Ex: https://meet.jit.si'
subject: 'Meeting with thorito',
userDisplayName: 'My Name',
userEmail: 'myemail@email.com',
userAvatarURL: 'https://someimageurl.com/image.jpg',
// or .png
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,
},
// https://github.com/jitsi/jitsi-meet/blob/master/config.js
'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");
}
}
JitsiMeetingOptions
Field | Required | Default | Description |
---|---|---|---|
room | Yes | N/A | Unique room name that will be appended to serverURL. Valid characters: alphanumeric, dashes, and underscores. |
subject | No | $room | Meeting name displayed at the top of the meeting. If null, defaults to room name where dashes and underscores are replaced with spaces and first characters are capitalized. |
userDisplayName | No | "Fellow Jitster" | User's display name. |
userEmail | No | none | User's email address. |
audioOnly | No | false | Start meeting without video. Can be turned on in meeting. |
audioMuted | No | false | Start meeting with audio muted. Can be turned on in meeting. |
videoMuted | No | false | Start meeting with video muted. Can be turned on in meeting. |
serverURL | No | meet.jitsi.si | Specify your own hosted server. Must be a valid absolute URL of the format <scheme>://<host>[/path] , i.e. https://someHost.com. Defaults to Jitsi Meet's servers. |
userAvatarURL | N/A | none | User's avatar URL. |
token | N/A | none | JWT token used for authentication. |
featureFlag | No | see below | Object of FeatureFlag class used to enable/disable features and set video resolution of Jitsi Meet SDK. |
configOverrides | No | see below | Object of ConfigOverrides class used to enable/disable features and set video resolution of Jitsi Meet SDK. |
webOptions | No | see below | Settings in web |
FeatureFlag
Feature flag allows you to limit video resolution and enable/disable few features of Jitsi Meet SDK mentioned in the list below.
If you don't provide any flag to JitsiMeetingOptions, default values will be used.
We are using the official list of flags, taken from the Jitsi Meet repository
Flag | Default (Android) | Default (iOS) | Description |
---|---|---|---|
addPeopleEnabled |
true | true | Enable the blue button "Add people", show up when you are alone in a call. Required for flag inviteEnabled to work. |
audioFocusDisabled |
false | false | Flag indicating if the SDK should not require the audio focus. |
audioMuteButtonEnabled |
true | true | Flag indicating if the audio mute button should be displayed. |
audioOnlyButtonEnabled |
true | true | Flag indicating that the Audio only button in the overflow menu is enabled. |
calendarEnabled |
true | auto | Enable calendar integration. |
callIntegrationEnabled |
true | true | Enable call integration (CallKit on iOS, ConnectionService on Android). SEE REMARK BELOW |
carModeEnabled |
true | auto | Flag indicating if calendar integration should be enabled. |
closeCaptionsEnabled |
true | true | Enable close captions (subtitles) option in menu. |
conferenceTimerEnabled |
true | true | Enable conference timer. |
chatEnabled |
true | true | Enable chat (button and feature). |
filmstripEnabled |
true | true | Flag indicating if the filmstrip should be enabled. |
fullscreenEnabled |
true | true | Flag indicating if fullscreen (immersive) mode should be enabled. |
helpButtonEnabled |
true | true | Flag indicating if the Help button should be enabled. |
inviteEnabled |
true | true | Enable invite option in menu. |
iOSRecordingEnabled |
N/A | false | Enable recording in iOS. |
iOSScreenSharingEnabled |
N/A | false | Flag indicating if screen sharing should be enabled in iOS. |
androidScreenSharingEnabled |
true | N/A | Flag indicating if screen sharing should be enabled in android. |
speakerStatsEnabled |
true | true | Flag indicating if speaker statistics should be enabled. |
kickOutEnabled |
true | true | Enable kick-out option in video thumb of participants. |
liveStreamingEnabled |
auto | auto | Enable live-streaming option in menu. |
lobbyModeEnabled |
true | true | Flag indicating if lobby mode button should be enabled. |
meetingNameEnabled |
true | true | Display meeting name. |
meetingPasswordEnabled |
true | true | Display meeting password option in menu (if a meeting has a password set, the dialog will still show up). |
notificationsEnabled |
true | true | Flag indicating if the notifications should be enabled. |
overflowMenuEnabled |
auto | auto | Flag indicating if the audio overflow menu button should be displayed. |
pipEnabled |
auto | auto | Enable Picture-in-Picture mode. |
pipWhileScreenSharingEnabled |
false | false | Flag indicating if Picture-in-Picture button should be shown while screen sharing. |
prejoinPageEnabled |
true | true | Flag indicating if the prejoin page should be enabled. |
prejoinPageHideDisplayName |
false | false | Flag indicating if the participant name editing field should be displayed on the prejoin page. |
raiseHandEnabled |
true | true | Enable raise hand option in menu. |
recordingEnabled |
auto | N/A | Enable recording option in menu. |
replaceParticipant |
false | false | Flag indicating if the user should join the conference with the replaceParticipant functionality. |
resoulution |
N/A | N/A | Set local and (maximum) remote video resolution. Overrides server configuration. Accepted values are: LD_RESOLUTION for 180p, MD_RESOLUTION for 360p, SD_RESOLUTION for 480p(SD), HD_RESOLUTION for 720p(HD) . |
securityOptionsEnabled |
true | true | Flag indicating if the security options button should be enabled. |
serverURLChangeEnabled |
true | true | Enable server URL change. |
settingsEnabled |
true | true | Flag indicating if settings should be enabled. |
tileViewEnabled |
true | true | Enable tile view option in menu. |
toolboxAlwaysVisible |
false | false | Toolbox (buttons and menus) always visible during call (if not, a single tap displays it). |
toolboxEnabled |
true | true | Flag indicating if the toolbox should be enabled |
videoMuteButtonEnabled |
true | true | Flag indicating if the video mute button should be displayed. |
videoShareButtonEnabled |
true | true | Enable video share button. |
welcomePageEnabled |
false | false | Enable welcome page. "The welcome page lists recent meetings and calendar appointments and it's meant to be used by standalone applications." |
REMARK about Call integration Call integration on Android (known as ConnectionService) has been disabled on the official Jitsi Meet app because it creates a lot of issues. You should disable it too to avoid these issues.
JitsiMeetingResponse
Field | Type | Description |
---|---|---|
isSuccess | bool | Success indicator. |
message | String | Success message or error as a String. |
error | dynamic | Optional, only exists if isSuccess is false. The error object. |
Listening to Meeting Events
Events supported
Name | Description | Params |
---|---|---|
onOpened | Open event | none |
onClosed | Closed event | none |
onConferenceWillJoin | Meeting is loading. | url |
onConferenceJoined | User has joined meeting. | url |
onConferenceTerminated | User has exited the conference. | url, error |
onPictureInPictureWillEnter | User entered PIP mode. | message |
onPictureInPictureTerminated | User exited PIP mode. | message |
onError | Error has occurred with listening to meeting events. | error |
onAudioMutedChanged | Audio muted changed | isMuted |
onVideoMutedChanged | Video Muted changed | isMuted |
onScreenShareToggled | Screen share toggled | participantId, isSharing |
onParticipantJoined | Participant joined | email, name, role, participantId |
onParticipantLeft | Participant left | participantId |
onParticipantsInfoRetrieved | Info participants | participantsInfo, requestId |
onChatMessageReceived | Message chat received | senderOd, message, isPrivate |
onChatToggled | Chat toggled | isOpen |
Per Meeting Events
To listen to meeting events per meeting, pass in a JitsiMeetingListener
in joinMeeting. The listener will automatically be removed when an
onConferenceTerminated event is fired.
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");
}),
]),
);
Closing a Meeting Programmatically
JitsiMeet.hangUp();
Contributing
Send a pull request with as much information as possible clearly describing the issue or feature. Keep changes small and for one issue at a time.