# How the OpenMAIC Render Service Facilitates Video Export: Architecture and Implementation

> Learn how the OpenMAIC render service simplifies video export with its three-tier pipeline. Discover the architecture and implementation for efficient MP4 rendering and UI responsiveness.

- Repository: [MAIC/OpenMAIC](https://github.com/THU-MAIC/OpenMAIC)
- Tags: architecture
- Published: 2026-09-12

---

**The OpenMAIC render service facilitates video export by orchestrating a three-tier pipeline—React client hooks, server-side proxy logic, and an isolated containerized executor—to stream MP4 rendering progress from HyperFrames producers while maintaining UI responsiveness.**

OpenMAIC separates video export into distinct client, server, and service layers to ensure scalable, cancellable rendering workflows. This architecture delegates CPU-intensive MP4 generation to an isolated container while preserving real-time progress tracking through a global state store that survives component unmounts.

## Architectural Layers of the Export Pipeline

The system partitions responsibility across three logical tiers. The **Client UI** manages user interaction through React hooks defined in [`lib/video-export-app/use-render-video.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/video-export-app/use-render-video.ts). The **Server-side orchestration** layer in [`lib/server/render-service.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/render-service.ts) detects whether a dedicated render service is available via the `RENDER_SERVICE_URL` environment variable. Finally, the **Render Service**—an isolated container implemented in [`render-service/src/render-executor.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/src/render-executor.ts) and coordinated by [`render-service/src/render-coordinator.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/src/render-coordinator.ts)—executes the actual HyperFrames production using the `InProcessExecutor` class.

## Client-Side Integration with useRenderVideo

Components initiate exports through the **`useRenderVideo`** hook. This hook proxies the global **video-render store** (`useVideoRenderStore`) from [`lib/store/video-render.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/video-render.ts) and bundles the i18n `t` function with the current locale before invoking `startRender`.

```typescript
// lib/video-export-app/use-render-video.ts
import { useVideoRenderStore } from '@/lib/store/video-render';

export function useRenderVideo() {
  const store = useVideoRenderStore();
  
  return {
    rendering: store.status === 'rendering',
    percent: store.percent,
    etaMs: store.etaMs,
    options: store.options,
    setOptions: store.setOptions,
    renderVideo: () => store.startRender(),
  };
}

```

The hook reads mutable state—including `status` (`'idle' | 'compiling' | 'rendering' | 'failed' | 'succeeded'`), `percent`, `etaMs`, and export `options` (fps, quality, format)—that persists across UI unmounts. This ensures the export ring remains visible during navigation, as implemented in components like [`components/stage/video-export-dialog.tsx`](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/stage/video-export-dialog.tsx).

## Server-Side Routing and Proxy Logic

The server determines execution strategy via **`isRenderServiceConfigured()`** in [`lib/server/render-service.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/render-service.ts). This function checks for the `RENDER_SERVICE_URL` environment variable using `getRenderServiceUrl()`.

```typescript
// lib/server/render-service.ts
export function isRenderServiceConfigured(): boolean {
  return getRenderServiceUrl() !== null;
}

```

When configured, the request is proxied to the isolated service endpoint (e.g., `http://render-service:9000/render`). If unconfigured, the system falls back to a ZIP download, allowing users to run a local CLI render. The `/health` endpoint enables the UI to query service capability without causing hard failures.

```typescript
// Simplified server-side handler pattern
import { resolveRenderServiceUrl } from '@/lib/server/render-service';
import { proxyFetch } from '@/lib/server/proxy-fetch';

export default async function handler(req, res) {
  const svc = resolveRenderServiceUrl();
  if (svc.error) return res.status(503).json({ error: 'Render service not configured' });

  const resp = await proxyFetch(`${svc.url}/render`, {
    method: 'POST',
    body: JSON.stringify(req.body),
    signal: req.signal,
  });

  res.status(resp.status);
  resp.body?.pipeTo(res);
}

```

## Containerized Execution with InProcessExecutor

Inside the isolated container, the **`InProcessExecutor`** class orchestrates actual MP4 generation. It implements the `RenderExecutor` interface and manages the HyperFrames producer lifecycle through a tiny HTTP API exposing `/render` and `/health` endpoints.

### Job Creation and Execution Flow

The executor follows a strict sequence:

1. **Create a producer job** via `createRenderJob`, injecting user options such as `fps`, `quality`, and `format`.
2. **Execute the job** through `executeRenderJob` against the project directory, streaming progress via the `onProgress` callback.
3. **Collect performance data** into a `RenderPerfSummary` object, translated to `RenderPerformanceSummary` for UI consumption.

For large projects, the executor delegates to **`executeRenderChunks`**, which parallelizes work across multiple workers, respects deadline constraints, and responds to `AbortSignal` cancellation.

### Error Handling and Status Codes

The service provides granular error classification for precise failure handling:

- **`'cancelled'`**: Triggered when the client aborts via `AbortSignal`.
- **`'deadline_exceeded'`**: Returned when rendering exceeds the configured timeout, with `code: 'deadline_exceeded'`.
- **`'unsupported_capture_mode'`**: Raised when the project lacks required `beginframe` markers, returning `code: 'unsupported_capture_mode'`.

All responses include a **`metrics`** object detailing runtime versions, resource profiles, and actual worker counts, which the UI can display for diagnostics.

## Real-Time Progress Streaming

Progress flows from the container back to the client through streaming JSON objects. The server pipes these updates via `proxyFetch`, allowing the Zustand store to update without polling.

```json
{
  "progress": 45,
  "stage": "rendering",
  "framesRendered": 450,
  "totalFrames": 1000
}

```

The **`onProgress`** handler in `useVideoRenderStore` updates `percent` and `etaMs`, while UI components read these values to render progress bars and time estimates in dialogs like [`components/stage/video-export-dialog.tsx`](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/stage/video-export-dialog.tsx).

## Summary

- OpenMAIC uses a **three-tier architecture** separating UI ([`lib/video-export-app/use-render-video.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/video-export-app/use-render-video.ts)), routing logic ([`lib/server/render-service.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/render-service.ts)), and execution ([`render-service/src/render-executor.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/src/render-executor.ts)).
- The **`useRenderVideo`** hook provides the primary interface for starting exports while maintaining persistent state across the application.
- Server-side logic decides between micro-service proxying and ZIP fallback based on the presence of `RENDER_SERVICE_URL`.
- The **`InProcessExecutor`** handles actual MP4 generation with support for chunked parallel processing via `executeRenderChunks`.
- **Progress streaming** maintains UI responsiveness through persistent state in `useVideoRenderStore`.
- Explicit error codes (`cancelled`, `deadline_exceeded`, `unsupported_capture_mode`) enable precise failure handling and user feedback.

## Frequently Asked Questions

### What happens if the render service is not configured?

If `RENDER_SERVICE_URL` is unset, `isRenderServiceConfigured()` returns false and the server falls back to generating a ZIP download, as shown in [`lib/server/render-service.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/server/render-service.ts). Users then run the render locally via CLI tools while the UI continues to display export options and status indicators.

### How does OpenMAIC handle large video projects?

For large projects, the `InProcessExecutor` in [`render-service/src/render-executor.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/src/render-executor.ts) invokes `executeRenderChunks`, which splits the workload across parallel workers. This method respects deadline constraints and cancels via `AbortSignal`, preventing resource exhaustion on massive timelines.

### Can users cancel a video export once it starts?

Yes. The client can abort the fetch request, triggering an `AbortSignal` that the `InProcessExecutor` monitors continuously. When detected, the service immediately halts the HyperFrames producer and returns a status of `'cancelled'` with a clear termination message.

### What performance metrics does the render service provide?

Every render completion includes a **`metrics`** object inside the response. According to the [`render-service/src/render-executor.ts`](https://github.com/THU-MAIC/OpenMAIC/blob/main/render-service/src/render-executor.ts) implementation, this contains runtime versions, the resource profile used, and actual worker counts, which the UI translates into a `RenderPerformanceSummary` for diagnostic display and optimization.