Skip to content
dpruteanPublic

About

A call library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Whisper

A multi-platform, WhatsApp-like real-time communication app built with Kotlin, JavaFX, and WebRTC.

Feature matrix

Feature Desktop (JavaFX) Web (browser) Mobile (Gluon)
Voice call ✅ ✅ ✅
Video call ✅ ✅ ✅
Screen / desktop share ✅ Windows/macOS/Linux ✅ —
Send images ✅ ✅ —
View incoming images ✅ ✅ ✅
Group calls (mesh) ✅ ✅ ✅

Project structure

whisper/
├── shared/              Shared Kotlin DTOs & SignalMessage protocol
├── signaling-server/    Ktor WebSocket signaling + static web assets
├── desktop-client/      JavaFX + webrtc-java desktop app
├── web-client/          TypeScript browser app (RTCPeerConnection)
├── mobile-client/       Gluon Mobile JavaFX app (Android / iOS)
├── infra/               docker-compose, coturn config, Dockerfile
└── gradle.properties    Shared dependency versions

Architecture

 ┌──────────────────┐   WS signaling   ┌──────────────────────────┐
 │  Desktop Client  │◄────────────────►│                          │
 │  (JavaFX)        │                  │   Ktor Signaling Server  │
 └────────┬─────────┘                  │   /ws  (WebSocket)       │
          │ WebRTC                     │   /app (static web files)│
          │ P2P mesh                   │   /health                │
 ┌────────▼─────────┐                  │                          │
 │  Web Client      │◄────────────────►│                          │
 │  (browser)       │                  └──────────┬───────────────┘
 └────────┬─────────┘                             │
          │ WebRTC                                │ ICE config
          │                                       ▼
 ┌────────▼─────────┐                  ┌──────────────────────────┐
 │  Mobile Client   │◄─────────────────│  coturn STUN/TURN        │
 │  (Gluon)         │ WebRTC           │  :3478                   │
 └──────────────────┘                  └──────────────────────────┘

Media (audio, video, screen) flows peer-to-peer over WebRTC. The Ktor server only handles signaling (SDP/ICE exchange), presence, room state, and image relay.


Prerequisites

Tool Version
JDK 17+
Gradle (wrapper included) 8.7
Node.js (web client build) 20+
Docker + Docker Compose any recent
GraalVM (optional, Gluon native) 22+

Quick start

1. Start the infrastructure (coturn + signaling server)

# Copy and configure environment
cp infra/.env.example infra/.env
# Edit infra/.env if needed (TURN_HOST, secrets, etc.)

cd infra
docker compose up

The signaling server will be available at http://localhost:8080.

2. Build & serve the web client

cd web-client
npm install
npm run build          # compiles TS → JS and copies to signaling-server resources

Then open http://localhost:8080/app/index.html in a browser.

3. Run the desktop client

./gradlew :desktop-client:run

Set the server URL via environment variable (default: ws://localhost:8080/ws):

WHISPER_SERVER=ws://your-server:8080/ws ./gradlew :desktop-client:run

4. Run the mobile client (host / desktop mode)

./gradlew :mobile-client:run

5. Build mobile client for Android

Requires GraalVM 22+ and Android SDK:

./gradlew :mobile-client:gluonfxBuild -Ptarget=android
./gradlew :mobile-client:gluonfxInstall -Ptarget=android
./gradlew :mobile-client:gluonfxRun -Ptarget=android

Set the server URL for Android (10.0.2.2 routes to host loopback in the emulator):

WHISPER_SERVER=ws://10.0.2.2:8080/ws ./gradlew :mobile-client:gluonfxRun

Environment variables

Variable Default Description
PORT 8080 Signaling server HTTP port
TURN_HOST localhost TURN server hostname (clients receive this)
TURN_USER whisper TURN username
TURN_PASS whisper_secret TURN password
WHISPER_SERVER ws://localhost:8080/ws WebSocket URL used by desktop + mobile
WHISPER_SERVER_HTTP http://10.0.2.2:8080 HTTP base URL used by the mobile WebView

Module details

shared

Kotlin JVM module. Contains:

  • SignalMessage – sealed class with @Serializable subtypes covering all signaling events
  • User, CallRoom, IceServerConfig – core domain models
  • whisperJson – shared kotlinx.serialization JSON codec

signaling-server

Ktor server (Netty). Responsibilities:

  • WebSocket endpoint /ws – SDP offer/answer relay, ICE forwarding, room state, image relay
  • Registry – in-memory connected user + room store (replace with Redis for multi-instance)
  • SignalingHandler – pure message dispatch logic
  • Serves compiled web client from resources/web

desktop-client

JavaFX app. Key classes:

  • DesktopSignalingClient – Ktor WS client with automatic reconnect
  • DesktopCallManager – mesh call lifecycle, PeerConnectionFactory, device capture
  • DesktopPeerConnection – wraps RTCPeerConnection from webrtc-java
  • WebRTCVideoView – JavaFX Canvas that renders I420 video frames
  • MainController – FXML controller wiring UI ↔ call manager
  • Screen sharing gated by isScreenShareSupported (Windows / macOS / Linux only)

web-client

TypeScript app compiled with tsc. Key modules:

  • SignalingClient – WebSocket client with auto-reconnect
  • PeerConnection – thin RTCPeerConnection wrapper
  • CallManager – mesh call manager (one PC per remote peer)
  • app.ts – UI event wiring

Built assets are copied to signaling-server/src/main/resources/web by npm run build.

mobile-client

Gluon Mobile JavaFX app. Three views:

  • LoginView – user ID + display name entry
  • LobbyView – room join/leave, participants, incoming image gallery
  • CallView – embeds the web client via WebView for the WebRTC call session (see note below)

WebRTC on Gluon: Native org.webrtc bindings do not integrate with Gluon's JavaFX runtime. The call session is implemented by loading the Whisper web client inside a javafx.scene.web.WebView. This gives full WebRTC support (voice + video) on Android API 21+ and desktop. Screen share is intentionally excluded on mobile.

infra

  • docker-compose.yml – coturn + Ktor server
  • Dockerfile.server – multi-stage Docker build (Node web → Ktor JAR → slim JRE)
  • turnserver.conf – coturn STUN/TURN configuration
  • .env.example – environment template

Group calls (mesh topology)

All participants in a room connect directly to each other (full mesh). Concretely:

  • When a participant joins a room, the server sends room_state to all members.
  • Each client opens a PeerConnection to every other member it doesn't already have one for.
  • The alphabetically-first userId creates the WebRTC offer; the other side answers.

Scaling note: Mesh is efficient for up to ~4–6 participants. For larger rooms, migrate to an SFU (e.g. LiveKit, Janus, mediasoup) and update the signaling server to route media through it.


Development tips

  • Run only the signaling server (no Docker): ./gradlew :signaling-server:run
  • Run tests: ./gradlew test
  • Format code: ./gradlew ktlintFormat (add ktlint plugin to root build if desired)
  • The web client uses plain TypeScript with no bundler – import maps or a simple HTTP server work for local dev: npx serve web-client/public

About

A call library

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages