A self-hosted video conferencing desktop app built on LiveKit Meet and Electron.
Web users and desktop users join the same rooms — fully interoperable.
| Package | Description |
|---|---|
packages/meet-core |
Fork of livekit-examples/meet — Next.js web app, tracks
upstream |
packages/desktop |
Electron wrapper with native screen picker and per-app audio capture |
pnpm installcd packages\desktop
pnpm electron:compile
pnpm electron:devThe app opens directly into the Voidline room. In dev it loads
http://localhost:3000/rooms/voidline; in production it
loads https://voidline-vid.duckdns.org/rooms/voidline.
To produce a Windows installer (.exe) to share with
others:
cd packages\desktop
pnpm electron:buildThis runs three steps in sequence: 1. Builds meet-core
(next build) 2. Compiles the Electron TypeScript
(tsc) 3. Packages everything with electron-builder
The installer lands at:
packages/desktop/dist/Voidline Meet Setup <version>.exe
Share that file with your friends — they just run it and the app installs like any other Windows program, with a desktop shortcut and Start Menu entry.
https://voidline-vid.duckdns.org — recipients don’t need
Node.js, pnpm, or anything else installed.electron-builder.yml but audio capture will not work on
those platforms.Before building a new release, update the version in
packages/desktop/package.json:
"version": "0.2.0"Then tag the commit:
git tag v0.2.0
git push origin main --tagsvoidline-meet/
packages/
meet-core/ ← livekit-examples/meet fork (tracks upstream)
desktop/
electron/
main.ts ← Main process, BrowserWindow setup
preload.ts ← contextBridge for renderer
screenPicker.ts ← Intercepts getDisplayMedia, shows native picker
audio.ts ← Per-process audio capture (wraps application-loopback)
picker/ ← Picker UI (HTML/CSS/JS)
lib/
AppAudioButton.tsx ← Audio capture toolbar button + process picker
useAppAudio.ts ← Renderer-side AudioWorklet hook
audioWorkletProcessor.js ← PCM player (int16→float32, 48kHz)
VolumeControl.tsx ← Per-user volume slider + mute toggle
VolumeMixer.tsx ← Floating mixer panel (one slider per remote participant)
vendor/
application-loopback/ ← Vendored WASAPI capture binary (MIT)
package.json ← pnpm workspace root
pnpm-workspace.yaml
getDisplayMedia() internally via
@livekit/components-reactsession.defaultSession.setDisplayMediaRequestHandler()desktopCapturer.getSources() fetches all
screens/windows with thumbnailsBrowserWindow renders a picker gridgetDisplayMedia() resolved normallyAudio capture uses application-loopback (vendored, MIT), a thin Node.js wrapper around a custom C++ binary that uses the Windows WASAPI loopback API.
Two capture modes are supported: - Window share → include mode: captures audio only from the shared window’s process - Full-screen share → exclude mode: captures all system audio except a chosen process (e.g. mute Discord while sharing your desktop)
The AppAudioButton toolbar button lets users pick the
audio source, mute/unmute, and manage a hide list to reduce noise from
system processes. Captured PCM is streamed to the renderer via IPC,
decoded in an AudioWorklet, and published as a LiveKit
ScreenShareAudio track.
A floating 🎚️ button sits bottom-right above the control bar.
Clicking it opens a mixer panel listing all remote participants, each
with a name label and volume slider. VolumeControl calls
RemoteAudioTrack.setVolume() on all of the participant’s
subscribed audio tracks. Works for both web and Electron users.
git fetch upstream-meet
git checkout upstream-meet/main -- packages/meet-core
git add packages/meet-core
git commit -m "chore: sync meet-core with upstream"| Phase | Status | Description |
|---|---|---|
| 1 | ✅ Complete | Electron wrapper + native screen/window picker |
| 2 | ✅ Complete | Per-app audio capture (WASAPI via vendored application-loopback) |
| 3 | ✅ Complete | Per-user volume mixer (floating panel, one slider per remote participant) |
wss://voidline.duckdns.org/livekit
(self-hosted, Azure 2 vCPU, 8GB RAM)https://voidline-vid.duckdns.org