Code With Bisky

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 Authentication

Description:

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.