Skip to content
muleskinPublic

About

Z - Conferencing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

51 Commits

Folders and files

Repository files navigation

Z+ — C# Client-Server Video Conferencing

A Zoom-style video conferencing platform for Windows: WPF desktop client + ASP.NET Core backend with SignalR signaling and WebRTC (SIPSorcery) peer-to-peer media.

This is Phase 1 (MVP) of the larger platform roadmap: accounts, instant/scheduled meetings, join by ID + password, group audio/video, in-meeting chat (public & private), participant roster, and host controls.

Solution layout

Project Purpose
src/ZPlus.Shared DTOs and hub method/event contracts shared by client and server
src/ZPlus.Server ASP.NET Core Web API (JWT auth, meetings, admin) + SignalR MeetingHub (roster, chat, WebRTC signaling relay, host controls) + EF Core/SQLite persistence
src/ZPlus.Client WPF (.NET 10) MVVM client with SIPSorcery WebRTC mesh media engine
src/ZPlus.Admin WPF admin console: user management, roles/rights, server settings, active-meeting control
src/ZPlus.ClientGui Cross-platform Avalonia meeting client (zplus-client) — the Linux GUI: meetings, roster, E2EE chat
src/ZPlus.AdminGui Cross-platform Avalonia admin console (zplus-admin) — the Linux GUI, same features as the WPF admin

Building single-file executables

Each application publishes to one self-contained binary (all DLLs and the .NET runtime embedded — nothing to install on the target machine).

Windows (net8.0 / net10.0-windows GUI apps):

dotnet publish src/ZPlus.Server -c Release -f net8.0 -o publish/server
dotnet publish src/ZPlus.Client -c Release -o publish/client
dotnet publish src/ZPlus.Admin  -c Release -o publish/admin

Copy Z+ Server.exe, Z+ Client.exe, and Z+ Admin.exe wherever you need them.

Linux (net10.0, into publish/linux) — use the Makefile at the repository root:

scripts/install-dotnet.sh   # one-time: install the .NET 10 SDK + native libraries
make                        # builds zplus-server, zplus-client and zplus-admin
make server                 # or build one target: server / client / admin
make clean

scripts/install-dotnet.sh sets up a Linux machine for Z+: it installs the .NET 10 SDK (needed to build) plus the native libraries the self-contained binaries rely on at runtime (ICU, OpenSSL, and X11/fontconfig for the GUI apps). It detects the distro (Debian/Ubuntu, Fedora/RHEL, openSUSE, Arch, Alpine) and falls back to Microsoft's dotnet-install.sh on others. To only prepare a machine to run the published binaries (no SDK), use scripts/install-dotnet.sh --deps-only.

Requires GNU make and the .NET 10 SDK. The Linux binaries target net10.0 (Windows builds stay on net8.0); the cross-platform projects multi-target net8.0;net10.0. The RuntimeIdentifier is auto-detected from the build machine (x86_64 → linux-x64, aarch64 → linux-arm64, armv7l/armv6l → linux-arm, with linux-musl-* on Alpine). Cross-build by overriding it, e.g. make RID=linux-arm64; make DOTNET=dotnet.exe runs the Makefile from WSL using the Windows .NET SDK.

Linux

publish/linux/ contains three self-contained x64 binaries — no .NET install required (chmod +x them after copying):

  • zplus-server — the full Z+ server, identical feature set to Windows. Creates zplus.db and zplus.key next to itself, binds 0.0.0.0:5199. All environment overrides (ZPLUS_DB, ZPLUS_KEY, ZPLUS_ADMIN_EMAIL, ZPLUS_ADMIN_PASSWORD) work the same. Windows and Linux clients connect to it interchangeably.
  • zplus-client — the Linux desktop meeting client (Avalonia, same dark Z+ look as Windows): sign in/register, start/join/schedule meetings with email invitations, live roster with host controls, end-to-end encrypted public/private chat. No audio/video yet — the media capture stack (Windows Media Foundation) is Windows-only; Linux participants join with roster + chat.
  • zplus-admin — the Linux desktop admin console (Avalonia), same features as the Windows admin app: users/roles, all server settings including SMTP, active meetings with force-end.

The GUI apps share their view-models with the Windows WPF apps and also run on Windows.

Browser (web client)

The server also hosts a browser-based client — no install required. Open http://<server>:5199/app (or https://…/app) and sign in. Invitation emails and the /join/{code} landing page also offer a "Join in your browser" button that opens the web client on /app#code=…&pw=…; it reads the meeting id (and password) from the URL fragment and joins automatically after sign-in. It supports the full feature set — sign in (with MFA), start / join / schedule meetings, live roster, end-to-end encrypted chat, reactions, polls, file sharing, whiteboard, breakout rooms and the waiting room — plus in-browser audio and video, and a personal email address book (add / edit / delete contacts, then one-click them into a meeting's invite list). The web client's chat E2EE is byte-compatible with the native clients (ECDH P-256 → AES-256-GCM via the browser's Web Crypto), so browser and desktop participants read each other's encrypted messages.

In-browser audio/video. The web client captures the camera and microphone with getUserMedia and connects over WebRTC in the same full-mesh topology as the Windows app: one RTCPeerConnection per participant, the newcomer offering to everyone already in the room. The offer/answer/ICE signaling is relayed over the SignalR hub using the same envelope format the native SIPSorcery client emits, so browser and Windows participants interoperate (VP8 video). Each tile shows live video or a name avatar, plus a connection indicator (connecting… / can't connect) until media is flowing; Mute, Stop video, and Share screen toggle the local tracks and update the roster. Screen sharing uses getDisplayMedia and swaps the shared screen onto each peer's existing video sender via replaceTrack (no renegotiation); the presenter's tile is badged 🖥 and shown whole (letterboxed) rather than cropped. A participant can present even without a camera, and native/other viewers see the screen in place of the presenter's video. In the browser this uses getDisplayMedia, so the OS/browser picker lets you choose a specific display, a single window, or a tab. The Windows (WPF) client can also originate a screen share and offers the same choice: a picker lists every display and every open window, and the chosen source is captured (GDI CopyFromScreen for a display, PrintWindow for a single window — which keeps working even when the window is covered) and fed through a software VP8 encoder (VideoEncoderEndPoint), switching each peer's outgoing video from the camera to the screen on the same RTP track. The browser/other viewers see it exactly like a camera swap, and presenter tiles are badged 🖥 everywhere. While a share is active the web client switches to a presentation view — the shared screen takes the stage and the camera feeds shrink to a thumbnail strip — and restores the equal grid automatically when sharing ends. When a presenter stops sharing (or turns their camera off), viewers show that person's avatar rather than a frozen last frame — clients broadcast their real camera state, so tiles hide video for anyone whose camera is off and who isn't presenting (the WPF sender also flushes a black frame on stop as a belt-and-suspenders). If no camera/mic is present the client joins receive-only (you still see and hear others). getUserMedia needs a secure context — use https:// in production; localhost also qualifies.

Active-speaker highlighting. Each client measures its own microphone level (RMS with a short debounce) and broadcasts a speaking flag over the hub, so every participant's tile shows a green glow while they're talking — consistent across all clients. The browser meters the mic via the Web Audio API; the WPF client decodes its encoded mic samples to measure level. Muted participants never light up.

Activity toasts. A transient popup (~5 seconds) announces meeting activity to everyone — a new chat message (💬 Name: …), a shared file (📎 Name shared file), or whiteboard drawing (debounced to once every 8 s). You never get a toast for your own actions. Implemented in all clients — the browser, and the WPF / Avalonia / mobile apps (which share the view-model logic).

STUN/TURN (cross-network calls). Clients fetch their ICE server list from GET /api/ice. The default is STUN-only (stun:stun.l.google.com:19302), which is enough when at least one peer has an open/cone NAT. Participants on different networks behind symmetric NAT or restrictive firewalls (common on cellular) will fail to connect with STUN alone and need a TURN relay. Configure one without a client rebuild by setting the IceServersJson server-setting row (or the ZPLUS_ICE_SERVERS environment variable) to a JSON array, e.g.:

[{"urls":"stun:stun.l.google.com:19302"},
 {"urls":["turn:turn.example.com:3478","turns:turn.example.com:5349"],"username":"u","credential":"p"}]

A common setup is coturn on the same host as the Z+ server — reuse the server's TLS certificate for turns: on 5349 so media also traverses firewalls that only allow TLS. The native Windows (WPF) client also fetches this list from /api/ice before creating peer connections, so it inherits the same TURN config (it keeps stun:/turn: entries; turns: and ?transport=tcp variants are dropped since SIPSorcery relays over UDP). The Linux and mobile clients are roster + chat only, so they have no ICE configuration to apply.

Join before the host. A scheduled meeting can be joined before the host arrives — the first participant to join activates it (the waiting room, when enabled, still holds guests until the host admits them).

It is a small vanilla-JS single-page app embedded in the server executable (so the server stays a single self-contained file) and served same-origin with the API and SignalR hub — no CORS configuration is required. For http:// LAN use the browser will warn about an insecure context but Web Crypto still works on localhost; use https:// in production (Web Crypto requires a secure context on non-localhost origins).

Mobile (iOS + Android)

src/ZPlus.Mobile is a .NET MAUI app targeting iOS and Android from one C# codebase. It reuses the same shared pieces as the Linux client — ZPlus.Shared, MeetingHubClient, ApiClient, and the Login/Home/Meeting view-models — so it has the same feature set: sign in / register (with MFA), start/join/schedule meetings, live roster, E2EE chat, reactions, polls, file sharing, whiteboard, breakout rooms, and the waiting room. Like the Linux client it is roster + chat + features only — camera/mic capture (WebRTC) is not wired up on mobile yet.

Requires the .NET MAUI workload (dotnet workload install maui). Build:

# Android APK (needs the Android SDK + a JDK; Visual Studio installs both)
dotnet build src/ZPlus.Mobile -c Release -f net10.0-android

# iOS — must be built on macOS with Xcode (Apple requirement); on Windows it
# compiles but cannot produce a signed .app/.ipa without a paired Mac.
dotnet build src/ZPlus.Mobile -c Release -f net10.0-ios

If the Android SDK/JDK aren't auto-detected, pass them explicitly, e.g. -p:AndroidSdkDirectory="C:\Program Files (x86)\Android\android-sdk" -p:JavaSdkDirectory="C:\Program Files\Android\openjdk\jdk-21.0.8".

http:// LAN servers are allowed (Android usesCleartextTraffic, iOS ATS override); for production use an https:// server URL. On first launch, set the Server field to your Z+ server's public URL, then sign in.

Running it

  1. Server — run Z+ Server.exe (or dotnet run --project src/ZPlus.Server). On first run, next to the executable, it creates:

    • zplus.db — SQLite database holding all users and all server configuration
    • zplus.key — the 64-byte AES/HMAC master key protecting secrets inside the database

    It binds to http://0.0.0.0:5199 by default so LAN clients can connect (Windows Firewall will prompt to allow inbound TCP 5199), and seeds the first super admin (admin@zplus.local / ChangeMe123! — change this immediately).

    Optional environment overrides: ZPLUS_DB (db path), ZPLUS_KEY (key file path), ZPLUS_ADMIN_EMAIL / ZPLUS_ADMIN_PASSWORD (first-run seed account).

  2. Client — run Z+ Client.exe on each participant's machine; point the Server field at http://<server-ip>:5199, create an account, then start or join a meeting.

  3. Admin — run Z+ Admin.exe and sign in with an Admin/SuperAdmin account:

    • Users — create accounts, edit display names, assign roles (User, Admin, SuperAdmin), enable/disable accounts, reset passwords. Disabled accounts cannot sign in or join meetings. Only super admins can create/modify super admin accounts; nobody can demote or disable themselves.
    • Server settings — allow/deny self-registration, require passwords on all meetings, cap participants per meeting, change the server listen URL (restart required), and configure email invitations: SMTP host/port/from/credentials plus the Public URL used in invite links. All stored in the database and enforced live.
    • Active meetings — see currently running meetings with participant counts and force-end any meeting (participants are notified instantly).

Security model

  • End-to-end encryption. Audio/video flows peer-to-peer over DTLS-SRTP and never touches the server. Chat is AES-256-GCM encrypted with a per-meeting key that participants exchange directly via ECDH (P-256) — the server relays and stores only ZE1$… ciphertext, so neither admins nor database access can read messages.
  • Passwords are hashed, then encrypted (AES + HMAC). Every password (user accounts and meeting passwords) is first one-way hashed with PBKDF2 — so the original password can never be recovered — and the hash is then wrapped in an AES-256-CBC + HMAC-SHA256 encrypt-then-MAC envelope (ZP1$… values in the database). The HMAC rejects any tampered or foreign ciphertext in constant time.
  • Key separation. The AES/HMAC master key lives in zplus.key, outside the database: a stolen zplus.db exposes no usable credential material without the key file. Back up the key file securely; if it is lost, stored secrets cannot be decrypted (users would need password resets).
  • Configuration lives in SQLite. There is no appsettings.json. Server behavior settings, the listen URL, and the JWT signing key (auto-generated, encrypted at rest) are all rows in the ServerSettings table, manageable through the admin app.
  • JWTs (12 h expiry) carry the user's role; SignalR connections authenticate via the access_token query parameter. Admin endpoints require the Admin/SuperAdmin role; disabled accounts are re-checked at meeting join, not just at login.

HTTPS / TLS

The server can terminate TLS natively — no reverse proxy required. In the admin app's Server settings:

  1. Set an HTTPS certificate (a server-side file path):
    • PFX/PKCS#12 — set the certificate path to your .pfx and the certificate password; leave the private-key path empty.
    • PEM (Let's Encrypt style) — set the certificate path to fullchain.pem, the private key path to privkey.pem, and the password only if the key is encrypted.
  2. Set the listen URL to https://0.0.0.0:5199 and the Public URL to https://<host>:5199. Save, then restart the server for the listen URL to apply.

Settings are validated on save — a missing file or wrong password is rejected before it can break a restart. Clients, the admin app and SignalR all accept https:///wss://.

Automatic renewal reload. The certificate is re-read from disk automatically when its files change (e.g. after certbot renew), so renewal needs no restart — the new cert is picked up within ~5 minutes. Override the poll interval with ZPLUS_CERT_RELOAD_SECONDS.

Notes: use a CA-issued certificate in production (self-signed certs are correctly rejected by the client). Ensure the account running the server can read the cert files (Let's Encrypt's /etc/letsencrypt/live/** is root-only by default — grant access or copy the pair somewhere readable). Enabling HTTPS also encrypts the admin/API traffic itself.

Architecture notes

  • Signaling: SignalR hub relays SDP offers/answers and ICE candidates between peers; the hub validates that both peers are in the same meeting.
  • Media: full-mesh WebRTC — each client holds one RTCPeerConnection per remote participant, sharing a single microphone and camera source (VP8 video, one encode fan-out). The joining client always initiates offers, which avoids negotiation glare.
  • Host controls: mute all, ask-to-unmute, remove participant, transfer host, end for all. Host role auto-promotes to the longest-connected participant if the host drops.
  • Persistence: users, meetings, attendance records, chat history, and configuration in SQLite (swap the EF Core provider for SQL Server/PostgreSQL in production). Live roster state is in-memory (MeetingStateStore).

In-meeting features

Beyond core audio/video/chat, meetings support:

  • Waiting room — enable it when starting/scheduling; non-hosts wait until the host admits or denies them (host sees a pending list in the People tab).
  • Reactions — one-click emoji that float over the meeting for everyone.
  • Polls — the host starts a poll (question + 2–10 options); everyone votes and sees live tallies; the host can close it. Polls persist for the meeting.
  • File sharing — share a file (up to 25 MB) into the meeting; everyone can download it.
  • Whiteboard — a shared, collaborative drawing canvas; strokes replay for anyone who joins mid-session; anyone can clear it.
  • Breakout rooms — the host creates rooms, auto-assigns participants, and opens/closes them; public chat is scoped to your room while breakouts are open.

The feature panels appear as tabs (People / Chat / Polls / Files / Board / Rooms) in the meeting window on both the Windows (WPF) and Linux (Avalonia) clients.

Admin features

The admin console adds, alongside user/settings/active-meeting management:

  • Dashboard — at-a-glance stats (users, admins, MFA-enrolled, active meetings/participants, meetings today/all-time, chat totals).
  • Smart search — search users and meetings by name, email, topic, or meeting code.
  • Audit log — an append-only record of security/admin actions (logins, user create/update, role/disable changes, password/MFA resets, settings saves, force-ends).
  • MFA (TOTP) — require or reset multi-factor auth per user. Required users enroll an authenticator app (Google Authenticator, Authy, 1Password, …) at next sign-in; the shared secret is stored AES+HMAC-encrypted and never leaves the server in plaintext.

Roadmap (remaining, per project spec)

Implemented above: waiting room, reactions, file sharing, breakout rooms, whiteboard, polls, admin dashboard, MFA, audit logs, smart search. Still on the roadmap (these need external media/AI/identity infrastructure):

  • Phase 2: screen sharing, recording
  • Phase 3: captions/transcription
  • Phase 4: SSO, compliance, large meetings (SFU media server)
  • Phase 5: AI meeting summaries, action items, translation

Production hardening TODOs

  • Native HTTPS/TLS is supported (see above) — use a CA-issued certificate in production.
  • Add TURN servers for NAT traversal beyond STUN (stun.l.google.com is used for dev).
  • Move from mesh to an SFU for meetings beyond ~4-6 participants.
  • Switch EnsureCreated to EF Core migrations before real data matters.

About

Z - Conferencing

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages