|
| DVD Player | `dvd-player` |
|
| e-Reader / Card-e Reader | `e-reader-slash-card-e-reader` |
|
+| EasyRPG | `rpg-maker` |
|
| ECD Micromind | `ecd-micromind` |
|
| EDSAC | `edsac` |
|
| Elektor TV Games Computer | `elektor` |
|
diff --git a/docs/troubleshooting/in-browser-play.md b/docs/troubleshooting/in-browser-play.md
index 5bd0cd7b..5ecf065d 100644
--- a/docs/troubleshooting/in-browser-play.md
+++ b/docs/troubleshooting/in-browser-play.md
@@ -7,10 +7,14 @@ description: Diagnose EmulatorJS and Ruffle issues
## EmulatorJS won't load at all
-- **On the slim image without internet?** The slim image fetches EmulatorJS cores from a CDN at runtime rather than bundling them, so without outbound network the container can't load games. Either switch to the full image (cores bundled) or open outbound access (see [Image Variants](../install/image-variants.md)).
+- **On the slim image without internet?** The slim image fetches EmulatorJS cores from a CDN at runtime rather than bundling them, so without outbound network the browser can't load games. Either switch to the full image (cores bundled) or open outbound access (see [Image Variants](../install/image-variants.md)). Ruffle and PICO-8 are full-image only.
- Check the **browser console** and look for 404s on `/assets/emulatorjs/...`, which indicate the EmulatorJS bundle didn't install correctly in the container. Check `docker logs romm` for entrypoint install-step failures.
- **Browser compatibility**: EmulatorJS uses SharedArrayBuffer, which needs a modern Chrome/Firefox/Safari and an HTTPS-served instance (cross-origin isolation requires HTTPS). If you're still on plain HTTP, set up TLS first (see [Reverse Proxy](../install/reverse-proxy.md)).
+## "This core needs a secure connection"
+
+Threaded cores need the `SharedArrayBuffer` API, and browsers only hand that out on a secure, cross-origin-isolated origin. Over plain HTTP the player now tells you this directly instead of throwing a generic error at you. Fix it by serving the app over `https://` (see [Reverse Proxy](../install/reverse-proxy.md)), or pick a single-threaded core for the platform. It comes up with the PSP core, with [`js-dos`](../using/in-browser-play/js-dos.md) for Windows 3.x and 9x, and with a few of the heavier EmulatorJS cores.
+
## Black screen or no audio
- **Core incompatibility.** Some cores have issues with specific ROMs. Try a different core via in-game Menu → **Core**.
diff --git a/docs/troubleshooting/netplay.md b/docs/troubleshooting/netplay.md
index dd1db0ee..0d04f9b3 100644
--- a/docs/troubleshooting/netplay.md
+++ b/docs/troubleshooting/netplay.md
@@ -9,10 +9,10 @@ Netplay uses WebRTC + ICE servers for peer-to-peer connections, so most issues a
## `Failed to start game`
-The most common error, and almost always the operator-side config:
+The most common error, and almost always the server owner config:
1. **Is Netplay enabled?** `emulatorjs.netplay.enabled: true` in `config.yml`.
-2. **Are ICE servers configured?** The operator needs at least one STUN server in `emulatorjs.netplay.ice_servers`. Without any, NAT traversal can't begin.
+2. **Are ICE servers configured?** The server owner needs at least one STUN server in `emulatorjs.netplay.ice_servers`. Without any, NAT traversal can't begin.
3. **ICE server URLs reachable?** RomM can't talk to `stun.l.google.com:19302` if your server has no outbound internet. Sounds silly but happens in air-gapped labs.
Full config: [Configuration File → `emulatorjs.netplay`](../reference/configuration-file.md#emulatorjsnetplay).
diff --git a/docs/using/account-and-profile.md b/docs/using/account-and-profile.md
index 1f526c04..5689b611 100644
--- a/docs/using/account-and-profile.md
+++ b/docs/using/account-and-profile.md
@@ -9,7 +9,7 @@ Every user (User or Admin) can manage their own profile, but Admins can edit _ot
## Preferred username in OIDC
-If you're an OIDC user and want to show your `preferred_username` from the token instead of your email local-part, the operator can set `OIDC_USERNAME_ATTRIBUTE=preferred_username` (see [OIDC Setup](../administration/oidc/index.md)).
+If you're an OIDC user and want to show your `preferred_username` from the token instead of your email local-part, the server owner can set `OIDC_USERNAME_ATTRIBUTE=preferred_username` (see [OIDC Setup](../administration/oidc/index.md)).
## Client API tokens
diff --git a/docs/using/emulator-streaming-migration.md b/docs/using/emulator-streaming-migration.md
index 0a2fdaca..9bc0dd3f 100644
--- a/docs/using/emulator-streaming-migration.md
+++ b/docs/using/emulator-streaming-migration.md
@@ -9,7 +9,7 @@ Emulator streaming used to mean one container per emulator, each running its own
!!! warning "The per-emulator broker mods are deprecated"
- A `config.yml` container without `protocol: webstation` still works today, logs a startup warning telling you to migrate, and will stop being supported in a future release.
+ A `config.yml` container without `protocol: webstation` still works today, but will stop being supported in a future release.
## Why
@@ -63,7 +63,7 @@ The full field reference lives in [Configuration File → `streaming`](../refere
2. Point `library_path` in `config.yml` at that same mount.
3. Move each platform you were streaming into the new container's `platforms:` map (see the before/after above).
4. Confirm streaming works for each platform, then remove the old per-emulator containers.
-5. Restart RomM. The startup warning about legacy containers disappears once no `config.yml` container is missing `protocol: webstation`.
+5. Restart RomM. Every container is on the new shape once none of them is missing `protocol: webstation`.
## Per-emulator notes
diff --git a/docs/using/emulator-streaming.md b/docs/using/emulator-streaming.md
index 3e9c7e60..d5f17922 100644
--- a/docs/using/emulator-streaming.md
+++ b/docs/using/emulator-streaming.md
@@ -5,88 +5,169 @@ description: Launch games into a native emulator running in a container
# Emulator Streaming
-Emulator streaming launches a game into a **native** emulator running in a separate container and streams the picture, sound, and input back to your browser. Unlike [in-browser play](in-browser-play/emulatorjs.md), the emulation runs server-side on real emulator binaries (PCSX2, Dolphin, xemu), so the heavy lifting happens on the host rather than in the client. The server claims a session, tells the emulator which ROM to launch, and shows the live stream inside a player view, with save-state and volume controls in the toolbar.
+Emulator streaming runs your game in a **real** emulator on the server and streams the video, audio and input to your browser. Where [in-browser play](in-browser-play/emulatorjs.md) compiles emulators to WebAssembly and runs them on your machine, this runs actual PCSX2, Dolphin, RPCS3 and RetroArch binaries on the host. Your browser is just a screen.
-Each emulator runs in its own [linuxserver](https://docs.linuxserver.io) container with a [Selkies](https://github.com/selkies-project/selkies) WebRTC stream and a small HTTP broker sidecar that RomM talks to. Nothing appears in the UI until you configure at least one container.
+It all runs in one [docker-webstation](https://github.com/linuxserver/docker-webstation) container: a [Selkies](https://github.com/selkies-project/selkies) WebRTC desktop with the emulators installed, plus the [romm-broker](https://github.com/romm-streaming/romm-broker) sidecar RomM sends commands to. A single container handles **every** platform you point at it. Until you configure one, none of this shows up in the UI.
-
-!!! info "Already running per-emulator containers?"
- Streaming is moving to a single webstation container that serves every platform. See [Migrating to webstation](emulator-streaming-migration.md).
+If a platform can do both, you get separate actions for browser play and streaming, so turning on streaming doesn't take browser play away.
-!!! warning "Work in progress"
- This is the first release of the streaming framework and ships with three emulators. More integrations (rpcs3 for PS3, and others) are planned as separate follow-ups.
+!!! info "Coming from the per-emulator broker mods?"
+ A container without `protocol: webstation` still works, but will stop being supported in a future release. See [Migrating to webstation](emulator-streaming-migration.md) for the config rewrite.
+
+## How a session works
+
+There's one display per container, so a container runs **one session at a time** no matter which platform it's playing. Sessions are stored in [Valkey](../install/redis-or-valkey.md) and claimed atomically, which keeps multiple API workers from stepping on each other. Whoever claims a session owns it, and only they or an admin can control or release it.
+
+List the same platform on several containers and you get a **pool**. RomM walks them in config order and grabs the first free one, so two people can play SNES at once if you've got two containers. If they're all busy, RomM checks for sessions whose heartbeat has gone quiet (someone closed a tab, a browser crashed) and clears those out before telling you the platform is in use.
-## Supported emulators
+Containers only pool together if they agree on `emulator`, `memory_card_sync` and `protocol`. Those three decide where saves end up and which controls the player offers, and it would be a bad surprise to land on a pool member and find your saves missing. Containers that differ are treated as separate setups.
-Each emulator ships as a companion Docker mod repo with the broker sidecar and a worked `docker-compose.yml`.
+## Supported platforms
-| Platform slug | Emulator | Save states | Manual slots | Autosave slot | Broker repo |
-| ------------- | -------- | ----------- | ------------ | ------------- | ------------------------------------------------------------------------------------- |
-| `ps2` | PCSX2 | Yes | 9 | Slot 10 | [pcsx2-romm-integration](https://github.com/LoneAngelFayt/pcsx2-romm-integration) |
-| `ngc`, `wii` | Dolphin | Yes | 7 | Slot 8 | [dolphin-romm-integration](https://github.com/LoneAngelFayt/dolphin-romm-integration) |
-| `xbox` | xemu | Yes | 9 | Slot 10 | [xemu-romm-integration](https://github.com/LoneAngelFayt/xemu-romm-integration) |
+The broker ships standalone emulators for the platforms below, and RetroArch for everything else.
-The broker launches ROMs as direct files, so **archive extraction is not supported**.
+| Platform slug | Emulator | Save states | Memory card | Disc swap |
+| ------------------- | ----------- | --------------------------- | ----------- | --------- |
+| `ps2` | PCSX2 | Slots 1-9, autosave slot 10 | Yes | Manual |
+| `ngc` | Dolphin | Slots 1-7, autosave slot 8 | Yes | - |
+| `wii` | Dolphin | Slots 1-7, autosave slot 8 | - | - |
+| `psx` | DuckStation | One resume state | - | - |
+| `ps3` | RPCS3 | One resume state | - | - |
+| `xbox` | xemu | - | - | - |
+| `xbox360` | Xenia | - | - | - |
+| `wiiu` | Cemu | - | - | - |
+| `switch` | Eden | - | - | - |
+| `3ds` | Azahar | - | - | - |
+| `ps4` | shadPS4 | - | - | - |
+| `psp` | PPSSPP | - | - | - |
+| `dc` | Flycast | - | - | Yes |
+| _(anything else)_ | RetroArch | One resume state | - | Varies |
+| _(adventure games)_ | ScummVM | One resume state | - | - |
-Only **one session per platform** can be active at a time, since there is a single emulator container behind it. Sessions are stored in [Valkey](../install/redis-or-valkey.md) with an atomic claim, so multiple workers stay consistent. The session is bound to the user who claimed it, and only that owner or an admin can control or release it, though an admin can force-release a stuck session.
+RetroArch covers dozens of platforms from the one container. The broker picks the core, not RomM, and RomM just labels the action with whatever core that is (`RA Snes9x`, `RA mGBA`). If you want to change the mapping it's the broker's `retroarch_platforms.json`.
+
+ROMs are launched as plain files, so **archives won't work**. Extract them first.
## Saves and save states
-**Save states** are the emulator's own quick-save slots: numbered manual slots plus a dedicated autosave slot per platform (see the table above). The autosave slot is reserved for **Save & Exit** and is overwritten on the next exit.
+Three separate things move between the container and your library. Which ones apply depends on the platform.
+
+### Save states
+
+These are the emulator's own snapshots, and the table above says which of three shapes each platform gets:
+
+- **Numbered slots with an autosave.** The autosave slot belongs to save-and-exit and is overwritten every time you exit, so don't keep anything there.
+- **A single resume state.** These emulators only write a state as they shut down, so there's no grid to pick from, just the one state that saving and resuming both use.
+- **No states at all.** You rely on the game's own save data instead.
-Each state is copied off the container into your library as it is written, with a thumbnail alongside it, and the state written by **Save & Exit** is filed when the session ends. Your stored states are offered the next time you launch that game, so you can carry on from one instead of booting fresh. RomM keeps the newest states per game, emulator, and user, and prunes the rest past `STREAMING_STATE_HISTORY_LIMIT` (default `50`, `0` to keep everything).
+Whenever a state is written, RomM copies it off the container, along with a thumbnail grabbed from the video. The state from save-and-exit is collected when the session closes. Claim a container later and RomM pushes your stored states back onto it, which is why they follow you between containers and survive a container being rebuilt.
-These streaming states are stored separately from RomM's [per-user saves and states](saves-and-states.md) from in-browser play.
+RomM keeps the most recent `STREAMING_STATE_HISTORY_LIMIT` states per game, per emulator, per user (default `50`, or `0` to keep everything) and prunes the rest.
-## Memory cards
+These are kept apart from the [saves and states](saves-and-states.md) that in-browser play produces, because the formats aren't interchangeable.
-On platforms with a memory card (**PS2** and **GameCube**), you can opt a container into whole-card sync with `memory_card_sync: true`. The card then lives in your RomM library rather than on the container: RomM loads your card in when a session starts, copies it back when you exit, and leaves the container's slot blank in between. Your most recently used card for that emulator loads by default, and you can keep several cards and pick which one a session mounts.
+### In-game saves
-PCSX2 only serves its card when Slot 1 holds a **Folder** card rather than a **File** card. With a File card the broker refuses the transfer and the session will not start, so set the card type before turning the flag on (see the [PCSX2 broker's memory card setup](https://github.com/LoneAngelFayt/pcsx2-romm-integration#memory-card-setup)). Dolphin's broker pins its own folder card, so GameCube needs no extra setup.
+This is the save data the game itself writes: NAND, battery saves, the emulated user profile. When a session ends, RomM zips the lot off the container and stores it as a single save, so it all stays together. On platforms with no save states, this is the only thing keeping your progress.
-Because each session starts from your library, the first time RomM uses a container that already has a card on it, it stops and asks what to do:
+### Memory cards
-- Importing it stores the container's card in your library, as a new version of your current card for that emulator, or as your first card if you have none.
-- Starting fresh erases the container's card, which is confirmed first because it cannot be undone.
+On **PS2** and **GameCube** you can set `memory_card_sync: true` and have RomM manage the whole card instead of individual save files. The card lives in your library: RomM loads it in when a session starts, copies it back when you exit, and leaves the container's slot empty in between. This **replaces** the in-game save handling above for that container.
-RomM records your answer per container, so it only asks once.
+Cards are their own little library. Keep as many as you like, name and rename them, snapshot the current state as a version and roll back to it later, share one with another user, or download it. Whichever card you used last loads by default.
+
+Set the flag on a platform with no memory card (Wii, xemu) and RomM logs a warning and ignores it, syncing individual save files instead. Obeying it would mean shuffling an empty card back and forth while the saves those platforms actually use stopped syncing.
+
+PCSX2 needs Slot 1 set to a **Folder** card, not a **File** card. Given a File card, the broker refuses to hand it over and the session won't start at all. Change the card type before you turn the flag on (see the [PCSX2 memory card setup](https://github.com/LoneAngelFayt/pcsx2-romm-integration#memory-card-setup)). Dolphin pins its own folder card, and GameCube works out of the box.
+
+Every session starts from your library's copy, so the first time RomM meets a container that already has a card sitting on it, it stops and asks:
+
+- **Import it**, and the container's card goes into your library, either as a new version of your current card or as your first one.
+- **Start fresh**, and the container's card is wiped. You get a confirmation first, since there's no undo.
+
+RomM remembers your answer per container and won't ask again.
!!! warning "Playing on the container directly"
- Syncing only happens around a RomM streaming session. If you play on the emulator container directly, outside RomM, those saves and cards stay on the container and are not pulled into your library. A later RomM session loads your library's copy over them, so make your progress through RomM to keep it.
+ Syncing only happens at the start and end of a RomM session. Play on the container directly, outside RomM, and those saves and cards never reach your library. Worse, the next RomM session will write your library's copy over them. Go through RomM if you want to keep the progress.
+
+## Multi-disc games
+
+Multi-disc games boot their full playlist. On `dc`, `saturn`, `segacd`, `turbografx-cd` and `dos` you can change discs without restarting the emulator, choosing from that game's own discs.
+
+RomM records which disc was mounted when you saved a state and remounts the same one when you load it, so a save on disc 3 comes back on disc 3.
+
+PS2 has no swap control, but you can change discs from inside PCSX2 itself.
+
+## Joining a session
+
+You can open a session up to a second player. Anyone can see which sessions are joinable and ask to join. Joining hooks into the running session rather than claiming a container of its own, so it doesn't eat a second one.
+
+## Administration
+
+Admins get two extra things:
+
+**Desktop sessions** give you the container's desktop with no game running. This is how you set an emulator up from the inside: BIOS paths, controllers, whatever per-emulator settings it needs. A desktop takes the same lock a game does, so it blocks players out and a running game blocks you out.
+
+**The container fleet** shows every container you've configured, what it's running and who claimed it, and lets you force-release any session. Rows are per container, not per platform, since a container serving five platforms still only holds one session. Anything RomM can't claim (a `host` missing its scheme, a broker it can't reach) shows as unconfigured rather than idle, so a typo is visible instead of silent.
+
+Force-release is also your way out when a platform is stuck as in-use because someone closed their browser without releasing it.
## Setup
-### Run the emulator containers
+### Run the webstation container
+
+Start [docker-webstation](https://github.com/linuxserver/docker-webstation) with [romm-broker](https://github.com/romm-streaming/romm-broker), mounting your ROM library read-only. Two things on it matter to RomM:
+
+- The **Selkies web UI**, which is what your browser loads the stream from.
+- The **broker API**, which is where RomM sends launch, save, state and disc commands. It's served under the container's `SUBFOLDER` on the same origin.
-Pick the broker repos for the platforms you want and follow each repo's `docker-compose.yml`. Each container exposes two things we need:
+There's a working compose file at [`docker-compose.streaming.yml`](https://github.com/rommapp/romm/blob/master/docker-compose.streaming.yml) upstream. Note the image is amd64 only.
-- The **Selkies web UI** the browser loads the stream from (an HTTPS port).
-- The **broker API** RomM sends launch, save, and volume commands to (default port `8000`).
+### Set up `config.yml`
-### Setup `config.yml`
+Add a `streaming` block with **one entry per container** (full schema in [Configuration File → `streaming`](../reference/configuration-file.md#streaming)). Whatever you set at the container level applies to every platform it serves, and a platform block only needs to name the things that differ.
-Add a `streaming` block with one entry per emulator container, full schema in [Configuration File → `streaming`](../reference/configuration-file.md#streaming).
+```yaml
+streaming:
+ enabled: true
+ containers:
+ - protocol: webstation
+ host: https://192.168.1.56:3010
+ subfolder: /streaming
+ label: Emulation station
+ platforms:
+ snes: retroarch # just the emulator name...
+ ps2: # ...or a block overriding container keys
+ emulator: pcsx2
+ memory_card_sync: true
+```
-- `host` must be reachable from clients and served over **HTTPS** (Selkies WebRTC requires a secure context). Use the container's built-in self-signed cert or a [reverse proxy with TLS](../install/reverse-proxy.md).
-- `broker_host` is called server-side, so HTTP is fine. If the containers share a Docker network, use the container name (e.g. `http://pcsx2:8000`). If `broker_host` is omitted, it gets derived from `host`.
-- `label` is the text shown on the play action.
-- `memory_card_sync: true` opts a **PS2** or **GameCube** container into whole-card sync (see [Memory cards](#memory-cards)). It has no effect on platforms without a memory card.
-- `library_path` overrides the in-container library path if the container mounts the RomM library somewhere other than the default `/romm/library`.
-- `emulator` sets an explicit name used to group this container's states and memory cards, lowercased. It defaults to `label`, then the platform slug, so setting it keeps stored states and cards attached when you rename a label later.
+Four of those keys have consequences worth knowing before you pick their values:
-Multiple platforms can share one container (point `ngc` and `wii` at the same Dolphin instance) or each use their own.
+- `host` is what the browser connects to, and it has to be **HTTPS**: Selkies WebRTC won't run without a secure context. Use the container's self-signed cert, or put it behind a [reverse proxy with TLS](../install/reverse-proxy.md). A path like `/streaming` works if you've proxied the container onto RomM's own origin, but then you must set `broker_host` yourself, because a bare path gives RomM no address to call.
+- `broker_host` is only ever called server to server, so plain HTTP is fine. Pooled containers are identified by it, so two serving the same platform need different ones.
+- `library_path` is where the container sees your library. Don't change it casually, since your state and save history is keyed to it.
+- The per-platform `emulator` names what that platform's states and memory cards are filed under, so renaming it later orphans everything stored under the old name. A container-level `emulator` is ignored whenever `platforms` is used.
### Set the shared secret
-`STREAMING_BROKER_SECRET` authenticates calls to the broker. Set the **same value** in every container. If a broker needs a different secret, set `broker_secret` on that entry in `config.yml` and leave `STREAMING_BROKER_SECRET` unset, because the env var wins over the per-container value whenever it carries one.
+`STREAMING_BROKER_SECRET` is what authenticates RomM to the broker, and it has to match the container's `BROKER_SECRET`. Use the **same value** everywhere.
+
+If one broker needs its own secret, put `broker_secret` on that entry in `config.yml` and leave `STREAMING_BROKER_SECRET` unset entirely. The env var beats the per-container value whenever it's set, so you can't mix the two.
+
+### Tune the timeouts
+
+Three env vars bound how long streaming waits, listed with their defaults in [Environment Variables → Emulator Streaming](../reference/environment-variables.md#emulator-streaming).
-If a broker's save wait exceeds the default 45 seconds, raise `STREAMING_SAVE_TIMEOUT` (seconds) so Save & Exit doesn't time out. `STREAMING_STATE_HISTORY_LIMIT` (default `50`) caps how many save states RomM keeps per game, emulator, and user before pruning the oldest, and `0` keeps every state. All are set as env vars (see [Environment Variables](../reference/environment-variables.md)).
+`STREAMING_LAUNCH_TIMEOUT` covers the whole launch, including any unpacking before the emulator starts. `STREAMING_SAVE_TIMEOUT` covers save-and-exit, and needs raising if a broker's own `SAVE_WAIT` is higher. `STREAMING_STATE_HISTORY_LIMIT` caps how many states are kept per ROM, emulator and user.
## Troubleshooting
-- **No Play on `