# How RomM Integrates RuffleRS for In-Browser Flash Playback

> Discover how RomM integrates RuffleRS for in-browser Flash playback. Learn about static asset serving, API interfaces, and dynamic script injection for seamless ROM streaming.

- Repository: [The RomM Project/romm](https://github.com/rommapp/romm)
- Tags: how-to-guide
- Published: 2026-07-05

---

**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:

```dockerfile

# 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`](https://github.com/rommapp/romm/blob/main/ruffle.js) via the path [`/assets/ruffle/ruffle.js`](https://github.com/rommapp/romm/blob/main//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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/Base.vue) and the modern v2 [`Ruffle.vue`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main//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:

```typescript
// 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.

```typescript
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:

```typescript
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`](https://github.com/rommapp/romm/blob/main/frontend/src/plugins/router.ts) at line 256. The v2 architecture lazy-loads the component via [`frontend/src/v2/router/routes.ts`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main/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`](https://github.com/rommapp/romm/blob/main//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`](https://github.com/rommapp/romm/blob/main/frontend/src/views/Player/RuffleRS/Base.vue) for the legacy v1 interface and [`frontend/src/v2/views/Player/Ruffle.vue`](https://github.com/rommapp/romm/blob/main/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.