Mastering Jitsi Integration in Flutter: Create Your Ultimate Self-Hosted Video Conferencing [29]
Key Topics covered:
- Setting up a Self-Hosted Jitsi Server
- Integrating Jitsi Meet SDK with Flutter
- Creating a Customizable Video Conferencing App
- Room Management
Resources:
Jitsi Flutter Sdk Self-Hosting Guide - Docker Jitsi AuthenticationDescription:
In this comprehensive tutorial, we'll guide you through the process of integrating Jitsi, the powerful open-source video conferencing solution, with your Flutter application and setting up self-hosting for complete control and customization. Whether you're a developer looking to add video conferencing features to your app or a business wanting to maintain data privacy, this video has you covered.
1. Deploy Jitsi Docker Image:
We need to deploy the latest Jisti docker image on our server. Let's run below commands
mkdir jitsi
cd jitsi/
git clone https://github.com/jitsi/docker-jitsi-meet && cd docker-jitsi-meet
make
cp env.example .env
nano .env
./gen-passwords.sh
mkdir -p ~/.jitsi-meet-cfg/{web,transcripts,prosody/config,prosody/prosody-plugins-custom,jicofo,jvb,jigasi,jibri}
docker-compose up -d
sudo ufw allow 10000/udp
sudo ufw allow 3478/udp
sudo ufw allow 5349/tcp
sudo ufw allow 8443/tcp
Try to access https://domain:8443
We need to add reverse proxy of our subdomain that we are going to use for our video call. video call will not work right now because we need to add ssl certificate. We are using let's encrypt
Add sub domain meet.codewithbisky.com server block
Run below command and add the server block in that file nano /etc/nginx/sites-available/meet.codewithbisky.com Press CTL+X and then type y to save and press enter
server {
server_name meet.codewithbisky.com;
index index.html index.htm;
access_log /var/log/nginx/meet-codewithbisky.log;
error_log /var/log/nginx/meet-codewithbisky-error.log error;
client_max_body_size 100M;
location /xmpp-websocket {
proxy_pass https://localhost:8443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location / {
proxy_pass https://localhost:8443;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Create the new subdomain on you DNS Management portal first and run below commands to restart nginx and generate certificate
sudo ln -s /etc/nginx/sites-available/meet.codewithbisky.com /etc/nginx/sites-enabled/
sudo nginx -t
systemctl restart nginx
sudo certbot --nginx -d meet.codewithbisky.com
Add the following configs in your .env file
# uncomment below config if you have issue of 8080 port is in use
#JVB_COLIBRI_PORT=8089
PUBLIC_URL=https://meet.codewithbisky.com
DOCKER_HOST_ADDRESS=192.*.*.*
Let's access https://meet.codewithbisky.com
2. JitsiProvider:
Create a JitsiProvider to handle calls
Add the following dependencies in your pubspec.yaml
dependencies:
.........
jitsi_meet_flutter_sdk: ^0.1.4
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:jitsi_meet_flutter_sdk/jitsi_meet_flutter_sdk.dart';
final jitsiProvider =
Provider((ref) => JitsiProvider(ref));
class JitsiProvider {
final Ref _ref;
JitsiProvider(this._ref);
void createMeeting({
required String roomName,
required bool isAudioMuted,
required bool isVideoMuted,
String username = '',
String email = '',
bool preJoined = true,
bool isVideo = true,
bool isGroup = true,
}) async {
try {
Map<String, Object> featureFlag = {};
featureFlag['welcomepage.enabled'] = false;
featureFlag['prejoinpage.enabled'] = preJoined;
featureFlag['add-people.enabled'] = isGroup;
var options = JitsiMeetConferenceOptions(
room: roomName,
serverURL: 'https://meet.codewithbisky.com',
configOverrides: {
"startWithAudioMuted": isAudioMuted,
"startWithVideoMuted": isVideoMuted,
"subject" : "Jitsi with Flutter",
},
userInfo: JitsiMeetUserInfo(
displayName: username,
email: email
),
featureFlags: featureFlag,
);
var jitsiMeet = JitsiMeet();
await jitsiMeet.join( options);
} catch (error) {
print("error: $error");
}
}
}
Dashboard.dart:
We need to add a listener to listen to a call on our dashboard
import 'package:riverpod_test/core/providers/JitsiProvider.dart';
// declare this variable
JitsiProvider? _jitsiProvider ;
// initialize it in the build method and call the listener method
_jitsiProvider = ref.read(jitsiProvider);
_listenIncomingCalls();
// create this method
void _listenIncomingCalls() {
LocalStorageService.deleteKey(LocalStorageService.callInProgress);
ref.listen<RealtimeNotifier?>(
realtimeNotifierProvider.select((value) => value.asData?.value),
(_, next) async {
if (next?.document.$collectionId == Strings.collectionRoomId) {
final roomState = RoomAppwrite.fromMap(next!.document.data);
String username = await LocalStorageService.getString(LocalStorageService.userId) ?? "";
if(username == roomState.calleeUserId && next.type != RealtimeNotifier.delete){
final inProgress = await LocalStorageService.getString(LocalStorageService.callInProgress);
if(inProgress !=null){
// todo update room with state repository
return;
}
final myProfile = await ref.read(profileRepositoryProvider).getProfile(username);
if(myProfile != null){
_jitsiProvider?.createMeeting(
roomName: roomState.roomId ??"",
isAudioMuted: true,
isVideoMuted: true,
username: myProfile.name ?? "",
email: myProfile.email ?? ""
);
}
}
}
});
}
MessageViewModel.dart:
In our MessageNotifier, we need to add createRoom method
import 'package:riverpod_test/model/appwrite/RoomAppwrite.dart';
import 'package:riverpod_test/core/providers/RoomRepositoryProvider.dart';
import 'package:objectid/objectid.dart';
// declare _roomRepositoryProvider
RoomRepositoryProvider get _roomRepositoryProvider => ref.read(roomRepositoryProvider);
Future<String> createRoom(String callType,bool groupCall) async{
final id = ObjectId().hexString;
RoomAppwrite room = RoomAppwrite(
roomId: id,
callType: callType,
groupCall: groupCall,
callerUserId: state.myUserId,
calleeUserId: state.friendUserId);
await _roomRepositoryProvider.createNewRoom(room);
return id;
}
MessageScreen.dart:
In our MessageScreen, we need to call createRoom method from the MessageNotifier after clicking voice or video call icon
import 'package:riverpod_test/core/providers/JitsiProvider.dart';
// declare this variable
JitsiProvider? _jitsiProvider ;
// initialize it in the build method
_jitsiProvider = ref.read(jitsiProvider);
// create below method
Future<void> videoOrVoiceCall(bool isVideo) async {
final myProfile = await ref.read(profileRepositoryProvider).getProfile(myUserId);
if(myProfile != null){
String? roomId = await messageNotifier?.createRoom(isVideo ? 'video':'voice', false);
if(roomId != null){
_jitsiProvider?.createMeeting(
roomName: roomId,
isAudioMuted: true,
isVideoMuted: true,
isVideo: isVideo,
preJoined: false,
isGroup: false,
username: myProfile.name??"",
email: myProfile.email??"",
);
}
}
}
// for voice call the parameter would be false
await videoOrVoiceCall(false);
// for video call the parameter would be true
await videoOrVoiceCall(true);
Conclusion:
In this tutorial, we've walked you through the process of integrating Jitsi with your Flutter application and setting up self-hosting for a powerful video conferencing solution. With the skills you've gained, you can now create your own customizable video conferencing app, tailored to your specific needs. Whether it's for business meetings, webinars, or online classrooms, you have the flexibility and control to host secure and efficient video conferences. We hope this tutorial has been helpful on your journey to harnessing the full potential of Jitsi and Flutter for your app development needs.