Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

**Multi-seat headless game streaming for Windows using Moonlight/Apollo.**

MultiSeat lets you run multiple simultaneous Moonlight game-streaming sessions on a single Windows machine. Each "seat" gets its own isolated Windows user account, virtual display, virtual audio cable, and Apollo (Sunshine) streaming instance — all managed from a single web dashboard.
MultiSeat lets you run multiple simultaneous Moonlight game-streaming sessions on a single Windows machine. Each "seat" gets its own isolated Windows user account, virtual display, its own audio endpoint, and Apollo (Sunshine) streaming instance — all managed from a single web dashboard.

---

## How It Works

1. You create or link a Windows local account for each streaming seat.
2. MultiSeat provisions a seat: launches a dedicated Apollo process in the account's RDP session, attaches a virtual display (SudoVDA), and routes a virtual audio cable (VB-CABLE) to it.
2. MultiSeat provisions a seat: launches a dedicated Apollo process in the account's RDP session, attaches a virtual display (SudoVDA), and gives it its own audio endpoint (by default the session's RDP "Remote Audio"; under `SharedHost` audio, a VB-CABLE virtual cable).
3. The Moonlight client connects to the seat's Apollo instance using the host's IP and the seat's assigned port.
4. Each seat streams independently with isolated input, audio, and display.

Expand Down Expand Up @@ -205,7 +205,8 @@ Edit `appsettings.json` in `C:\Program Files\MultiSeat\` (restart the service af
| `ApolloLogLevel` | `info` | Apollo's own log level per seat. `debug` is the only way to see why a seat refuses a pairing or a client. Values: `verbose`, `debug`, `info`, `warning`, `error`. |
| `ApiPort` | `9550` | Dashboard port |
| `ApiKey` | *(auto-generated)* | API key required to access the dashboard. Auto-generated on first start and saved to `C:\ProgramData\MultiSeat\api-key.txt`. Set a fixed value here to override. Set to `disabled` to turn off authentication entirely (only safe on a fully trusted private network). |
| `VacCableCount` | `4` | Number of installed VB-CABLE devices |
| `AudioMode` | `PerSession` | `PerSession`: each seat uses its own RDP "Remote Audio" endpoint. No VB-CABLE or VoiceMeeter, no seat cap from audio, no microphone; set the client's "Play audio on host PC" **on**. `SharedHost`: host virtual cables (VB-CABLE + VoiceMeeter Potato), at most 4 seats, microphone works, client "Play audio on host PC" **off**. `SharedHost` can leave the host's own audio silent after a seat is provisioned; `scripts\fix-host-audio.ps1` repairs it. |
| `VacCableCount` | `4` | Number of installed VB-CABLE devices. Used only under `SharedHost` audio |
| `EnableKeyboardMouseIsolation` | `false` | Keyboard/mouse session isolation (no-op as architected — see Known Constraints in CLAUDE.md) |
| `EnableSharedGameLibrary` | `true` | Create a shared games/ROMs folder all seats can use |
| `SharedGameLibraryDir` | `C:\MultiSeatGames` | Root of the shared library (`\SteamLibrary` + `\ROMs`) |
Expand Down Expand Up @@ -328,7 +329,7 @@ MultiSeatService (Windows Service, SYSTEM)
├── SessionLauncher — RDP session + mstsc window management
├── ApolloManager — per-seat Apollo process management
├── VirtualDisplayManager — SudoVDA display attach/detach
├── AudioRouter — VB-CABLE assignment per seat
├── AudioRouter — VB-CABLE assignment per seat (`SharedHost` audio only)
├── InputRouter — XInput/ViGEm controller routing
├── HidHideConfigurator — controller cloaking
├── InputHookManager — keyboard/mouse session isolation
Expand Down Expand Up @@ -408,8 +409,8 @@ Controller isolation is handled by HidHide, not the InputHook DLL. Ensure HidHid
**RDPWrap shows "Not supported" after a Windows update**
Re-run `prerequisites\install-prerequisites.ps1` — it will fetch the latest `rdpwrap.ini` automatically.

**Multiple VB-CABLE devices needed**
Each seat requires one VB-CABLE. After installing the first one via the prerequisites script, run `VBCABLE_Setup_x64.exe` manually for each additional seat (found in the extracted `VBCABLE_Driver_Pack45.zip`).
**Multiple VB-CABLE devices needed** (`SharedHost` audio only)
Under `SharedHost`, each seat requires one VB-CABLE. The default `PerSession` mode needs none. After installing the first one via the prerequisites script, run `VBCABLE_Setup_x64.exe` manually for each additional seat (found in the extracted `VBCABLE_Driver_Pack45.zip`).

---

Expand Down
2 changes: 1 addition & 1 deletion REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ For each concurrent streaming seat:
|----------|-------------|
| Windows local account | One per seat (created/managed by MultiSeat, or link an existing account) |
| Virtual display | One SudoVDA display per seat |
| Virtual audio device | One per seat: VB-CABLE (seat 0) or VoiceMeeter virtual input (seats 1–3) |
| Virtual audio device | `SharedHost` audio only: one per seat, VB-CABLE (seat 0) or VoiceMeeter virtual input (seats 1–3). `PerSession` (default): none, each seat uses its own RDP Remote Audio endpoint |
| TCP/UDP ports | 30-port block per seat (default: 48100–48129, 48130–48159, ...) |
| Apollo config directory | Created automatically under `C:\ProgramData\MultiSeat\apollo\` |
| Emulator netplay port | One per seat: `PortBase + 13` (48113, 48143, ...); seats netplay over `127.0.0.1` |
Expand Down
23 changes: 16 additions & 7 deletions src/MultiSeat.Dashboard/src/pages/TipsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,27 @@ export function TipsPage() {
<Section title="Audio & Microphone">
<TipCard title="Game audio">
<p>
Each seat uses a dedicated virtual audio device (VB-CABLE or VoiceMeeter bus).
Apollo loopback-captures from that device so only that seat's game audio is streamed.
If audio is silent after provisioning, use <strong>Manage Services → Audio → Reset</strong>
on the seat card.
By default (<code>AudioMode = PerSession</code>) each seat uses its own RDP
<strong> Remote Audio</strong> endpoint. No VB-CABLE or VoiceMeeter is needed, and in
the Moonlight client <strong>Play audio on host PC</strong> should be <strong>on</strong>.
</p>
<p>
Under <code>SharedHost</code> audio, each seat instead uses a dedicated virtual audio
device (VB-CABLE or VoiceMeeter bus), and <strong>Play audio on host PC</strong> should
be <strong>off</strong>. If audio is silent after provisioning in that mode, use
<strong> Manage Services → Audio → Reset</strong> on the seat card.
</p>
</TipCard>

<TipCard title="Microphone passthrough">
<p>
ApolloVibe streams the Moonlight client's microphone into the seat session via
<strong> Steam Streaming Microphone</strong>. Games see it as a normal mic input — no
extra configuration required.
Microphone passthrough works only with <code>AudioMode = SharedHost</code>. The default,
<code>PerSession</code>, has no microphone path.
</p>
<p>
Under <code>SharedHost</code>, ApolloVibe streams the Moonlight client's microphone into
the seat session via <strong>Steam Streaming Microphone</strong>. Games see it as a
normal mic input — no extra configuration required.
</p>
<ul>
<li>Requires the standard Moonlight client (mic packets are supported in ApolloVibe)</li>
Expand Down
Loading