Build secure, scalable, and feature-rich video conferencing applications with our comprehensive meeting API. From P2P technology to AI-powered scheduling, from HD video calls to seamless calendar integration.
allgram Meet provides enterprise-level security, GDPR compliance, and seamless integration with popular platforms. Perfect for businesses, developers, and organizations requiring professional video conferencing solutions.
E2EE, FIPS 140-2, GDPR compliant
Jitsi Meet SDK, WebRTC, instant delivery
Smart calendar, time zones, automation
Build robust video conferencing applications with Node.js and Express.js. Our comprehensive API provides real-time video calls, Jitsi Meet SDK integration, and enterprise-grade security features. Perfect for building scalable backend services and real-time meeting applications.
// allgram Meet Node.js Integration Example
const express = require('express');
const WebSocket = require('ws');
const crypto = require('crypto');
const axios = require('axios');
class allgramMeetClient {
constructor(apiKey, baseUrl = 'https://api.allgram.best') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.ws = null;
this.meetings = new Map();
this.jitsiConfig = {
domain: 'meet.allgram.best',
configOverwrite: {
startWithVideoMuted: true,
startAudioOnly: false,
videoQuality: {
persist: true,
codecPreferenceOrder: ['VP8', 'VP9', 'H264']
}
}
};
}
// Initialize WebSocket connection for real-time updates
async connectWebSocket() {
try {
this.ws = new WebSocket(`wss://api.allgram.best/ws/meetings/`);
this.ws.on('open', () => {
console.log('✅ WebSocket connected for meetings');
// Authenticate with API key
this.ws.send(JSON.stringify({
type: 'auth',
api_key: this.apiKey
}));
});
this.ws.on('message', (data) => {
this.handleWebSocketMessage(JSON.parse(data));
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.reconnect();
});
} catch (error) {
console.error('Failed to connect WebSocket:', error);
}
}
// Create a new meeting with calendar integration
async createMeeting(name, description, participants, startTime, endTime, isP2P = false) {
try {
const response = await axios.post(`${this.baseUrl}/meetings/create`, {
meeting_name: name,
meeting_description: description,
participants: participants,
start_datetime: startTime,
end_datetime: endTime,
is_p2p: isP2P,
is_public: false,
max_participants: 50
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
const meeting = response.data;
this.meetings.set(meeting.meeting_id, meeting);
console.log(`✅ Meeting created: ${meeting.meeting_name}`);
return meeting;
}
} catch (error) {
console.error('Failed to create meeting:', error.response?.data || error.message);
throw error;
}
}
// Generate Jitsi Meet room configuration
generateJitsiConfig(meetingId, isP2P = false) {
const roomName = `meet_${meetingId}_${Date.now()}`;
const config = {
...this.jitsiConfig,
roomName: roomName,
configOverwrite: {
...this.jitsiConfig.configOverwrite,
p2p: {
enabled: isP2P,
preferH264: true,
disableH264: false
}
}
};
return config;
}
// Handle WebSocket messages for meetings
handleWebSocketMessage(message) {
if (message.content.action) {
this.handleSystemEvent(message);
} else if (message.content.meeting_data) {
this.handleMeetingUpdate(message);
}
}
// Handle system events
handleSystemEvent(message) {
switch (message.content.action) {
case 'participant_joined':
console.log(`👥 Participant joined meeting: ${message.participant}`);
break;
case 'meeting_started':
console.log(`🎬 Meeting started: ${message.meeting_id}`);
break;
case 'meeting_ended':
console.log(`🏁 Meeting ended: ${message.meeting_id}`);
this.updateMeetingStatus(message.meeting_id, 'ended');
break;
}
}
// Handle meeting updates
handleMeetingUpdate(message) {
const meetingId = message.meeting_id;
const meetingData = message.content.meeting_data;
if (this.meetings.has(meetingId)) {
const meeting = this.meetings.get(meetingId);
Object.assign(meeting, meetingData);
this.meetings.set(meetingId, meeting);
console.log(`📊 Meeting updated: ${meetingId}`);
}
}
// Update meeting status
updateMeetingStatus(meetingId, status) {
if (this.meetings.has(meetingId)) {
const meeting = this.meetings.get(meetingId);
meeting.status = status;
meeting.end_time = new Date().toISOString();
this.meetings.set(meetingId, meeting);
}
}
// Get meeting analytics
async getMeetingAnalytics(meetingId) {
try {
const response = await axios.get(`${this.baseUrl}/meetings/${meetingId}/analytics`, {
headers: {
'Authorization': `Bearer ${this.apiKey}`
}
});
if (response.status === 200) {
return response.data;
}
} catch (error) {
console.error('Failed to get meeting analytics:', error.response?.data || error.message);
throw error;
}
}
// Reconnection logic
reconnect() {
setTimeout(() => {
console.log('🔄 Attempting to reconnect...');
this.connectWebSocket();
}, 5000);
}
}
// Usage example
const meetClient = new allgramMeetClient('your-api-key-here');
// Initialize connection
meetClient.connectWebSocket();
// Create a P2P meeting
const startTime = new Date(Date.now() + 30 * 60 * 1000); // 30 minutes from now
const endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // 1 hour duration
meetClient.createMeeting(
'Team Standup',
'Daily team synchronization meeting',
['user1', 'user2', 'user3'],
startTime.toISOString(),
endTime.toISOString(),
true // Enable P2P
).then(meeting => {
console.log('Meeting created successfully:', meeting);
// Generate Jitsi configuration
const jitsiConfig = meetClient.generateJitsiConfig(meeting.meeting_id, true);
console.log('Jitsi configuration:', jitsiConfig);
}).catch(error => {
console.error('Failed to create meeting:', error);
});
Our Node.js integration provides enterprise-grade video conferencing capabilities with minimal setup. Built on proven technologies like Express.js and Jitsi Meet SDK, it offers exceptional performance and reliability for production applications.
Build native iOS video conferencing applications with Swift and SwiftUI. Our iOS SDK provides seamless integration with UIKit and SwiftUI, real-time video calls, and secure P2P implementation. Perfect for creating professional iOS meeting applications with modern design patterns.
// allgram Meet Swift iOS Integration Example
import SwiftUI
import Combine
import CryptoKit
import JitsiMeetSDK
// MARK: - Meeting Models
struct Meeting: Codable, Identifiable {
let id: String
let name: String
let description: String
let isP2P: Bool
let participants: [String]
let startTime: Date
let endTime: Date
let maxParticipants: Int
let status: MeetingStatus
enum CodingKeys: String, CodingKey {
case id = "meeting_id"
case name = "meeting_name"
case description = "meeting_description"
case isP2P = "is_p2p"
case participants
case startTime = "start_datetime"
case endTime = "end_datetime"
case maxParticipants = "max_participants"
case status
}
}
enum MeetingStatus: String, Codable {
case scheduled = "scheduled"
case active = "active"
case ended = "ended"
case cancelled = "cancelled"
}
struct Participant: Codable, Identifiable {
let id: String
let username: String
let displayName: String
let avatar: String?
let isHost: Bool
let joinTime: Date?
enum CodingKeys: String, CodingKey {
case id = "user_id"
case username
case displayName = "display_name"
case avatar
case isHost = "is_host"
case joinTime = "join_time"
}
}
// MARK: - Meet Service
class allgramMeetService: ObservableObject {
@Published var meetings: [Meeting] = []
@Published var currentMeeting: Meeting?
@Published var participants: [Participant] = []
@Published var isConnected = false
@Published var meetingStatus: MeetingStatus = .scheduled
private var webSocket: URLSessionWebSocketTask?
private var cancellables = Set<AnyCancellable>()
private let baseURL = "https://api.allgram.best"
private let apiKey: String
private var jitsiMeetView: JitsiMeetView?
init(apiKey: String) {
self.apiKey = apiKey
setupWebSocket()
}
// MARK: - WebSocket Setup
private func setupWebSocket() {
guard let url = URL(string: "wss://api.allgram.best/ws/meetings/") else { return }
let session = URLSession(configuration: .default)
webSocket = session.webSocketTask(with: url)
webSocket?.resume()
// Authenticate
let authMessage = WebSocketMessage(type: "auth", apiKey: apiKey)
sendWebSocketMessage(authMessage)
// Start receiving messages
receiveMessage()
}
// MARK: - Meeting Management
func createMeeting(name: String, description: String, participants: [String], startTime: Date, endTime: Date, isP2P: Bool = false) async throws -> Meeting {
let url = URL(string: "\(baseURL)/meetings/create")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let meetingData = [
"meeting_name": name,
"meeting_description": description,
"participants": participants,
"start_datetime": ISO8601DateFormatter().string(from: startTime),
"end_datetime": ISO8601DateFormatter().string(from: endTime),
"is_p2p": isP2P,
"is_public": false,
"max_participants": 50
]
request.httpBody = try JSONSerialization.data(withJSONObject: meetingData)
let (data, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw MeetError.serverError
}
let meeting = try JSONDecoder().decode(Meeting.self, from: data)
DispatchQueue.main.async {
self.meetings.append(meeting)
}
return meeting
}
// MARK: - Jitsi Meet Integration
func joinMeeting(_ meeting: Meeting) {
let options = JitsiMeetConferenceOptions.fromBuilder { builder in
builder.room = "meet_\(meeting.id)_\(Date().timeIntervalSince1970)"
builder.serverURL = URL(string: "https://meet.allgram.best")
// P2P configuration
if meeting.isP2P {
builder.p2pEnabled = true
builder.preferH264 = true
}
// Video quality settings
builder.videoMuted = false
builder.audioMuted = false
// User information
builder.userInfo = JitsiMeetUserInfo()
builder.userInfo?.displayName = "iOS User"
builder.userInfo?.email = "user@example.com"
}
// Present Jitsi Meet view
DispatchQueue.main.async {
self.meetingStatus = .active
self.currentMeeting = meeting
self.presentJitsiMeet(options: options)
}
}
private func presentJitsiMeet(options: JitsiMeetConferenceOptions) {
// This would typically be presented modally or in a navigation stack
// Implementation depends on your app's navigation structure
print("🎬 Joining meeting with options: \(options)")
}
// MARK: - WebSocket Communication
private func sendWebSocketMessage(_ message: WebSocketMessage) {
guard let data = try? JSONEncoder().encode(message),
let jsonString = String(data: data, encoding: .utf8) else { return }
let wsMessage = URLSessionWebSocketTask.Message.string(jsonString)
webSocket?.send(wsMessage) { error in
if let error = error {
print("❌ Failed to send WebSocket message: \(error)")
}
}
}
private func receiveMessage() {
webSocket?.receive { [weak self] result in
switch result {
case .success(let message):
self?.handleWebSocketMessage(message)
self?.receiveMessage() // Continue receiving
case .failure(let error):
print("❌ WebSocket receive error: \(error)")
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
self?.reconnect()
}
}
}
}
private func handleWebSocketMessage(_ message: URLSessionWebSocketTask.Message) {
switch message {
case .string(let text):
guard let data = text.data(using: .utf8),
let wsMessage = try? JSONDecoder().decode(WebSocketMessage.self, from: data) else { return }
DispatchQueue.main.async {
self.processWebSocketMessage(wsMessage)
}
default:
break
}
}
private func processWebSocketMessage(_ message: WebSocketMessage) {
if let action = message.action {
handleSystemEvent(action, message: message)
} else if message.content?.meetingData != nil {
handleMeetingUpdate(message)
}
}
private func handleSystemEvent(_ action: String, message: WebSocketMessage) {
switch action {
case "participant_joined":
print("👥 Participant joined meeting: \(message.participant ?? "Unknown")")
if let participant = message.participant {
addParticipant(participant)
}
case "meeting_started":
print("🎬 Meeting started: \(message.meetingId ?? "Unknown")")
DispatchQueue.main.async {
self.meetingStatus = .active
}
case "meeting_ended":
print("🏁 Meeting ended: \(message.meetingId ?? "Unknown")")
DispatchQueue.main.async {
self.meetingStatus = .ended
self.currentMeeting = nil
}
default:
break
}
}
private func handleMeetingUpdate(_ message: WebSocketMessage) {
guard let meetingId = message.meetingId,
let meetingData = message.content?.meetingData else { return }
// Update meeting data
if let index = meetings.firstIndex(where: { $0.id == meetingId }) {
DispatchQueue.main.async {
// Update meeting with new data
// Implementation depends on your data structure
print("📊 Meeting updated: \(meetingId)")
}
}
}
private func addParticipant(_ username: String) {
let participant = Participant(
id: UUID().uuidString,
username: username,
displayName: username,
avatar: nil,
isHost: false,
joinTime: Date()
)
DispatchQueue.main.async {
self.participants.append(participant)
}
}
// MARK: - Utility Methods
private func reconnect() {
print("🔄 Attempting to reconnect...")
setupWebSocket()
}
}
// MARK: - WebSocket Message Model
struct WebSocketMessage: Codable {
let type: String?
let apiKey: String?
let meetingId: String?
let participant: String?
let content: MessageContent?
let action: String?
enum CodingKeys: String, CodingKey {
case type
case apiKey = "api_key"
case meetingId = "meeting_id"
case participant
case content
case action
}
}
struct MessageContent: Codable {
let meetingData: [String: Any]?
enum CodingKeys: String, CodingKey {
case meetingData = "meeting_data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
meetingData = try container.decodeIfPresent([String: Any].self, forKey: .meetingData)
}
}
// MARK: - Error Handling
enum MeetError: Error, LocalizedError {
case serverError
case networkError
case authenticationError
var errorDescription: String? {
switch self {
case .serverError:
return "Server error occurred"
case .networkError:
return "Network connection failed"
case .authenticationError:
return "Authentication failed"
}
}
}
// MARK: - SwiftUI Views
struct MeetingListView: View {
@StateObject private var meetService = allgramMeetService(apiKey: "your-api-key")
var body: some View {
NavigationView {
List(meetService.meetings) { meeting in
NavigationLink(destination: MeetingDetailView(meeting: meeting, meetService: meetService)) {
MeetingRowView(meeting: meeting)
}
}
.navigationTitle("Meetings")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("New Meeting") {
// Show new meeting creation
}
}
}
}
}
}
struct MeetingRowView: View {
let meeting: Meeting
var body: some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(meeting.name)
.font(.headline)
Spacer()
if meeting.isP2P {
Image(systemName: "network")
.foregroundColor(.green)
}
}
Text(meeting.description)
.font(.subheadline)
.foregroundColor(.secondary)
Text("\(meeting.participants.count) participants")
.font(.caption)
.foregroundColor(.secondary)
Text(meeting.status.rawValue.capitalized)
.font(.caption)
.foregroundColor(.blue)
}
.padding(.vertical, 4)
}
}
Our Swift SDK leverages the latest iOS technologies including SwiftUI, Combine, and async/await. Built with modern iOS development patterns, it provides a seamless developer experience while maintaining high performance and security standards.
Build powerful Android video conferencing applications with Kotlin and Jetpack Compose. Our Android SDK provides seamless integration with modern Android development tools, real-time video calls, and P2P video capabilities. Perfect for creating professional Android meeting applications.
// allgram Meet Kotlin Android Integration Example
package com.allgram.meet
import android.util.Log
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import org.webrtc.*
import java.security.MessageDigest
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
// MARK: - Data Models
data class Meeting(
val meetingId: String,
val meetingName: String,
val meetingDescription: String,
val isP2P: Boolean,
val participants: List<String>,
val startDatetime: Long,
val endDatetime: Long,
val maxParticipants: Int,
val status: MeetingStatus = MeetingStatus.SCHEDULED
)
enum class MeetingStatus {
SCHEDULED, ACTIVE, ENDED, CANCELLED
}
data class Participant(
val userId: String,
val username: String,
val displayName: String,
val avatar: String?,
val isHost: Boolean,
val joinTime: Long?
)
// MARK: - Meet Service
class allgramMeetService(
private val apiKey: String,
private val baseUrl: String = "https://api.allgram.best"
) {
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
private var webSocket: WebSocket? = null
private val messageCallbacks = mutableListOf<(String) -> Unit>()
private val systemEventCallbacks = mutableListOf<(String, String) -> Unit>()
// WebRTC components
private var peerConnection: PeerConnection? = null
private var localVideoTrack: VideoTrack? = null
private var localAudioTrack: AudioTrack? = null
// MARK: - WebSocket Management
fun connectWebSocket() {
val request = Request.Builder()
.url("wss://api.allgram.best/ws/meetings/")
.build()
webSocket = client.newWebSocket(request, object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
Log.d("allgramMeet", "✅ WebSocket connected for meetings")
// Authenticate
sendWebSocketMessage(JSONObject().apply {
put("type", "auth")
put("api_key", apiKey)
}.toString())
}
override fun onMessage(webSocket: WebSocket, text: String) {
try {
val json = JSONObject(text)
handleWebSocketMessage(json)
} catch (e: Exception) {
Log.e("allgramMeet", "Failed to parse WebSocket message", e)
}
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
Log.e("allgramMeet", "WebSocket failure", t)
// Attempt reconnection
CoroutineScope(Dispatchers.IO).launch {
delay(5000)
connectWebSocket()
}
}
})
}
// MARK: - Meeting Management
suspend fun createMeeting(
name: String,
description: String,
participants: List<String>,
startTime: Long,
endTime: Long,
isP2P: Boolean = false
): Result<Meeting> = withContext(Dispatchers.IO) {
try {
val requestBody = JSONObject().apply {
put("meeting_name", name)
put("meeting_description", description)
put("participants", JSONObject().apply {
participants.forEachIndexed { index, participant ->
put(index.toString(), participant)
}
})
put("start_datetime", startTime)
put("end_datetime", endTime)
put("is_p2p", isP2P)
put("is_public", false)
put("max_participants", 50)
}.toString()
val request = Request.Builder()
.url("$baseUrl/meetings/create")
.post(requestBody.toRequestBody("application/json".toMediaType()))
.addHeader("Authorization", "Bearer $apiKey")
.build()
val response = client.newCall(request).execute()
if (response.isSuccessful) {
val responseBody = response.body?.string()
val meetingJson = JSONObject(responseBody ?: "")
val meeting = Meeting(
meetingId = meetingJson.getString("meeting_id"),
meetingName = meetingJson.getString("meeting_name"),
meetingDescription = meetingJson.getString("meeting_description"),
isP2P = meetingJson.optBoolean("is_p2p", false),
participants = participants,
startDatetime = meetingJson.optLong("start_datetime", startTime),
endDatetime = meetingJson.optLong("end_datetime", endTime),
maxParticipants = meetingJson.optInt("max_participants", 50)
)
Log.d("allgramMeet", "✅ Meeting created: ${meeting.meetingName}")
Result.success(meeting)
} else {
Log.e("allgramMeet", "Failed to create meeting: ${response.code}")
Result.failure(Exception("Server error: ${response.code}"))
}
} catch (e: Exception) {
Log.e("allgramMeet", "Exception creating meeting", e)
Result.failure(e)
}
}
// MARK: - WebRTC Setup
fun initializeWebRTC(meetingId: String, isP2P: Boolean) {
val rtcConfig = RTCConfiguration(
listOf(
PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer(),
PeerConnection.IceServer.builder("stun:stun1.l.google.com:19302").createIceServer()
)
).apply {
iceTransportsType = PeerConnection.IceTransportsType.ALL
bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE
rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE
if (isP2P) {
iceCandidatePoolSize = 10
}
}
val factory = PeerConnectionFactory.builder()
.setVideoDecoderFactory(DefaultVideoDecoderFactory())
.setVideoEncoderFactory(DefaultVideoEncoderFactory())
.setOptions(PeerConnectionFactory.Options().apply {
disableEncryption = false
disableNetworkMonitor = false
})
.createPeerConnectionFactory()
peerConnection = factory.createPeerConnection(rtcConfig, object : PeerConnection.Observer {
override fun onIceCandidate(candidate: IceCandidate) {
// Send ICE candidate to other participants
sendIceCandidate(candidate, meetingId)
}
override fun onAddStream(stream: MediaStream) {
// Handle incoming media stream
Log.d("allgramMeet", "📹 Incoming media stream: ${stream.videoTracks.size} video tracks")
}
override fun onConnectionChange(newState: PeerConnection.PeerConnectionState) {
when (newState) {
PeerConnection.PeerConnectionState.CONNECTED -> {
Log.d("allgramMeet", "✅ WebRTC connection established")
}
PeerConnection.PeerConnectionState.DISCONNECTED -> {
Log.d("allgramMeet", "❌ WebRTC connection lost")
}
else -> {
Log.d("allgramMeet", "🔄 WebRTC state: $newState")
}
}
}
// Implement other required methods...
override fun onSignalingChange(state: PeerConnection.SignalingState) {}
override fun onIceConnectionChange(state: PeerConnection.IceConnectionState) {}
override fun onIceConnectionReceivingChange(receiving: Boolean) {}
override fun onIceGatheringChange(state: PeerConnection.IceGatheringState) {}
override fun onAddTrack(receiver: RtpReceiver, streams: Array<out MediaStream>) {}
override fun onTrack(transceiver: RtpTransceiver) {}
override fun onRemoveStream(stream: MediaStream) {}
override fun onDataChannel(channel: DataChannel) {}
override fun onRenegotiationNeeded() {}
override fun onAddIceCandidate(candidate: IceCandidate) {}
})
// Create local video track
val videoCapturer = createCameraCapturer()
val videoSource = factory.createVideoSource(videoCapturer.isScreencast)
localVideoTrack = factory.createVideoTrack(videoSource)
videoCapturer.initialize(SurfaceTextureHelper.create("CaptureThread", null), null)
videoCapturer.startCapture(480, 640, 30)
// Create local audio track
val audioSource = factory.createAudioSource(MediaConstraints())
localAudioTrack = factory.createAudioTrack(audioSource)
// Add tracks to peer connection
peerConnection?.addTrack(localVideoTrack)
peerConnection?.addTrack(localAudioTrack)
}
private fun createCameraCapturer(): CameraVideoCapturer {
val cameraEnumerator = Camera2Enumerator(context)
val deviceNames = cameraEnumerator.deviceNames
// Find front camera
for (deviceName in deviceNames) {
if (cameraEnumerator.isFrontFacing(deviceName)) {
return cameraEnumerator.createCapturer(deviceName, null)
}
}
// Fallback to back camera
return cameraEnumerator.createCapturer(deviceNames[0], null)
}
private fun sendIceCandidate(candidate: IceCandidate, meetingId: String) {
val candidateData = JSONObject().apply {
put("type", "ice_candidate")
put("meeting_id", meetingId)
put("candidate", candidate.sdp)
put("sdpMLineIndex", candidate.sdpMLineIndex)
put("sdpMid", candidate.sdpMid)
}
webSocket?.send(candidateData.toString())
}
// MARK: - WebSocket Communication
private fun sendWebSocketMessage(message: String) {
webSocket?.send(message) ?: run {
Log.w("allgramMeet", "WebSocket not connected")
}
}
private fun handleWebSocketMessage(json: JSONObject) {
when {
json.has("action") -> {
val action = json.getString("action")
val meetingId = json.optString("meeting_id", "")
handleSystemEvent(action, meetingId)
}
json.has("ice_candidate") -> {
handleIceCandidate(json)
}
json.has("meeting_data") -> {
// Handle meeting updates
Log.d("allgramMeet", "📊 Meeting data update received")
}
}
}
private fun handleSystemEvent(action: String, meetingId: String) {
Log.d("allgramMeet", "System event: $action in meeting: $meetingId")
systemEventCallbacks.forEach { callback ->
callback(action, meetingId)
}
}
private fun handleIceCandidate(json: JSONObject) {
val candidate = IceCandidate(
json.getString("sdpMid"),
json.getInt("sdpMLineIndex"),
json.getString("candidate")
)
peerConnection?.addIceCandidate(candidate)
}
// MARK: - Callbacks
fun addMessageCallback(callback: (String) -> Unit) {
messageCallbacks.add(callback)
}
fun addSystemEventCallback(callback: (String, String) -> Unit) {
systemEventCallbacks.add(callback)
}
// MARK: - Cleanup
fun disconnect() {
webSocket?.close(1000, "Disconnecting")
webSocket = null
localVideoTrack?.dispose()
localAudioTrack?.dispose()
peerConnection?.dispose()
}
}
// MARK: - ViewModel
class MeetViewModel(
private val meetService: allgramMeetService
) : ViewModel() {
private val _meetings = MutableStateFlow<List<Meeting>>(emptyList())
val meetings: StateFlow<List<Meeting>> = _meetings.asStateFlow()
private val _currentMeeting = MutableStateFlow<Meeting?>(null)
val currentMeeting: StateFlow<Meeting?> = _currentMeeting.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
init {
setupMeetService()
}
private fun setupMeetService() {
meetService.addSystemEventCallback { action, meetingId ->
when (action) {
"meeting_started" -> {
// Handle meeting start
}
"meeting_ended" -> {
// Handle meeting end
}
}
}
}
fun createMeeting(name: String, description: String, participants: List<String>, startTime: Long, endTime: Long, isP2P: Boolean = false) {
viewModelScope.launch {
_isLoading.value = true
try {
val result = meetService.createMeeting(name, description, participants, startTime, endTime, isP2P)
result.onSuccess { meeting ->
_meetings.value = _meetings.value + meeting
}.onFailure { error ->
Log.e("MeetViewModel", "Failed to create meeting", error)
}
} finally {
_isLoading.value = false
}
}
}
fun joinMeeting(meeting: Meeting) {
_currentMeeting.value = meeting
meetService.initializeWebRTC(meeting.meetingId, meeting.isP2P)
}
override fun onCleared() {
super.onCleared()
meetService.disconnect()
}
}
// MARK: - Compose UI
@Composable
fun MeetingListScreen(
viewModel: MeetViewModel = viewModel { MeetViewModel(allgramMeetService("your-api-key")) }
) {
val meetings by viewModel.meetings.collectAsState()
val isLoading by viewModel.isLoading.collectAsState()
LazyColumn {
items(meetings) { meeting ->
MeetingItem(
meeting = meeting,
onClick = { viewModel.joinMeeting(meeting) }
)
}
}
if (isLoading) {
CircularProgressIndicator()
}
}
@Composable
fun MeetingItem(
meeting: Meeting,
onClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable { onClick() },
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = meeting.meetingName,
style = MaterialTheme.typography.h6
)
if (meeting.isP2P) {
Icon(
imageVector = Icons.Default.NetworkCheck,
contentDescription = "P2P Meeting",
tint = Color.Green
)
}
}
Text(
text = meeting.meetingDescription,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
Text(
text = "${meeting.participants.size} participants",
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}
}
Our Kotlin SDK leverages the latest Android technologies including Jetpack Compose, Coroutines, and modern Android architecture patterns. Built with performance and developer experience in mind, it provides a robust foundation for building scalable video conferencing applications.
Leverage WebRTC technology for direct peer-to-peer video communication. Our WebRTC implementation provides low-latency video calls, data channels for real-time collaboration, and seamless integration with the Jitsi Meet SDK for enterprise-grade video conferencing.
// allgram Meet WebRTC P2P Integration Example
class allgramWebRTCClient {
constructor(meetingId, isP2P = false) {
this.meetingId = meetingId;
this.isP2P = isP2P;
this.peerConnection = null;
this.localStream = null;
this.remoteStreams = new Map();
this.dataChannel = null;
this.iceServers = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
];
if (isP2P) {
this.iceServers.push({
urls: 'turn:turn.allgram.best:3478',
username: 'allgram_user',
credential: 'allgram_password'
});
}
}
// Initialize WebRTC connection
async initializeConnection() {
try {
// Get user media
this.localStream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280, max: 1920 },
height: { ideal: 720, max: 1080 },
frameRate: { ideal: 30, max: 60 }
},
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
// Create peer connection
this.peerConnection = new RTCPeerConnection({
iceServers: this.iceServers,
iceCandidatePoolSize: this.isP2P ? 10 : 0,
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require'
});
// Add local stream tracks
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
// Handle ICE candidates
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.sendIceCandidate(event.candidate);
}
};
// Handle incoming streams
this.peerConnection.ontrack = (event) => {
const [stream] = event.streams;
this.remoteStreams.set(event.track.id, stream);
this.onRemoteStreamAdded(stream);
};
// Handle connection state changes
this.peerConnection.onconnectionstatechange = () => {
console.log('Connection state:', this.peerConnection.connectionState);
this.onConnectionStateChange(this.peerConnection.connectionState);
};
// Create data channel for P2P communication
if (this.isP2P) {
this.dataChannel = this.peerConnection.createDataChannel('allgram_data', {
ordered: true,
maxRetransmits: 3
});
this.setupDataChannel(this.dataChannel);
}
console.log('✅ WebRTC connection initialized');
return true;
} catch (error) {
console.error('❌ Failed to initialize WebRTC:', error);
throw error;
}
}
// Create and send offer
async createOffer() {
try {
const offer = await this.peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await this.peerConnection.setLocalDescription(offer);
// Send offer to signaling server
this.sendSignalingMessage({
type: 'offer',
meetingId: this.meetingId,
offer: offer,
isP2P: this.isP2P
});
return offer;
} catch (error) {
console.error('❌ Failed to create offer:', error);
throw error;
}
}
// Handle incoming answer
async handleAnswer(answer) {
try {
await this.peerConnection.setRemoteDescription(answer);
console.log('✅ Answer processed successfully');
} catch (error) {
console.error('❌ Failed to process answer:', error);
throw error;
}
}
// Handle incoming offer
async handleOffer(offer) {
try {
await this.peerConnection.setRemoteDescription(offer);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
// Send answer back
this.sendSignalingMessage({
type: 'answer',
meetingId: this.meetingId,
answer: answer
});
return answer;
} catch (error) {
console.error('❌ Failed to handle offer:', error);
throw error;
}
}
// Handle ICE candidates
async handleIceCandidate(candidate) {
try {
await this.peerConnection.addIceCandidate(candidate);
console.log('✅ ICE candidate added');
} catch (error) {
console.error('❌ Failed to add ICE candidate:', error);
}
}
// Send ICE candidate to signaling server
sendIceCandidate(candidate) {
this.sendSignalingMessage({
type: 'ice_candidate',
meetingId: this.meetingId,
candidate: candidate
});
}
// Setup data channel for P2P communication
setupDataChannel(channel) {
channel.onopen = () => {
console.log('✅ Data channel opened');
this.onDataChannelOpen(channel);
};
channel.onmessage = (event) => {
const data = JSON.parse(event.data);
this.onDataChannelMessage(data);
};
channel.onclose = () => {
console.log('🔒 Data channel closed');
this.onDataChannelClose();
};
channel.onerror = (error) => {
console.error('❌ Data channel error:', error);
};
}
// Send data through data channel
sendData(data) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
this.dataChannel.send(JSON.stringify(data));
return true;
}
return false;
}
// Send signaling message
sendSignalingMessage(message) {
// Implementation depends on your signaling server
// This could be WebSocket, HTTP, or other transport
console.log('📡 Sending signaling message:', message.type);
// Example WebSocket implementation
if (window.meetSignalingSocket) {
window.meetSignalingSocket.send(JSON.stringify(message));
}
}
// Event handlers (to be implemented by consumer)
onRemoteStreamAdded(stream) {
console.log('📹 Remote stream added:', stream.id);
// Handle new remote stream (e.g., add to UI)
}
onConnectionStateChange(state) {
console.log('🔄 Connection state changed:', state);
// Handle connection state changes
}
onDataChannelOpen(channel) {
console.log('📡 Data channel ready for communication');
// Handle data channel ready state
}
onDataChannelMessage(data) {
console.log('📨 Data channel message:', data);
// Handle incoming data channel messages
}
onDataChannelClose() {
console.log('🔒 Data channel closed');
// Handle data channel closure
}
// Get connection statistics
async getConnectionStats() {
if (!this.peerConnection) return null;
try {
const stats = await this.peerConnection.getStats();
const connectionStats = {};
stats.forEach((report) => {
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
connectionStats.currentRoundTripTime = report.currentRoundTripTime;
connectionStats.availableOutgoingBitrate = report.availableOutgoingBitrate;
}
});
return connectionStats;
} catch (error) {
console.error('❌ Failed to get connection stats:', error);
return null;
}
}
// Cleanup resources
disconnect() {
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
if (this.dataChannel) {
this.dataChannel.close();
}
if (this.peerConnection) {
this.peerConnection.close();
}
this.localStream = null;
this.peerConnection = null;
this.dataChannel = null;
this.remoteStreams.clear();
console.log('🧹 WebRTC connection cleaned up');
}
}
// Usage example
const webrtcClient = new allgramWebRTCClient('meeting_123', true);
// Initialize connection
webrtcClient.initializeConnection()
.then(() => {
console.log('WebRTC ready for P2P communication');
// Create offer for P2P connection
return webrtcClient.createOffer();
})
.then(offer => {
console.log('Offer created:', offer);
})
.catch(error => {
console.error('Failed to setup WebRTC:', error);
});
// Handle incoming signaling messages
window.handleSignalingMessage = (message) => {
switch (message.type) {
case 'offer':
webrtcClient.handleOffer(message.offer);
break;
case 'answer':
webrtcClient.handleAnswer(message.answer);
break;
case 'ice_candidate':
webrtcClient.handleIceCandidate(message.candidate);
break;
}
};
Our WebRTC implementation provides enterprise-grade P2P video communication with advanced features for real-time collaboration and secure data exchange.
Seamlessly integrate video meetings with your calendar system. Our AI-powered scheduling automatically finds optimal meeting times, handles time zones, and coordinates participants across different platforms and devices.
// allgram Meet Calendar Integration & AI Scheduling Example
class allgramCalendarService {
constructor(apiKey, baseUrl = 'https://api.allgram.best') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
this.calendars = new Map();
this.meetings = new Map();
this.aiScheduler = new AIScheduler();
}
// Get user's calendar events
async getCalendarEvents(calendarId, startDate, endDate) {
try {
const response = await fetch(`${this.baseUrl}/calendar/${calendarId}/events`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
params: {
start_date: startDate.toISOString(),
end_date: endDate.toISOString(),
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
}
});
if (response.ok) {
const events = await response.json();
this.calendars.set(calendarId, events);
return events;
}
} catch (error) {
console.error('Failed to fetch calendar events:', error);
throw error;
}
}
// Create meeting with AI scheduling
async createMeetingWithAI(meetingData) {
try {
const {
title,
description,
participants,
duration,
preferredTimes,
timezone,
isUrgent = false
} = meetingData;
// Get availability for all participants
const availability = await this.getParticipantAvailability(participants, preferredTimes, timezone);
// Use AI to find optimal meeting time
const optimalTime = await this.aiScheduler.findOptimalTime(availability, duration, {
timezone,
isUrgent,
preferredTimes
});
// Create meeting at optimal time
const meeting = await this.createMeeting({
title,
description,
participants,
startTime: optimalTime.start,
endTime: optimalTime.end,
timezone,
calendarId: optimalTime.calendarId
});
// Send invitations to participants
await this.sendMeetingInvitations(meeting, participants);
return meeting;
} catch (error) {
console.error('Failed to create AI-scheduled meeting:', error);
throw error;
}
}
// Get participant availability
async getParticipantAvailability(participants, timeRange, timezone) {
const availability = {};
for (const participant of participants) {
try {
const participantCalendars = await this.getParticipantCalendars(participant);
const participantAvailability = this.calculateAvailability(participantCalendars, timeRange, timezone);
availability[participant] = participantAvailability;
} catch (error) {
console.warn(`Could not get availability for ${participant}:`, error);
// Assume available if calendar access fails
availability[participant] = this.getDefaultAvailability(timeRange);
}
}
return availability;
}
// Calculate availability from calendar events
calculateAvailability(events, timeRange, timezone) {
const availability = [];
const { start, end } = timeRange;
// Convert to target timezone
const startTime = new Date(start);
const endTime = new Date(end);
// Generate 30-minute time slots
const timeSlots = [];
let currentTime = new Date(startTime);
while (currentTime < endTime) {
const slotEnd = new Date(currentTime.getTime() + 30 * 60 * 1000);
timeSlots.push({
start: new Date(currentTime),
end: slotEnd,
available: true
});
currentTime = slotEnd;
}
// Mark busy times based on calendar events
events.forEach(event => {
const eventStart = new Date(event.start.dateTime || event.start.date);
const eventEnd = new Date(event.end.dateTime || event.end.date);
timeSlots.forEach(slot => {
if (this.timesOverlap(slot, { start: eventStart, end: eventEnd })) {
slot.available = false;
slot.busyReason = event.summary;
}
});
});
return timeSlots.filter(slot => slot.available);
}
// Check if two time ranges overlap
timesOverlap(range1, range2) {
return range1.start < range2.end && range1.end > range2.start;
}
// Get default availability (assume available during business hours)
getDefaultAvailability(timeRange) {
const availability = [];
const { start, end } = timeRange;
let currentTime = new Date(start);
while (currentTime < end) {
const hour = currentTime.getHours();
const isBusinessHour = hour >= 9 && hour <= 17;
const isWeekday = currentTime.getDay() >= 1 && currentTime.getDay() <= 5;
if (isBusinessHour && isWeekday) {
const slotEnd = new Date(currentTime.getTime() + 30 * 60 * 1000);
availability.push({
start: new Date(currentTime),
end: slotEnd,
available: true
});
}
currentTime.setTime(currentTime.getTime() + 30 * 60 * 1000);
}
return availability;
}
// Create meeting
async createMeeting(meetingData) {
try {
const response = await fetch(`${this.baseUrl}/meetings/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
meeting_name: meetingData.title,
meeting_description: meetingData.description,
participants: meetingData.participants,
start_datetime: meetingData.startTime.toISOString(),
end_datetime: meetingData.endTime.toISOString(),
timezone: meetingData.timezone,
calendar_id: meetingData.calendarId,
is_public: false,
max_participants: meetingData.participants.length + 1
})
});
if (response.ok) {
const meeting = await response.json();
this.meetings.set(meeting.meeting_id, meeting);
return meeting;
}
} catch (error) {
console.error('Failed to create meeting:', error);
throw error;
}
}
// Send meeting invitations
async sendMeetingInvitations(meeting, participants) {
for (const participant of participants) {
try {
await this.sendInvitation(meeting, participant);
} catch (error) {
console.warn(`Failed to send invitation to ${participant}:`, error);
}
}
}
// Send individual invitation
async sendInvitation(meeting, participant) {
const invitation = {
meeting_id: meeting.meeting_id,
participant: participant,
title: meeting.meeting_name,
description: meeting.meeting_description,
start_time: meeting.start_datetime,
end_time: meeting.end_datetime,
timezone: meeting.timezone,
join_url: `https://meet.allgram.best/join/${meeting.meeting_id}`
};
// Send via email, push notification, or in-app message
await this.sendNotification(participant, 'meeting_invitation', invitation);
}
// Send notification
async sendNotification(participant, type, data) {
try {
const response = await fetch(`${this.baseUrl}/notifications/send`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
recipient: participant,
type: type,
data: data
})
});
if (response.ok) {
console.log(`✅ Invitation sent to ${participant}`);
}
} catch (error) {
console.error(`Failed to send invitation to ${participant}:`, error);
throw error;
}
}
// Get participant calendars
async getParticipantCalendars(participant) {
try {
const response = await fetch(`${this.baseUrl}/calendar/${participant}/calendars`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error(`Failed to get calendars for ${participant}:`, error);
return [];
}
}
}
// AI Scheduler for optimal meeting times
class AIScheduler {
constructor() {
this.preferences = {
businessHours: { start: 9, end: 17 },
preferredDays: [1, 2, 3, 4, 5], // Monday to Friday
bufferTime: 15, // minutes before/after meetings
urgencyWeight: 0.8
};
}
// Find optimal meeting time
async findOptimalTime(availability, duration, options) {
const { timezone, isUrgent, preferredTimes } = options;
// Score each available time slot
const scoredSlots = [];
for (const [participant, slots] of Object.entries(availability)) {
for (const slot of slots) {
const score = this.scoreTimeSlot(slot, duration, {
isUrgent,
preferredTimes,
timezone
});
scoredSlots.push({
participant,
slot,
score
});
}
}
// Sort by score and find best overlapping time
scoredSlots.sort((a, b) => b.score - a.score);
// Find time that works for most participants
const bestTime = this.findBestOverlappingTime(scoredSlots, duration);
return {
start: bestTime.start,
end: bestTime.end,
calendarId: 'primary',
score: bestTime.score
};
}
// Score a time slot based on various factors
scoreTimeSlot(slot, duration, options) {
let score = 100;
const { isUrgent, preferredTimes, timezone } = options;
// Check if slot duration is sufficient
const slotDuration = (slot.end - slot.start) / (1000 * 60); // minutes
if (slotDuration < duration) {
score -= 50;
}
// Prefer business hours
const hour = slot.start.getHours();
if (hour < this.preferences.businessHours.start || hour > this.preferences.businessHours.end) {
score -= 20;
}
// Prefer weekdays
const day = slot.start.getDay();
if (!this.preferences.preferredDays.includes(day)) {
score -= 15;
}
// Prefer preferred times if specified
if (preferredTimes && preferredTimes.length > 0) {
const isPreferred = preferredTimes.some(prefTime => {
const prefHour = new Date(prefTime).getHours();
return Math.abs(hour - prefHour) <= 2;
});
if (isPreferred) {
score += 25;
}
}
// Boost score for urgent meetings
if (isUrgent) {
score *= this.preferences.urgencyWeight;
}
return Math.max(0, score);
}
// Find best overlapping time for multiple participants
findBestOverlappingTime(scoredSlots, duration) {
const timeSlots = new Map();
// Group slots by time
scoredSlots.forEach(({ participant, slot, score }) => {
const key = `${slot.start.toISOString()}_${slot.end.toISOString()}`;
if (!timeSlots.has(key)) {
timeSlots.set(key, {
start: slot.start,
end: slot.end,
participants: [],
totalScore: 0
});
}
const timeSlot = timeSlots.get(key);
timeSlot.participants.push(participant);
timeSlot.totalScore += score;
});
// Find slot with highest score and most participants
let bestSlot = null;
let bestScore = 0;
for (const [key, slot] of timeSlots) {
const slotDuration = (slot.end - slot.start) / (1000 * 60);
if (slotDuration >= duration && slot.totalScore > bestScore) {
bestSlot = slot;
bestScore = slot.totalScore;
}
}
return bestSlot || {
start: new Date(),
end: new Date(Date.now() + duration * 60 * 1000),
score: 0
};
}
}
// Usage example
const calendarService = new allgramCalendarService('your-api-key');
// Create AI-scheduled meeting
const meetingData = {
title: 'Team Planning Session',
description: 'Quarterly planning and strategy discussion',
participants: ['user1@company.com', 'user2@company.com', 'user3@company.com'],
duration: 60, // minutes
preferredTimes: [
new Date(Date.now() + 24 * 60 * 60 * 1000).setHours(10, 0, 0, 0), // Tomorrow 10 AM
new Date(Date.now() + 24 * 60 * 60 * 1000).setHours(14, 0, 0, 0) // Tomorrow 2 PM
],
timezone: 'America/New_York',
isUrgent: false
};
calendarService.createMeetingWithAI(meetingData)
.then(meeting => {
console.log('AI-scheduled meeting created:', meeting);
})
.catch(error => {
console.error('Failed to create AI-scheduled meeting:', error);
});
Our calendar integration provides intelligent scheduling with AI-powered optimization, seamless time zone handling, and automated participant coordination.
Ready to take your video conferencing integration to the next level? Explore our comprehensive resources and advanced features to build enterprise-grade meeting applications with allgram.
Learn about our enterprise security features, GDPR compliance, and FIPS 140-2 certification.
Learn More →Discover advanced techniques for optimizing video performance and scalability.
Learn More →Join our developer community for support, updates, and collaboration.
Join Community →