DEV Community

Abdul Muqeet
Abdul Muqeet

Posted on • Updated on

Video Flutter SDK - Meet Hour

Meet Hour Plugin for Flutter. Supports Android and iOS platforms.

"Meet Hour is 100% free video conference solution with End to End Encrypted and many other features such as lobby mode, Video call recording, Live Streaming, Whiteboard, Control Mute Audio & video of participants etc."


Example Project is here - https://github.com/v-empower/MeetHour-Web-MobileSDKs/tree/master/Mobile/Flutter/MeetHourSDKTest

Pub dev - https://pub.dev/packages/meet_hour
meet_hour: '>=5.0.17'

MeetHour SDK Implementation - Steps

  1. SDK Examples Link - https://github.com/v-empower/MeetHour-Web-MobileSDKs
  2. API Documentation Link - https://docs.v-empower.com/docs/MeetHour-API/
   LoginType LoginObject = LoginType(
     client_id: '',
     client_secret: '',
     grant_type: 'password',
     password: '',
     username: ''
     );

    Map<String, dynamic> response - await ApiServices.login(LoginObject);
Enter fullscreen mode Exit fullscreen mode

Steps to Use Meet Hour React Flutter SDK Example

  1. Go to meethour.io and signup for Developer or Higher plan. Currently we offer 28 days free trial.
  2. Go to the dashboard and then click on developers menu.
  3. Copy your Client ID, Client Secret and Api Key. After copying, paste each copied text to the respective constant in the source code lib/constants/index.dart
  4. On Home page Click on Get Access Token
  5. Then Try Schedule a Meeting & Join Meeting.

Table of Contents

API End Points Supported

  1. To Get Access Token Endpoint : => https://docs.v-empower.com/docs/MeetHour-API/a44a7d7669f91-user-login-get-access-token
   LoginType LoginObject = LoginType(
     client_id: '',
     client_secret: '',
     grant_type: 'password',
     password: '',
     username: ''
     );

    Map<String, dynamic> response - await ApiServices.login(LoginObject);
Enter fullscreen mode Exit fullscreen mode

=> You have to pass respective values in the argument section. Hence, to get desired response.

  1. To schedule a meeting: => https://docs.v-empower.com/docs/MeetHour-API/2de4b757a6312-meeting-schedule-meeting
 ScheduleMeetingType ScheduleObject = ScheduleMeetingType(
   meetingName : 'Test',
   agenda: '',
   passcode: '',
   meetingDate: '',
   meetingTime: '',
   meetingMeridiem: '',
   durationhr: '',
   durationmin: '',
   timezone: '',
   isrecurring: '',
   recurringtype: '',
   repeat_interval: '',
   endBy: '',
   enddatetime: '',
   instructions: '',
   is_show_portal: '',
   enablepreregistration: '',
   meetingtopic: '',
   meetingagenda: '',
   options: '',
   attend: '',
   groups: '',
   hostusers: ''
 );

 Map<String, dynamic> response = await ApiServices.scheduleMeeting(token, ScheduleObject);

Enter fullscreen mode Exit fullscreen mode
  1. To Generate JWT Token Endpoint => https://docs.v-empower.com/docs/MeetHour-API/b7e3d0ab3906f-generate-jwt
   GenerateJwtType JWTObject = GenerateJwtType(
     config: '',
     contactid,
     meetingid,
     uiconfig

   Map<String, dynamic> response =  await ApiServices.generateJwt(token, JWTObject);

   );
Enter fullscreen mode Exit fullscreen mode
  1. To fetch User Details: => https://docs.v-empower.com/docs/MeetHour-API/ff9d0e37d9191-user-details
    Map<String, dynamic> response =  await ApiServices.userDetails(token);
Enter fullscreen mode Exit fullscreen mode
  1. To fetch access Token using Refresh Token: => https://docs.v-empower.com/docs/MeetHour-API/d851be1af9804-get-access-token-using-refresh-token
   RefreshTokenType RefreshTokenObject = RefreshTokenType(
            client_id: '';
            client_secret: '';
            grant_type: '';
            refresh_token: '';
   )
   Map<String, dynamic> response =  await ApiServices.getRefreshToken(token, RefreshTokenObject);
Enter fullscreen mode Exit fullscreen mode
  1. To add a contact in Meet Hour database: => https://docs.v-empower.com/docs/MeetHour-API/bd1e416413e8c-add-contact
    AddContactType AddContactObject = AddContactType(
         countrycode: '',
         email: '',
         firstname: '',
         image: '',
         is_show_portal: '',
         lastname: '',
         phone: ''
    );
   Map<String, dynamic> response =  await ApiServices.addContact(token AddContactObject);
Enter fullscreen mode Exit fullscreen mode
  1. To get Timezones of various countries: => https://docs.v-empower.com/docs/MeetHour-API/c688c29bce9b9-timezone-list
    Map<String, dynamic> response =  await ApiServices.timezone(token)
Enter fullscreen mode Exit fullscreen mode
  1. To get list of all the contacts in your Meet Hour account: => https://api.meethour.io/api/{version}/customer/contacts
   ContactsType AddContactObject = ContactsType(
     exclude_hosts: 1100,
     limit: 10,
     page: 1
   );

   Map<String, dynamic> response =  await ApiServices.contactsList(token, AddContactObject);

Enter fullscreen mode Exit fullscreen mode
  1. To make changes in the existing contact details: => https://docs.v-empower.com/docs/MeetHour-API/28cae9187d215-edit-contact

 EditContactType EditContactObject =  EditContactType(
   contactid: 1150;
   countrycode: '',
   email: '',
   firstname: '',
   image: '',
   is_show_portal: '',
   lastname: '',
   phone: ''
 );

Map<String, dynamic> response = ApiServices.editContact(token, EditContactObject);

Enter fullscreen mode Exit fullscreen mode
  1. To get Upcoming Meetings: => https://docs.v-empower.com/docs/MeetHour-API/31df88388416d-upcoming-meetings
UpcomingMeetingType UpcomingMeetingObject =  UpcomingMeetingType(
   page: 10,
   limit: 10,
   show_all: 1
 );

  Map<String, dynamic> response = ApiServices.upcomingMeetings(token, UpcomingMeetingObject
Enter fullscreen mode Exit fullscreen mode
  1. To archive a meeting: => https://docs.v-empower.com/docs/MeetHour-API/1dd64523cc6bf-archive-meeting
ArchiveMeetingType ArchiveMeetingObject =  ArchiveMeetingType(
   id: 10
 );

  Map<String, dynamic> response = ApiServices.archiveMeeting(token, ArchiveMeetingObject);
Enter fullscreen mode Exit fullscreen mode
  1. To get the details of a missed meeting: => https://docs.v-empower.com/docs/MeetHour-API/92998e2dda102-missed-meetings
MissedMeetingType MissedMeetingObject =  MissedMeetingType(
   limit: 10,
   page: number,
   show_all: 1
 );

  Map<String, dynamic> response = ApiServices.missedMeetings(token, MissedMeetingObject);
Enter fullscreen mode Exit fullscreen mode
  1. To get completed meetings: => https://docs.v-empower.com/docs/MeetHour-API/aa9ef6a678250-completed-meetings
 CompletedMeetingType CompletedMeetingObject =  CompletedMeetingType(
   limit: 10,
   page: number,
   show_all: 1
 );

  Map<String, dynamic> response = ApiServices.completedMeetings(token, CompletedMeetingObject);
Enter fullscreen mode Exit fullscreen mode
  1. To edit an existing meeting: => https://docs.v-empower.com/docs/MeetHour-API/5dedde36380b4-meeting-edit-meeting

 EditMeetingType EdiMeetingObject = EditMeetingType(
   meeting_id: ''
   meetingName : 'Test',
   agenda: '',
   passcode: '',
   meetingDate: '',
   meetingTime: '',
   meetingMeridiem: '',
   durationhr: '',
   durationmin: '',
   timezone: '',
   isrecurring: '',
   recurringtype: '',
   repeat_interval: '',
   endBy: '',
   enddatetime: '',
   instructions: '',
   is_show_portal: '',
   enablepreregistration: '',
   meetingtopic: '',
   meetingagenda: '',
   old_attend: []
   options: '',
   attend: '',
   groups: '',
   hostusers: ''
 );
 Map<String, dynamic> response = await ApiServices.editMeeting(token, EdiMeetingObject);


Enter fullscreen mode Exit fullscreen mode
  1. To view a meeting: => https://docs.v-empower.com/docs/MeetHour-API/7e9a0a1e0da7f-meeting-view-meeting

 ViewMeetingType ViewMeetingObject =  ViewMeetingType(
   meeting_id: ''
 );

  Map<String, dynamic> response = ApiServices.viewMeeting(token, ViewMeetingObject);
Enter fullscreen mode Exit fullscreen mode
  1. To get all the recordings list: => https://docs.v-empower.com/docs/MeetHour-API/ce7c4fd8cae7e-recording-list
 RecordingsListType RecordingMeetingObject =  RecordingsListType(
   filter_by: '';
   limit: '';
   page: '';
 );

  Map<String, dynamic> response = ApiServices.recordingsList(token, RecordingMeetingObject);
Enter fullscreen mode Exit fullscreen mode

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 11.0 or above and disable BITCODE.
platform :ios, '12.1'

...

post_install do |installer|
  installer.pods_project.targets.each do |target|
  flutter_additional_ios_build_settings(target)
      target.build_configurations.each do |config|
        config.build_settings['ENABLE_BITCODE'] = 'NO'
        config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64"
         end
     end
   end
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

Android

Gradle
Set dependencies of build tools gradle to minimum 3.6.3:

dependencies {
    classpath 'com.android.tools.build:gradle:7.1.0' <!-- Upgrade this -->
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}

Enter fullscreen mode Exit fullscreen mode

Set distribution gradle wrapper to minimum 5.6.4.

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip <!-- Upgrade this -->
Enter fullscreen mode Exit fullscreen mode

AndroidManifest.xml
Meet Hour'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>
Enter fullscreen mode Exit fullscreen mode

Minimum SDK Version 23
Update your minimum sdk version to 23 in android/app/build.gradle

defaultConfig {
    applicationId "go.meethour.io.flutter.sdk_example"
    minSdkVersion 23 //Required for MeetHour
    targetSdkVersion 33
    versionCode flutterVersionCode.toInteger()
    versionName flutterVersionName
}
Enter fullscreen mode Exit fullscreen mode

Proguard
MeetHour'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 3 lines for proguard
        minifyEnabled false
        useProguard true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

Join A Meeting

_joinMeeting() async {
    try {
      FeatureFlag featureFlag = FeatureFlag();
      featureFlag.welcomePageEnabled = false;
      featureFlag.resolution = FeatureFlagVideoResolution.MD_RESOLUTION; // Limit video resolution to 360p

      var options = MeetHourMeetingOptions()
        ..room = "TestRoom" // Required, spaces will be trimmed
        ..serverURL = "https://meethour.io"
        ..subject = "Meeting with John"
        ..userDisplayName = "John Delise"
        ..token = "" // JWT Token for User authentication as Moderator.
        ..pcode = "" // To Pass meeting password dynamically.
        ..userEmail = "john@gmail.com"
        ..userAvatarURL = "https://someimageurl.com/image.jpg" // or .png
        ..audioOnly = true
        ..audioMuted = true
        ..videoMuted = true
        ..prejoinPageEnabled = true // Make it false to Skip PrejoinPage
        ..disableInviteFunctions = true // To disable invite functions in Mobile SDK.
        ..featureFlag = featureFlag;

      await MeetHour.joinMeeting(options);
    } catch (error) {
      debugPrint("error: $error");
    }
  }
Enter fullscreen mode Exit fullscreen mode

MeetHourMeetingOptions

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 "Guest" 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 meethour.io Specify your own hosted server. Must be a valid absolute URL of the format <scheme>://<host>[/path], i.e. https://someHost.com. Defaults to Meet Hour's servers.
userAvatarURL N/A none User's avatar URL.
token N/A none JWT token used for authentication.
pcode N/A none pcode used for passing meeting password dynamically.
prejoinPageEnabled N/A false Make it false to Skip PrejoinPage.
disableInviteFunctions N/A false To disable invite functions in Mobile SDK.
featureFlag No see below Object of FeatureFlag class used to enable/disable features and set video resolution of Meet Hour SDK.

FeatureFlag

Feature flag allows you to limit video resolution and enable/disable few features of Meet Hour SDK mentioned in the list below.

If you don't provide any flag to MeetHourMeetingOptions, default values will be used.

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.
calendarEnabled true auto Enable calendar integration.
callIntegrationEnabled true true Enable call integration (CallKit on iOS, ConnectionService on Android). SEE REMARK BELOW
closeCaptionsEnabled true true Enable close captions (subtitles) option in menu.
conferenceTimerEnabled true true Enable conference timer.
chatEnabled true true Enable chat (button and feature).
inviteEnabled true true Enable invite option in menu.
iOSRecordingEnabled N/A false Enable recording in iOS.
kickOutEnabled true true Enable kick-out option in video thumb of participants.
liveStreamingEnabled auto auto Enable live-streaming option in menu.
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).
pipEnabled auto auto Enable Picture-in-Picture mode.
raiseHandEnabled true true Enable raise hand option in menu.
recordingEnabled auto N/A Enable recording option in menu.
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) .
serverURLChangeEnabled true true Enable server URL change.
tileViewEnabled true true Enable tile view option in menu.
toolboxAlwaysVisible true true Toolbox (buttons and menus) always visible during call (if not, a single tap displays it).
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."

MeetHourMeetingResponse

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
onConferenceWillJoin Meeting is loading.
onConferenceJoined User has joined meeting.
onConferenceTerminated User has exited the conference.
onPictureInPictureWillEnter User entered PIP mode.
onPictureInPictureTerminated User exited PIP mode.
onError Error has occurred with listening to meeting events.

Per Meeting Events

To listen to meeting events per meeting, pass in a MeetHourMeetingListener
in joinMeeting. The listener will automatically be removed when an

onConferenceTerminated event is fired.

await MeetHour.joinMeeting(options,
  listener: MeetHourMeetingListener(onConferenceWillJoin: ({message}) {
    debugPrint("${options.room} will join with message: $message");
  }, onConferenceJoined: ({message}) {
    debugPrint("${options.room} joined with message: $message");
  }, onConferenceTerminated: ({message}) {
    debugPrint("${options.room} terminated with message: $message");
  }, onPictureInPictureWillEnter: ({message}) {
    debugPrint("${options.room} entered PIP mode with message: $message");
  }, onPictureInPictureTerminated: ({message}) {
    debugPrint("${options.room} exited PIP mode with message: $message");
  }));
Enter fullscreen mode Exit fullscreen mode

Global Meeting Events

To listen to global meeting events, simply add a MeetHourListener with

MeetHour.addListener(myListener). You can remove listeners using

MeetHour.removeListener(listener) or MeetHour.removeAllListeners().

@override
void initState() {
  super.initState();
  MeetHour.addListener(MeetHourMeetingListener(
    onConferenceWillJoin: _onConferenceWillJoin,
    onConferenceJoined: _onConferenceJoined,
    onConferenceTerminated: _onConferenceTerminated,
    onPictureInPictureWillEnter: _onPictureInPictureWillEnter,
    onPictureInPictureTerminated: _onPictureInPictureTerminated,
    onError: _onError));
}

@override
void dispose() {
  super.dispose();
  MeetHour.removeAllListeners();
}

_onConferenceWillJoin({message}) {
  debugPrint("_onConferenceWillJoin broadcasted");
}

_onConferenceJoined({message}) {
  debugPrint("_onConferenceJoined broadcasted");
}

_onConferenceTerminated({message}) {
  debugPrint("_onConferenceTerminated broadcasted");
}

_onPictureInPictureWillEnter({message}) {
debugPrint("_onPictureInPictureWillEnter broadcasted with message: $message");
}

_onPictureInPictureTerminated({message}) {
debugPrint("_onPictureInPictureTerminated broadcasted with message: $message");
}

_onError(error) {
  debugPrint("_onError broadcasted");
}
Enter fullscreen mode Exit fullscreen mode

Closing a Meeting Programmatically

MeetHour.closeMeeting();
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)