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

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. The Server-side orchestration layer in 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 and coordinated by 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 and bundles the i18n t function with the current locale before invoking startRender.

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

Server-Side Routing and Proxy Logic

The server determines execution strategy via isRenderServiceConfigured() in lib/server/render-service.ts. This function checks for the RENDER_SERVICE_URL environment variable using getRenderServiceUrl().

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

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

{
  "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.

Summary

  • OpenMAIC uses a three-tier architecture separating UI (lib/video-export-app/use-render-video.ts), routing logic (lib/server/render-service.ts), and execution (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. 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 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 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.

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 →