@3dsource/angular-unreal-module

中文文档
0.0.200发布于 2026/11/10UNLICENSEDTypeScriptES Module近 30 天下载 3102

Angular integration for Unreal Engine Pixel Streaming

原始使用文档
文档目录11 节
  1. Requirements
  2. Installation
  3. Entry points
  4. Styling
  5. Setup
  6. 1. Register state and configuration once
  7. 2. Boot the engine on a lazy route
  8. 3. Render the scene
  9. Main API
  10. Optional prefetch scripts
  11. Repository development

@3dsource/angular-unreal-module

Standalone Angular integration for Unreal Engine Pixel Streaming. The package provides the scene component, WebRTC and signalling lifecycle, NgRx state, command/callback APIs, reconnection, file transfer and telemetry.

Requirements

  • Angular, Angular CDK and Angular Forms >=19.0.0 <23.0.0
  • NgRx Store and Effects >=19.0.0 <23.0.0
  • RxJS >=7.8.0 <8.0.0
  • @3dsource/types-unreal >=0.0.14 <0.1.0
  • provideHttpClient() in the host application

Installation

pnpm add @3dsource/angular-unreal-module @3dsource/types-unreal

The package is standalone and does not expose an NgModule.

Entry points

Entry pointContains
@3dsource/angular-unreal-moduleEverything — scene component, services, NgRx state, providers, helpers
@3dsource/angular-unreal-module/configUNREAL_CONFIG, UnrealInitialConfig, UnrealMode only — no engine, 266 B of FESM

Import the token from /config in the application root and keep every other import behind a lazy route — see Setup for why.

Styling

The package ships its own styles and requires no UI library. Everything it draws on top of the video is customisable through three levels — take the cheapest one that solves your case.

NeedLevelCost
different colour / radius / fontdesign tokens3 lines of CSS
your own look for a whole blockclass hooksone class in global styles
a different set of elements insideslotsone template

The three levels compose: tokens recolour what stays, a class hook restyles one block, a slot replaces what is inside it. A slot makes the class hook for the same part irrelevant — the markup inside is then entirely yours.

What you can customise

These are the blocks the scene draws over the video. Nothing else is themed — the video itself, and the dev-only overlays, are out of scope.

BlockWhen the user sees itToken-onlyClass hook partsSlot
Loading statuswhile connecting, shows percentage✓statusCard, statusMessageunrealStatusSlot
Resume / Startstream paused or not yet started✓resumeCard, resumeText, resumeButtonunrealResumeSlot
AFK timeoutinactivity countdown before drop✓afkScrim, afkCard, afkButtonunrealAfkSlot

Error dialogs (unreal-error-modal, webrtc-error-modal) are not part of this contract: they have no class hook, no slot, and they read the @3dsource/source-ui-native tokens (--src-*) rather than --unreal-*. They also render outside the scene DOM — see the note under Design tokens.

1. Design tokens

Set the --unreal-* variables on app-unreal-scene — custom properties inherit through the DOM and reach every block inside.

app-unreal-scene {
  --unreal-surface-card: #101014;
  --unreal-text-main: #f5f5f7;
  --unreal-radius-card: 16px;
}
TokenDefault
--unreal-surface-card#fff
--unreal-surface-screen#fff
--unreal-surface-scrimrgba(100, 100, 100, .7)
--unreal-text-main#1f2937
--unreal-text-secondary#6b7280
--unreal-font-familysystem-ui, sans-serif
--unreal-radius-card8px
--unreal-radius-pill9999px
--unreal-shadow-card0 26px 80px 0 rgba(0,0,0,.2), 0 0 1px 0 rgba(0,0,0,.2)
--unreal-accent#017bff
--unreal-accent-hover#016fe6
--unreal-accent-text#fff

If @3dsource/source-ui-native is loaded, its tokens are used automatically wherever you do not set an --unreal-* value.

Anything opened through CDK Dialog is out of reach. The error dialogs render in a separate overlay container, outside the scene DOM, so variables set on app-unreal-scene never reach them — declare those on :root. Note that those dialogs read --src-*, not --unreal-*.

2. Class hooks

Supply your own class for a part; it replaces the built-in decoration class. The positioning class stays, so the scene layout cannot break.

<app-unreal-scene [uiClasses]="{ resumeCard: 'my-card', statusCard: 'my-pill' }" />

Parts: resumeCard, resumeText, resumeButton, afkScrim, afkCard, afkButton, statusCard, statusMessage.

Your classes must live in global styles (styles.scss). Declared in a component's own SCSS, Angular scopes them and they never reach the scene.

The two button parts behave differently. resumeButton and afkButton put your class on the <app-unreal-button> host, but the <button> inside is a library component whose styles are scoped — and a scoped .unreal-button selector (0,2,0) outweighs a global .my-cta button (0,1,1). So the hook is good for the button's outer box (width, margin, alignment); to change the button itself, use the --unreal-accent* tokens, or a slot if you want different markup altogether.

afkScrim and --unreal-surface-scrim have one caveat. The package also ships a legacy partial, src/lib/styles/unreal.scss, which is not part of the published bundle and is not meant to be imported. It contains .frame #videoPlayOverlay { background-color: … } — an id selector that outweighs any class. If you import that partial anyway, it wins over both the class hook and the token for the AFK overlay background.

3. Slots

Replace the contents of a block with your own markup. The module keeps the wrapper and the visibility logic; you get the data through the template context.

<app-unreal-scene>
  <ng-template unrealResumeSlot let-ctx>
    <button (click)="ctx.start()" class="my-cta">{{ ctx.isSecondStart() ? 'Resume' : 'Start' }}</button>
  </ng-template>

  <ng-template unrealStatusSlot let-ctx>
    <my-progress [value]="ctx.percents()" />
  </ng-template>
</app-unreal-scene>
DirectiveContext
unrealResumeSlotisSecondStart, isAfkDisconnect, start()
unrealAfkSlotcountdown (seconds left), reset()
unrealStatusSlotpercents, message

All context values except methods are Signals — call them in the template. percents is undefined until the first value arrives — guard it if you render it raw.

The context object is created once and never replaced; what changes are the signals inside it. So let-ctx is stable and you can pass ctx around freely.

Markup inside a slot belongs to you, not to the library. Angular scopes a template to the component that declares it, and your slot template is declared in your component. Two consequences:

  • The library's own classes do nothing there. Writing class="resume-box__text" inside a slot will not pick up the module's styling — that class is scoped to the module.
  • Style slot content the way you style any of your own markup: your component's SCSS applies normally (no ::ng-deep, no global stylesheet needed — unlike class hooks).

The --unreal-* tokens do reach inside, because CSS custom properties inherit through the DOM. Read them if you want your markup to follow the same theme:

.my-cta {
  background: var(--unreal-accent, #017bff);
  border-radius: var(--unreal-radius-card, 8px);
}

Recipes

Dark theme, nothing else. One rule, no TypeScript:

app-unreal-scene {
  --unreal-surface-card: #101014;
  --unreal-surface-screen: #0b0b0f;
  --unreal-text-main: #f5f5f7;
  --unreal-text-secondary: #a1a1aa;
  --unreal-shadow-card: 0 20px 60px 0 rgb(0 0 0 / 60%);
}

Your brand's accent on the buttons. Buttons are recoloured with tokens, not with a class hook — see the note above:

app-unreal-scene {
  --unreal-accent: #ff5f6d;
  --unreal-accent-hover: #f0454f;
  --unreal-accent-text: #fff;
}

If you need a button that is shaped differently, not just recoloured, replace the whole block through unrealResumeSlot and render your own control.

A completely different loading status. Structure, not just skin — so this is a slot. Visibility is still decided by the module:

<app-unreal-scene>
  <ng-template unrealStatusSlot let-ctx>
    <my-brand-progress [value]="ctx.percents() ?? 0" />
  </ng-template>
</app-unreal-scene>

What you cannot change

  • When a block appears. Visibility stays with the module — the conditions are non-trivial (connection state, reconnect flags, AFK timers) and duplicating them in a host app is a reliable source of bugs.
  • Where a block sits in the scene. Each customisable element keeps a positioning class that a host class never replaces, so the scene layout cannot be broken from outside. Move the block by restyling its container instead.
  • The video element and dev overlays (video-stats, stat-graph).

Setup

1. Register state and configuration once

Add the Unreal feature state, HTTP client and configuration at the application root. UNREAL_CONFIG is required, although all its fields are optional.

import { provideHttpClient } from '@angular/common/http';
import type { ApplicationConfig } from '@angular/core';
import { provideStore } from '@ngrx/store';
import { provideUnrealState } from '@3dsource/angular-unreal-module';
import {
  UNREAL_CONFIG,
  type UnrealInitialConfig,
} from '@3dsource/angular-unreal-module/config';

const unrealConfig = {
  regionsPingUrl: 'https://datacenter.3dsource.com/regions/',
  dataChannelConnectionTimeout: 8000,
  fpsMonitor: false,
} satisfies UnrealInitialConfig;

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideStore(),
    provideUnrealState(),
    { provide: UNREAL_CONFIG, useValue: unrealConfig },
  ],
};

Omit provideStore() when the root NgRx store is already configured.

UNREAL_CONFIG comes from the /config entry point on purpose. The token has to be provided in the root injector, because the module's services are providedIn: 'root' singletons and cannot see a route-level provider. The package itself ships as one FESM, and esbuild places a module in the common-ancestor chunk of its importers — so importing the token from the package root here pulls the whole engine into the application's eager bundle. On a metabox production build that was 180 kB raw (49 kB gzip) of main spent on pages that never stream.

This pays off only while nothing else in the eager graph imports the package root. Following the example above literally does not qualify — the provideUnrealState() import next to it brings the FESM back. An app that wants the engine out of its initial bundle moves provideUnrealState() to the lazy route alongside provideUnrealModule() (step 2) and leaves only the UNREAL_CONFIG provider at the root.

Available configuration fields:

FieldPurpose
regionsPingUrlRegion latency endpoint
dataChannelConnectionTimeoutDataChannel connection timeout in ms
customErrorsEndpointCustom error reporting endpoint
streamTelemetryV2UrlStream lifecycle telemetry endpoint
screenLockerContainerIdContainer used by the screen-locker overlay
mode'metabox' (default) — full Metabox command protocol; 'default' — stock Pixel Streaming app, the module sends no Metabox commands of its own
fpsMonitorEnables FPS monitoring (currently off — the service is not instantiated)
disableFovRecalculationSends payloadVersion: disableFovRecalculation with every resolution change, so Unreal keeps the camera FOV (the studio scene always sends it)
playwrightEnables the test-specific service behaviour

Use { provide: UNREAL_CONFIG, useValue: {} } for the minimal configuration.

2. Boot the engine on a lazy route

Lazy-load the route file from the application router:

import type { Routes } from '@angular/router';

export const APP_ROUTES: Routes = [
  {
    path: 'stream',
    loadChildren: () =>
      import('./stream/stream.routes').then((module) => module.STREAM_ROUTES),
  },
];

Register provideUnrealModule() inside that lazy route file:

import type { Routes } from '@angular/router';
import { provideUnrealModule } from '@3dsource/angular-unreal-module';
import { StreamComponent } from './stream.component';

export const STREAM_ROUTES: Routes = [
  {
    path: '',
    component: StreamComponent,
    providers: [provideUnrealModule()],
  },
];

Keep provideUnrealState() and UNREAL_CONFIG at the application root. The loadChildren() boundary keeps the streaming engine out of the initial bundle. Route-scoping provideUnrealModule() tears down its effects when the route is left.

UNREAL_CONFIG on a route's providers does not work: the engine services are providedIn: 'root', so they are created by the root injector and read the token from there. A route-level value is invisible to them and inject(UNREAL_CONFIG, { optional: true }) resolves to null — e.g. the post-connection region re-ping never runs (empty regionsPingUrl), so without the prefetch script no region is ever cached and every request goes out without one. Importing only the token at the root does not pull the module into the initial bundle (the package is sideEffects: false).

3. Render the scene

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { UnrealSceneComponent } from '@3dsource/angular-unreal-module';

@Component({
  selector: 'app-stream',
  imports: [UnrealSceneComponent],
  template: `<app-unreal-scene />`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StreamComponent {}

UnrealSceneComponent also accepts isStudio, useContainerAsSizeProvider and resolutionSize inputs, and emits changeMouseOverScene.

Main API

  • provideUnrealState() — registers the unrealFeature NgRx state.
  • provideUnrealModule() — registers effects and boots streaming services.
  • UnrealSceneComponent — renders and manages the Pixel Streaming scene.
  • UnrealCommunicatorService — sends commands and UI interactions.
  • UnrealCallbackService — observes Unreal callbacks and command responses.
  • unrealFeature, exported selectors and actions — expose connection and scene lifecycle state.

Command packet types are provided by @3dsource/types-unreal.

That package ships two type contours, prod and QA, and your application and this package must resolve the same one — the selection is a tsconfig path override on the bare specifier, never an import. A bundler that ignores tsconfig paths (plain Vite or webpack without a matching resolve.alias) takes the types from one contour and the enum values from the other, which fails at runtime rather than at compile time. See "Two contours: prod and QA" in the @3dsource/types-unreal README.

Run pnpm demo:start from the repository root to see the scene component in the demo application.

Optional prefetch scripts

The package publishes two dependency-free scripts for use in the document <head> before Angular starts:

  • region-ping-prefetch.js measures regions early, caches a full result and publishes its live answer. The connection never waits for it: the request takes the cached region, else the prefetch's answer if it is already certain, else no region at all (the orchestrator then picks by geo-IP). Load it first in <head>; without it the cache is filled after the first connection.
  • stream-prefetch.js opens and parks an eligible WebRTC connection so Angular can adopt it after bootstrap.
<script
  src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/region-ping-prefetch.js"
  async
></script>
<script
  src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/stream-prefetch.js"
  async
></script>

stream-prefetch.js runs only on metabox-configurator/modular/{configuratorId} routes — the configurator id comes from the route and there is no attribute to supply one, so the script cannot be enabled for an arbitrary page.

It reads the same-origin assets/config.json and assets/features.json, then the configurator's cached record from {cdn}/cache/configurator/{id}.json — the same document the host app reads — so it polls under the identity the app will use rather than inventing one. The newOrchestration feature flag picks the transport: signalling (HTTP polling) or newSignalling (control socket + requestStream). The orchestration-issued streamRequestId is forwarded to Cirrus on the WebSocket URL (the session connectionId) and parked for adoption.

window.__stream is both the parked stream and the lock: whichever of the script and the Angular module writes it first owns the connection, so a page never builds two. Load the script and the package from the same version — an older script does not know about the lock.

Optional data-*: data-config-url, data-features-url, data-ws-timeout, data-poll-timeout.

Pin an exact package version in production when deterministic CDN assets are required.

Repository development

Run commands from the repository root:

pnpm unreal-module:build
pnpm unreal-module:build:watch
pnpm unreal-module:lint
pnpm unreal-module:test
pnpm unreal-module:test:watch
pnpm unreal-module:test:signalling
pnpm unreal-module:test:signalling:leaks

unreal-module:build builds the local types-unreal dependency before this package.

Release commands publish only this package:

pnpm unreal-module:release:patch
pnpm unreal-module:release:dev

After a successful publish, the release automatically purges the matching jsDelivr tag. Retry a failed purge without rerunning the release:

pnpm unreal-module:purge-cdn -- latest
pnpm unreal-module:purge-cdn -- dev

Repository tooling requires Node.js 24.16.0 or newer and pnpm last version.

版本与发布记录

@3dsource/angular-unreal-module 共发布 270 个版本,首次发布于 2025/05/03;过去 12 个月发布了 193 个版本。

版本发布日期距今
0.0.200latest2026/11/10今天
0.0.200-dev.4dev2026/11/063 天前
0.0.200-dev.32026/11/064 天前
0.0.200-dev.22026/11/064 天前
0.0.200-dev.12026/11/064 天前
0.0.200-dev.02026/11/064 天前

关于 @3dsource/angular-unreal-module 的常见问题

@3dsource/angular-unreal-module 是什么?

Angular integration for Unreal Engine Pixel Streaming

如何安装 @3dsource/angular-unreal-module?

在项目目录运行 npm install @3dsource/angular-unreal-module,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:rxjs(>=7.8.0 <8.0.0)、@ngrx/store(>=19.0.0 <23.0.0)、@angular/cdk(>=19.0.0 <23.0.0)、@angular/core(>=19.0.0 <23.0.0)、@ngrx/effects(>=19.0.0 <23.0.0)、@angular/common(>=19.0.0 <23.0.0)、@3dsource/types-unreal(>=0.0.14 <0.1.0)、@angular/platform-browser(>=19.0.0 <23.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@3dsource/angular-unreal-module 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@3dsource/angular-unreal-module 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@3dsource/angular-unreal-module 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@3dsource/angular-unreal-module 可以商用吗?

@3dsource/angular-unreal-module 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@3dsource/angular-unreal-module 的下载量有多少?

根据 npm 官方下载统计,@3dsource/angular-unreal-module 在 2026/10/05 – 2026/11/03 这 30 天内被下载 3,102 次,日均约 103 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@3dsource/angular-unreal-module 还在维护吗?

最新版本 0.0.200 发布于 2026/11/10(今天);至今共发布 270 个版本,其中过去 12 个月发布了 193 个;首次发布于 2025/05/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。