How RomM Integrates RuffleRS for In-Browser Flash Playback

RomM embeds the RuffleRS Flash emulator by serving static assets from its container, declaring TypeScript interfaces for the player API, and mounting Vue components that dynamically inject the Ruffle script to stream ROM files directly in the browser.

The open-source ROM manager RomM (repository: rommapp/romm) delivers self-contained Flash game playback without requiring browser plugins or external emulators. According to the RomM source code, the integration relies on three coordinated layers: static asset delivery, type-safe TypeScript declarations, and Vue-based player components that handle script injection and ROM loading.

Static Asset Delivery

RomM ships with the pre-built Ruffle web player embedded in its container image. During the Docker build process, the Ruffle JavaScript and supporting files are copied into the assets/ruffle/ directory inside the container.

In docker/Dockerfile (lines 124-129), the build stage copies these files:


# Copy ruffle assets

COPY --from=swaggerapi/swagger-codegen-cli-v3:latest /assets/ruffle/ /assets/ruffle/

When the RomM server starts, these assets are served as static files from the same origin as the application. This allows the frontend to load ruffle.js via the path /assets/ruffle/ruffle.js, ensuring reliable access without depending on external CDNs.

TypeScript Type Definitions

To maintain type safety across the Vue components, RomM includes a minimal TypeScript declaration file at frontend/src/types/ruffle.d.ts. This file exposes the global window.RufflePlayer object and defines the RuffleSourceAPI interface used by the player components.

The type definitions allow the frontend to interact with the dynamically loaded Ruffle API while maintaining compile-time type checking for methods like newest() and createPlayer().

Vue Player Components

RomM implements the Flash player through two Vue components: the legacy v1 Base.vue and the modern v2 Ruffle.vue. Both components share the same core integration pattern but target different UI frameworks within the application.

Script Loading Strategy

On component mount, the Vue components inject a <script> tag pointing to the self-hosted /assets/ruffle/ruffle.js. If the self-hosted script fails to load—such as when the assets are missing or the server is unreachable—the components implement a fallback mechanism that loads the script from the unpkg CDN.

This dual-loading strategy appears in both implementations:

// Insert the self-hosted script
const script = document.createElement('script')
script.src = '/assets/ruffle/ruffle.js'

// Fallback to CDN if self-hosted fails
script.onerror = () => {
  const fallback = document.createElement('script')
  fallback.src = `https://unpkg.com/@ruffle-rs/ruffle@${RUFFLE_VERSION}/ruffle.js`
  document.body.appendChild(fallback)
}
document.body.appendChild(script)

Player Initialization

Once the script loads, the components access the Ruffle API through window.RufflePlayer.newest(). This returns the latest Ruffle source object, which provides the createPlayer() factory method. The components create a player instance and mount it into a container element—#game in the v1 implementation or #r-v2-ruffle-stage in the v2 version.

const ruffle = window.RufflePlayer.newest()
if (!ruffle) return

const player = ruffle.createPlayer()
const container = document.getElementById('r-v2-ruffle-stage')
container?.appendChild(player)

ROM Loading and Configuration

After initializing the player, the component loads the ROM file using RomM's download API. The getDownloadPath function generates the authenticated URL for the ROM file, while the publicPath parameter ensures Ruffle can resolve its own internal resources relative to the /assets/ruffle/ directory.

The player configuration includes user preferences persisted in localStorage, such as the background color, and gameplay settings like autoplay and fullscreen behavior:

player.load({
  url: getDownloadPath({ rom }),
  publicPath: '/assets/ruffle/',
  backgroundColor: userBackgroundColor,
  allowFullScreen: true,
  autoplay: 'on',
  forceAlign: true,
  forceScale: true,
  letterbox: 'on',
  openUrlMode: 'confirm',
})

The background color is stored per-ROM using a key pattern like player:ruffle:${rom.id}:backgroundColor, allowing individual games to remember user preferences across sessions.

Routing Configuration

The Ruffle player is accessible through the route /rom/:rom/ruffle, defined in frontend/src/plugins/router.ts at line 256. The v2 architecture lazy-loads the component via frontend/src/v2/router/routes.ts (line 39), ensuring the Flash emulator code is only downloaded when users attempt to play a Flash-based ROM.

This routing structure integrates the player into RomM's existing game library interface, allowing users to launch Flash games directly from their browser without leaving the application.

Summary

  • Static assets: RomM copies the Ruffle web player into assets/ruffle/ during the Docker build process and serves them as same-origin static files.
  • Type safety: The frontend/src/types/ruffle.d.ts file provides TypeScript definitions for window.RufflePlayer and the Ruffle source API.
  • Dual loading: Vue components attempt to load Ruffle from the self-hosted path first, falling back to the unpkg CDN if the local assets fail.
  • Player creation: Components call window.RufflePlayer.newest().createPlayer() to instantiate the emulator and mount it to a DOM container.
  • ROM streaming: The load() method receives the ROM URL from getDownloadPath and the publicPath pointing to /assets/ruffle/ for resource resolution.
  • User preferences: Background colors and display settings are persisted in localStorage using ROM-specific keys.
  • Route integration: The player is available at /rom/:rom/ruffle with lazy loading support in the v2 frontend architecture.

Frequently Asked Questions

How does RomM handle Ruffle script loading failures?

If the self-hosted script at /assets/ruffle/ruffle.js fails to load, the Vue components catch the error event and inject a fallback script pointing to https://unpkg.com/@ruffle-rs/ruffle@${RUFFLE_VERSION}/ruffle.js. This ensures Flash playback continues even if the local assets are missing or the server is unreachable.

Where does RomM store the Ruffle emulator files?

The Ruffle files are stored in the assets/ruffle/ directory inside the container, copied there during the Docker build process as defined in docker/Dockerfile (lines 124-129). The frontend accesses these via the /assets/ruffle/ URL path.

Can users customize the Flash player appearance in RomM?

Yes. Users can select a background color for the Flash player, which RomM persists in localStorage using a unique key for each ROM (player:ruffle:${rom.id}:backgroundColor). The component also supports toggling fullscreen mode and respects settings for autoplay, letterboxing, and URL opening behavior.

Which RomM frontend components manage the Ruffle integration?

The integration is handled by frontend/src/views/Player/RuffleRS/Base.vue for the legacy v1 interface and frontend/src/v2/views/Player/Ruffle.vue for the modern v2 interface. Both components implement the same script injection and player initialization logic but target different UI frameworks within the application.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →