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
- SDK Examples Link - https://github.com/v-empower/MeetHour-Web-MobileSDKs
- 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);
Steps to Use Meet Hour React Flutter SDK Example
- Go to meethour.io and signup for Developer or Higher plan. Currently we offer 28 days free trial.
- Go to the dashboard and then click on developers menu.
- 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
- On Home page Click on Get Access Token
- Then Try Schedule a Meeting & Join Meeting.
Table of Contents
API End Points Supported
- 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);
=> You have to pass respective values in the argument section. Hence, to get desired response.
- 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);
- 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);
);
- To fetch User Details: => https://docs.v-empower.com/docs/MeetHour-API/ff9d0e37d9191-user-details
Map<String, dynamic> response = await ApiServices.userDetails(token);
- 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);
- 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);
- 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)
- 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);
- 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);
- 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
- 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);
- 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);
- 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);
- 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);
- 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);
- 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);
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
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 3.6.3:
dependencies {
classpath 'com.android.tools.build:gradle:7.1.0' <!-- Upgrade this -->
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
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 -->
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>
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
}
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'
}
}
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");
}
}
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");
}));
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");
}
Closing a Meeting Programmatically
MeetHour.closeMeeting();
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)