JSPM

aethercall

1.0.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 2
  • Score
    100M100P100Q29115F
  • License MIT

A scalable WebRTC video calling API built with Node.js and OpenVidu

Package Exports

  • aethercall
  • aethercall/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (aethercall) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

AetherCall

npm version License: MIT Node.js CI

AetherCall is a production-ready, open-source video calling API built on top of OpenVidu. It provides a robust HTTP REST API for integrating real-time video communication, audio calling, screen sharing, and recording capabilities into any application.

Features

  • Real-time Video & Audio: High-quality WebRTC-based communication
  • Screen Sharing: Share entire screen or specific applications with participants
  • Session Recording: Record video sessions with configurable layouts and formats
  • Room-based Access: Simple room code system for easy participant joining
  • JWT Authentication: Secure token-based authentication with role management
  • HTTP REST API: Clean, documented API endpoints for all operations
  • Database Flexibility: Support for PostgreSQL, MongoDB, filesystem, or in-memory storage
  • Production Ready: Built-in rate limiting, CORS, security headers, and error handling
  • Comprehensive Testing: 45+ automated tests covering all functionality

Architecture

AetherCall/
├── index.js                # Main application entry point
├── src/
│   ├── core/               # Core business logic
│   │   ├── auth/           # JWT token management
│   │   ├── openvidu/       # OpenVidu API wrapper
│   │   └── storage/        # Database abstraction layer
│   │
│   ├── interfaces/
│   │   └── http/           # HTTP REST API
│   │       ├── routes/     # API route handlers
│   │       └── server.js   # Express server setup
│   │
│   └── utils/              # Shared utilities
│       ├── config.js       # Configuration management
│       └── logger.js       # Structured logging
│
├── tests/                  # Test suite (Jest)
├── docs/                   # API documentation
└── package.json            # Project dependencies

Installation

Prerequisites

  • Node.js 16.x or higher
  • npm or yarn
  • OpenVidu Server (Docker recommended)

Install via npm

npm install aethercall

Install from Source

git clone https://github.com/RayenMiri/AetherCall.git
cd AetherCall
npm install

Quick Start

1. Start OpenVidu Server

AetherCall requires an OpenVidu server. The easiest way is using Docker:

# Pull and run OpenVidu development server
docker run -p 4443:4443 --rm \
  -e OPENVIDU_SECRET=MY_SECRET \
  openvidu/openvidu-dev:2.29.0

2. Configure Environment

Create a .env file in your project root:

# OpenVidu Configuration
OPENVIDU_URL=http://localhost:4443
OPENVIDU_SECRET=MY_SECRET

# Server Configuration  
PORT=3000
HOST=localhost
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production

# Database (optional - defaults to memory)
DB_TYPE=memory

# Logging
LOG_LEVEL=info

3. Start the API Server

# Using npm
npm start

# Using yarn
yarn start

# Development mode with auto-reload
npm run dev

# Or using the setup helper
npm run setup

4. Using as a Module

See examples/usage-example.js for detailed examples of how to integrate AetherCall into your existing Node.js applications:

# Run different usage examples
node examples/usage-example.js simple      # Simple usage with defaults
node examples/usage-example.js advanced    # Advanced configuration
node examples/usage-example.js express     # Integration with Express app
node examples/usage-example.js production  # Production configuration

The API will be available at http://localhost:3000

5. Verify Installation

Check if the API is running:

curl http://localhost:3000/health

Expected response:

{
  "status": "healthy",
  "timestamp": "2025-07-27T10:00:00.000Z",
  "version": "1.0.0"
}

API Usage

Base URL

http://localhost:3000/api

Authentication

All API requests (except health check) require a JWT token:

// Get API token
const response = await fetch('/api/auth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ 
    clientId: 'your-application-id',
    expiresIn: '1h' // optional
  })
});

const { data } = await response.json();
const apiToken = data.accessToken;

Core Endpoints

Sessions Management

// Create a video session
const session = await fetch('/api/sessions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    mediaMode: 'ROUTED',
    recordingMode: 'MANUAL',
    metadata: JSON.stringify({ roomName: 'My Meeting' })
  })
});

// Get session info
const sessionInfo = await fetch(`/api/sessions/${sessionId}`, {
  headers: { 'Authorization': `Bearer ${apiToken}` }
});

// Close session
await fetch(`/api/sessions/${sessionId}`, {
  method: 'DELETE',
  headers: { 'Authorization': `Bearer ${apiToken}` }
});

Connection Management

// Create connection token for participant
const connection = await fetch('/api/connections', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    sessionId: 'session-id',
    role: 'PUBLISHER', // SUBSCRIBER, PUBLISHER, MODERATOR
    data: JSON.stringify({ 
      userId: 'user123',
      displayName: 'John Doe' 
    })
  })
});

// Join room with room code (simplified API)
const roomConnection = await fetch('/api/connections/join-room', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    roomCode: 'ABC123',
    userId: 'user123',
    displayName: 'John Doe',
    role: 'PUBLISHER'
  })
});

Recording Management

// Start recording
const recording = await fetch('/api/recordings/start', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    sessionId: 'session-id',
    name: 'my-recording',
    outputMode: 'COMPOSED',
    recordingLayout: 'BEST_FIT'
  })
});

// Stop recording
await fetch(`/api/recordings/stop/${recordingId}`, {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${apiToken}` }
});

// Get recording info
const recordingInfo = await fetch(`/api/recordings/${recordingId}`, {
  headers: { 'Authorization': `Bearer ${apiToken}` }
});

Room Management (Simplified API)

// Create room with simple code
const room = await fetch('/api/auth/room', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiToken}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    roomName: 'Daily Standup',
    maxParticipants: 10,
    roomCode: 'STANDUP123' // optional, will be generated if not provided
  })
});

Frontend Integration

Using with OpenVidu Browser SDK

React Example

import React, { useState, useEffect, useRef } from 'react';
import { OpenVidu } from 'openvidu-browser';

function VideoRoom({ roomCode, userDisplayName }) {
  const [session, setSession] = useState(null);
  const [publisher, setPublisher] = useState(null);
  const [subscribers, setSubscribers] = useState([]);
  const publisherRef = useRef(null);

  useEffect(() => {
    joinRoom();
    return () => leaveRoom();
  }, []);

  const joinRoom = async () => {
    try {
      // Get connection token from AetherCall API
      const response = await fetch('/api/connections/join-room', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          roomCode: roomCode,
          userId: `user-${Date.now()}`,
          displayName: userDisplayName,
          role: 'PUBLISHER'
        })
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const { data } = await response.json();
      
      // Initialize OpenVidu session
      const OV = new OpenVidu();
      const session = OV.initSession();

      // Event handlers
      session.on('streamCreated', (event) => {
        const subscriber = session.subscribe(event.stream, undefined);
        setSubscribers(prev => [...prev, subscriber]);
      });

      session.on('streamDestroyed', (event) => {
        setSubscribers(prev => 
          prev.filter(sub => sub.stream !== event.stream)
        );
      });

      // Connect to session
      await session.connect(data.token, { 
        clientData: data.userId 
      });

      // Create and publish local stream
      const publisher = await OV.initPublisherAsync(undefined, {
        audioSource: undefined,
        videoSource: undefined,
        publishAudio: true,
        publishVideo: true,
        resolution: '640x480',
        frameRate: 30,
        insertMode: 'APPEND',
        mirror: false
      });

      await session.publish(publisher);
      
      setSession(session);
      setPublisher(publisher);

    } catch (error) {
      console.error('Error joining room:', error);
    }
  };

  const leaveRoom = () => {
    if (session) {
      session.disconnect();
    }
  };

  const toggleVideo = () => {
    if (publisher) {
      publisher.publishVideo(!publisher.stream.videoActive);
    }
  };

  const toggleAudio = () => {
    if (publisher) {
      publisher.publishAudio(!publisher.stream.audioActive);
    }
  };

  return (
    <div className="video-room">
      <div className="controls">
        <button onClick={toggleVideo}>
          {publisher?.stream?.videoActive ? 'Turn Off Video' : 'Turn On Video'}
        </button>
        <button onClick={toggleAudio}>
          {publisher?.stream?.audioActive ? 'Mute' : 'Unmute'}
        </button>
        <button onClick={leaveRoom}>Leave Room</button>
      </div>
      
      <div className="video-container">
        <div ref={publisherRef} className="publisher-video" />
        
        <div className="subscribers">
          {subscribers.map((subscriber, index) => (
            <div 
              key={index} 
              ref={(ref) => {
                if (ref && subscriber.videos[0]) {
                  subscriber.addVideoElement(ref);
                }
              }}
              className="subscriber-video"
            />
          ))}
        </div>
      </div>
    </div>
  );
}

export default VideoRoom;

Vanilla JavaScript Example

<!DOCTYPE html>
<html>
<head>
    <title>AetherCall Video Room</title>
    <script src="https://github.com/OpenVidu/openvidu/releases/download/v2.29.0/openvidu-browser-2.29.0.min.js"></script>
</head>
<body>
    <div id="video-container">
        <div id="publisher"></div>
        <div id="subscribers"></div>
    </div>
    
    <div id="controls">
        <button onclick="toggleVideo()">Toggle Video</button>
        <button onclick="toggleAudio()">Toggle Audio</button>
        <button onclick="leaveRoom()">Leave Room</button>
    </div>

    <script>
        let session;
        let publisher;

        async function joinRoom(roomCode, displayName) {
            try {
                // Get token from AetherCall
                const response = await fetch('/api/connections/join-room', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({
                        roomCode: roomCode,
                        userId: `user-${Date.now()}`,
                        displayName: displayName,
                        role: 'PUBLISHER'
                    })
                });

                const { data } = await response.json();

                // Create OpenVidu session
                const OV = new OpenVidu();
                session = OV.initSession();

                // Handle new streams
                session.on('streamCreated', (event) => {
                    const subscriber = session.subscribe(event.stream, 'subscribers');
                });

                // Connect and publish
                await session.connect(data.token);
                publisher = await OV.initPublisherAsync(undefined, {
                    publishAudio: true,
                    publishVideo: true
                });
                
                await session.publish(publisher);
                publisher.addVideoElement('publisher');

            } catch (error) {
                console.error('Error:', error);
            }
        }

        function toggleVideo() {
            if (publisher) {
                publisher.publishVideo(!publisher.stream.videoActive);
            }
        }

        function toggleAudio() {
            if (publisher) {
                publisher.publishAudio(!publisher.stream.audioActive);
            }
        }

        function leaveRoom() {
            if (session) {
                session.disconnect();
            }
        }

        // Join room on page load
        joinRoom('DEMO123', 'Demo User');
    </script>
</body>
</html>

Backend Integration

Using AetherCall as a Service

Express.js Integration

const express = require('express');
const axios = require('axios');

const app = express();
const AETHERCALL_API_URL = 'http://localhost:3000/api';

// Get API token (you should cache this)
async function getAetherCallToken() {
  const response = await axios.post(`${AETHERCALL_API_URL}/auth/token`, {
    clientId: 'your-app-id'
  });
  return response.data.data.accessToken;
}

// Create meeting endpoint
app.post('/api/meetings', async (req, res) => {
  try {
    const token = await getAetherCallToken();
    
    // Create room in AetherCall
    const roomResponse = await axios.post(`${AETHERCALL_API_URL}/auth/room`, {
      roomName: req.body.title,
      maxParticipants: req.body.maxParticipants || 10
    }, {
      headers: { Authorization: `Bearer ${token}` }
    });

    const { data } = roomResponse.data;

    res.json({
      meetingId: data.sessionId,
      roomCode: data.roomCode,
      joinUrl: `${req.protocol}://${req.get('host')}/join/${data.roomCode}`
    });
  } catch (error) {
    console.error('Error creating meeting:', error);
    res.status(500).json({ error: 'Failed to create meeting' });
  }
});

// Join meeting endpoint
app.post('/api/meetings/:roomCode/join', async (req, res) => {
  try {
    const { roomCode } = req.params;
    const { userId, displayName, role = 'PUBLISHER' } = req.body;

    const connectionResponse = await axios.post(`${AETHERCALL_API_URL}/connections/join-room`, {
      roomCode,
      userId,
      displayName,
      role
    });

    res.json(connectionResponse.data);
  } catch (error) {
    console.error('Error joining meeting:', error);
    res.status(500).json({ error: 'Failed to join meeting' });
  }
});

Recording Management

// Start recording for a session
app.post('/api/meetings/:sessionId/recording/start', async (req, res) => {
  try {
    const token = await getAetherCallToken();
    const { sessionId } = req.params;

    const recording = await axios.post(`${AETHERCALL_API_URL}/recordings/start`, {
      sessionId,
      name: `meeting-${sessionId}-${Date.now()}`,
      outputMode: 'COMPOSED',
      recordingLayout: 'BEST_FIT'
    }, {
      headers: { Authorization: `Bearer ${token}` }
    });

    res.json({ 
      recordingId: recording.data.data.id,
      status: 'started' 
    });
  } catch (error) {
    console.error('Error starting recording:', error);
    res.status(500).json({ error: 'Failed to start recording' });
  }
});

// Stop recording
app.post('/api/recordings/:recordingId/stop', async (req, res) => {
  try {
    const token = await getAetherCallToken();
    const { recordingId } = req.params;

    const recording = await axios.post(`${AETHERCALL_API_URL}/recordings/stop/${recordingId}`, {}, {
      headers: { Authorization: `Bearer ${token}` }
    });

    res.json({
      recordingId,
      duration: recording.data.data.duration,
      downloadUrl: recording.data.data.url,
      status: 'completed'
    });
  } catch (error) {
    console.error('Error stopping recording:', error);
    res.status(500).json({ error: 'Failed to stop recording' });
  }
});

Using AetherCall as a Library

const AetherCall = require('aethercall');

// Initialize AetherCall instance
const aetherCall = new AetherCall({
  openviduUrl: process.env.OPENVIDU_URL,
  openviduSecret: process.env.OPENVIDU_SECRET,
  jwtSecret: process.env.JWT_SECRET,
  storage: {
    type: 'postgresql',
    connectionString: process.env.DATABASE_URL
  }
});

// Start the server
app.listen(3000, async () => {
  await aetherCall.start();
  console.log('AetherCall server running on port 3000');
});

Configuration

Environment Variables

Variable Description Default
OPENVIDU_URL OpenVidu server URL http://localhost:4443
OPENVIDU_SECRET OpenVidu server secret Required
PORT HTTP server port 3000
JWT_SECRET JWT signing secret Required
DB_TYPE Database type memory
LOG_LEVEL Logging level info

Database Support

# PostgreSQL
DB_TYPE=postgres
DB_CONNECTION_STRING=postgresql://user:pass@localhost:5432/aethercall

# MongoDB  
DB_TYPE=mongodb
DB_CONNECTION_STRING=mongodb://localhost:27017/aethercall

# File System
DB_TYPE=filesystem
DATA_PATH=./data

# Memory (Development)
DB_TYPE=memory

Deployment

Production Deployment

Environment Variables for Production

# OpenVidu Configuration
OPENVIDU_URL=https://your-openvidu-server.com:4443
OPENVIDU_SECRET=your-production-secret

# Server Configuration
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
JWT_SECRET=your-super-secure-jwt-secret-change-this

# Database
DB_TYPE=postgresql
DB_CONNECTION_STRING=postgresql://user:password@host:5432/aethercall

# Security
CORS_ORIGIN=https://yourdomain.com
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100

# Logging
LOG_LEVEL=warn

Docker Deployment

Dockerfile

FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm ci --only=production

# Copy source code
COPY . .

# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S aethercall -u 1001
USER aethercall

EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"

CMD ["npm", "start"]

Docker Compose with PostgreSQL

version: '3.8'

services:
  aethercall:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - OPENVIDU_URL=http://openvidu:4443
      - OPENVIDU_SECRET=MY_SECRET
      - DB_TYPE=postgresql
      - DB_CONNECTION_STRING=postgresql://postgres:password@postgres:5432/aethercall
      - JWT_SECRET=your-jwt-secret
    depends_on:
      postgres:
        condition: service_healthy
      openvidu:
        condition: service_started
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=aethercall
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  openvidu:
    image: openvidu/openvidu-dev:2.29.0
    environment:
      - OPENVIDU_SECRET=MY_SECRET
    ports:
      - "4443:4443"
    restart: unless-stopped

volumes:
  postgres_data:

Cloud Platform Deployment

Railway

# Install Railway CLI
npm install -g @railway/cli

# Login and deploy
railway login
railway init
railway up

Heroku

# Create Heroku app
heroku create your-app-name

# Set environment variables
heroku config:set NODE_ENV=production
heroku config:set OPENVIDU_URL=https://your-openvidu.herokuapp.com:4443
heroku config:set OPENVIDU_SECRET=your-secret
heroku config:set JWT_SECRET=your-jwt-secret

# Deploy
git push heroku main

Render

# render.yaml
services:
  - type: web
    name: aethercall-api
    env: node
    plan: starter
    buildCommand: npm install
    startCommand: npm start
    envVars:
      - key: NODE_ENV
        value: production
      - key: OPENVIDU_URL
        value: https://your-openvidu-instance.onrender.com:4443

OpenVidu Server Deployment

For production, you'll need a separate OpenVidu server. Options include:

  1. OpenVidu Cloud (Recommended for production)
  2. Self-hosted on AWS/GCP/Azure
  3. Docker deployment on your infrastructure

See OpenVidu Deployment Guide for detailed instructions.

Testing

AetherCall includes a comprehensive test suite with 45+ automated tests covering all functionality.

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run specific test file
npm test tests/http-api.test.js

# Run tests with coverage report
npm run test:coverage

# Run tests matching a pattern
npm test -- --grep "session"

Test Coverage

  • HTTP API Tests: All REST endpoints with authentication, validation, and error handling
  • Storage Tests: Database operations across all supported storage types
  • OpenVidu Integration Tests: Session, connection, and recording functionality
  • Authentication Tests: JWT token management and role-based access control

Test Requirements

Before running tests, ensure you have:

  1. OpenVidu Server running (see Installation section)
  2. Test environment configured:
# .env.test
OPENVIDU_URL=http://localhost:4443
OPENVIDU_SECRET=MY_SECRET
JWT_SECRET=test-secret-key
DB_TYPE=memory
LOG_LEVEL=error

Continuous Integration

Tests are automatically run on:

  • Pull requests
  • Commits to main branch
  • Release builds

Example GitHub Actions workflow:

name: Test
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      openvidu:
        image: openvidu/openvidu-dev:2.29.0
        ports:
          - 4443:4443
        env:
          OPENVIDU_SECRET: MY_SECRET
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test

Monitoring & Analytics

Health Check

curl http://localhost:3000/health

System Metrics

// Built-in metrics endpoint
app.get('/metrics', (req, res) => {
  res.json({
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    activeSessions: aetherCall.getActiveSessionCount(),
    totalConnections: aetherCall.getTotalConnectionCount()
  });
});

Security

Rate Limiting

Built-in rate limiting (100 requests per 15 minutes by default):

RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100

CORS Configuration

CORS_ORIGIN=https://yourdomain.com
CORS_METHODS=GET,POST,PUT,DELETE

Token Security

  • JWT tokens with configurable expiration
  • Role-based access control (SUBSCRIBER, PUBLISHER, MODERATOR)
  • Automatic token validation middleware

Contributing

We welcome contributions from the community! Please read our Contributing Guidelines before submitting PRs.

Development Setup

  1. Fork and clone the repository
git clone https://github.com/YOUR_USERNAME/AetherCall.git
cd AetherCall
  1. Install dependencies
npm install
  1. Set up development environment
cp .env.example .env
# Edit .env with your configuration
  1. Start OpenVidu development server
docker run -p 4443:4443 --rm \
  -e OPENVIDU_SECRET=MY_SECRET \
  openvidu/openvidu-dev:2.29.0
  1. Run in development mode
npm run dev

Contribution Guidelines

  • Code Style: We use ESLint and Prettier for code formatting
  • Testing: All new features must include tests
  • Documentation: Update README and API docs for any changes
  • Commit Messages: Use conventional commit format

Pull Request Process

  1. Create a feature branch: git checkout -b feature/amazing-feature
  2. Make your changes and add tests
  3. Run the test suite: npm test
  4. Update documentation if needed
  5. Commit your changes: git commit -m 'feat: add amazing feature'
  6. Push to your fork: git push origin feature/amazing-feature
  7. Open a Pull Request with a clear description

Security

Reporting Security Issues

If you discover a security vulnerability, please email us at security@aethercall.dev instead of using the issue tracker.

Security Features

  • JWT Authentication: Secure token-based authentication with configurable expiration
  • Rate Limiting: Built-in protection against abuse (configurable)
  • CORS Configuration: Proper cross-origin resource sharing setup
  • Input Validation: All API inputs are validated and sanitized
  • Security Headers: Helmet.js for security headers
  • Role-based Access: SUBSCRIBER, PUBLISHER, MODERATOR roles

;; ## License

;; This project is licensed under the MIT License - see the LICENSE file for details.

Support and Documentation

Documentation

;; ### Community Support ;; - GitHub Discussions - Community Q&A ;; - Issue Tracker - Bug reports and feature requests ;; - Discord Server - Real-time community chat

Professional Support

;; ## Roadmap

;; ### Version 2.0 (Q3 2025) ;; - [ ] WebSocket API: Real-time event streaming ;; - [ ] GraphQL Interface: Alternative to REST API ;; - [ ] Advanced Analytics: Detailed session and participant metrics ;; - [ ] Load Balancing: Multi-instance deployment support

;; ### Version 2.1 (Q4 2025) ;; - [ ] Mobile SDKs: React Native and Flutter wrappers ;; - [ ] Advanced Recording: Individual participant streams ;; - [ ] Moderation Tools: Participant management and content moderation

  • Custom Layouts: Configurable recording layouts

Future Releases

  • AI Integration: Automatic transcription and translation
  • Cloud Storage: Direct integration with AWS S3, Google Cloud Storage
  • Streaming: RTMP/HLS streaming support
  • SIP Integration: Traditional phone system integration

License

AetherCall is released under the MIT License. This means you can:

What You Can Do:

  • Use commercially - Build and sell applications using AetherCall
  • Modify freely - Customize the code to fit your needs
  • Distribute - Share the software with others
  • Sublicense - Include it in projects with different licenses
  • Private use - Use in proprietary/closed-source projects

📋 Requirements:

  • Include copyright notice - Keep the original copyright and license notice
  • Include license text - Include the MIT license text in distributions

🚫 Limitations:

  • No warranty - Software is provided "as-is" without warranties
  • No liability - Authors are not liable for damages or issues

💡 Why MIT License?

The MIT License offers maximum flexibility for developers while being business-friendly:

  • Corporate adoption - Companies can use and modify without legal concerns
  • Open source ecosystem - Compatible with most other open source licenses
  • Simple compliance - Easy to understand and follow requirements
  • Innovation encouragement - Allows derivative works and improvements
  • Community growth - Promotes sharing and collaboration

AetherCall - Professional video calling API for modern applications

GitHub stars GitHub forks GitHub issues License: MIT