@3dsource/angular-unreal-module 0.0.171 → 0.0.172

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,229 +1,421 @@
1
- # @3dsource/angular-unreal-module
2
-
3
- Standalone Angular integration for Unreal Engine Pixel Streaming. The package
4
- provides the scene component, WebRTC and signalling lifecycle, NgRx state,
5
- command/callback APIs, reconnection, file transfer and telemetry.
6
-
7
- ## Requirements
8
-
9
- - Angular, Angular CDK and Angular Forms `>=19.0.0 <23.0.0`
10
- - NgRx Store and Effects `>=19.0.0 <23.0.0`
11
- - RxJS `>=7.8.0 <8.0.0`
12
- - `@3dsource/types-unreal >=0.0.7`
13
- - `@3dsource/utils >=1.0.21`
14
- - `provideHttpClient()` in the host application
15
-
16
- ## Installation
17
-
18
- ```shell
19
- pnpm add @3dsource/angular-unreal-module @3dsource/types-unreal @3dsource/utils
20
- ```
21
-
22
- The package is standalone and does not expose an NgModule.
23
-
24
- ### Styling
25
-
26
- The package ships its own styles and does not require any UI library. Its
27
- components read `--src-*` design tokens where available, but every token has a
28
- built-in fallback, so `@3dsource/source-ui-native` is entirely optional: install
29
- it in the host application only if you want the components to follow that theme.
30
-
31
- ## Setup
32
-
33
- ### 1. Register state and configuration once
34
-
35
- Add the Unreal feature state, HTTP client and configuration at the application
36
- root. `UNREAL_CONFIG` is required, although all its fields are optional.
37
-
38
- <!-- prettier-ignore -->
39
- ```typescript
40
- import { provideHttpClient } from '@angular/common/http';
41
- import type { ApplicationConfig } from '@angular/core';
42
- import { provideStore } from '@ngrx/store';
43
- import {
44
- provideUnrealState,
45
- UNREAL_CONFIG,
46
- type UnrealInitialConfig,
47
- } from '@3dsource/angular-unreal-module';
48
-
49
- const unrealConfig = {
50
- regionsPingUrl: 'https://datacenter.3dsource.com/regions/',
51
- dataChannelConnectionTimeout: 8000,
52
- fpsMonitor: false,
53
- autoHighResolution: false,
54
- } satisfies UnrealInitialConfig;
55
-
56
- export const appConfig: ApplicationConfig = {
57
- providers: [
58
- provideHttpClient(),
59
- provideStore(),
60
- provideUnrealState(),
61
- { provide: UNREAL_CONFIG, useValue: unrealConfig },
62
- ],
63
- };
64
- ```
65
-
66
- Omit `provideStore()` when the root NgRx store is already configured.
67
-
68
- Available configuration fields:
69
-
70
- | Field | Purpose |
71
- | ------------------------------ | ---------------------------------------------- |
72
- | `regionsPingUrl` | Region latency endpoint |
73
- | `dataChannelConnectionTimeout` | DataChannel connection timeout in ms |
74
- | `customErrorsEndpoint` | Custom error reporting endpoint |
75
- | `commandTelemetryReceiver` | Command telemetry endpoint |
76
- | `streamTelemetryV2Url` | Stream lifecycle telemetry endpoint |
77
- | `screenLockerContainerId` | Container used by the screen-locker overlay |
78
- | `mode` | `'metabox'` (default) — full Metabox command protocol; `'default'` — stock Pixel Streaming app, the module sends no Metabox commands of its own |
79
- | `fpsMonitor` | Enables FPS monitoring (currently off — the service is not instantiated) |
80
- | `autoHighResolution` | Raises resolution after the scene becomes idle |
81
- | `playwright` | Enables the test-specific service behaviour |
82
-
83
- Use `{ provide: UNREAL_CONFIG, useValue: {} }` for the minimal configuration.
84
-
85
- ### 2. Boot the engine on a lazy route
86
-
87
- Lazy-load the route file from the application router:
88
-
89
- <!-- prettier-ignore -->
90
- ```typescript
91
- import type { Routes } from '@angular/router';
92
-
93
- export const APP_ROUTES: Routes = [
94
- {
95
- path: 'stream',
96
- loadChildren: () =>
97
- import('./stream/stream.routes').then((module) => module.STREAM_ROUTES),
98
- },
99
- ];
100
- ```
101
-
102
- Register `provideUnrealModule()` inside that lazy route file:
103
-
104
- <!-- prettier-ignore -->
105
- ```typescript
106
- import type { Routes } from '@angular/router';
107
- import { provideUnrealModule } from '@3dsource/angular-unreal-module';
108
- import { StreamComponent } from './stream.component';
109
-
110
- export const STREAM_ROUTES: Routes = [
111
- {
112
- path: '',
113
- component: StreamComponent,
114
- providers: [provideUnrealModule()],
115
- },
116
- ];
117
- ```
118
-
119
- Keep `provideUnrealState()` and `UNREAL_CONFIG` at the application root.
120
- The `loadChildren()` boundary keeps the streaming engine out of the initial
121
- bundle. Route-scoping `provideUnrealModule()` tears down its effects when the
122
- route is left.
123
-
124
- > `UNREAL_CONFIG` on a route's `providers` does **not** work: the engine
125
- > services are `providedIn: 'root'`, so they are created by the root injector
126
- > and read the token from there. A route-level value is invisible to them and
127
- > `inject(UNREAL_CONFIG, { optional: true })` resolves to `null` — e.g. region
128
- > pinging is skipped entirely (empty `regionsPingUrl`), the orchestration
129
- > `requestStream` goes out without a region and the post-connection re-ping
130
- > never runs. Importing only the token at the root does not pull the module into
131
- > the initial bundle (the package is `sideEffects: false`).
132
-
133
- ### 3. Render the scene
134
-
135
- ```typescript
136
- import { ChangeDetectionStrategy, Component } from '@angular/core';
137
- import { UnrealSceneComponent } from '@3dsource/angular-unreal-module';
138
-
139
- @Component({
140
- selector: 'app-stream',
141
- imports: [UnrealSceneComponent],
142
- template: `<app-unreal-scene />`,
143
- changeDetection: ChangeDetectionStrategy.OnPush,
144
- })
145
- export class StreamComponent {}
146
- ```
147
-
148
- `UnrealSceneComponent` also accepts `isStudio`,
149
- `useContainerAsSizeProvider` and `resolutionSize` inputs, and emits
150
- `changeMouseOverScene`.
151
-
152
- ## Main API
153
-
154
- - `provideUnrealState()` — registers the `unrealFeature` NgRx state.
155
- - `provideUnrealModule()` — registers effects and boots streaming services.
156
- - `UnrealSceneComponent` — renders and manages the Pixel Streaming scene.
157
- - `UnrealCommunicatorService` — sends commands and UI interactions.
158
- - `UnrealCallbackService` — observes Unreal callbacks and command responses.
159
- - `unrealFeature`, exported selectors and actions — expose connection and scene
160
- lifecycle state.
161
-
162
- Command packet types are provided by `@3dsource/types-unreal`.
163
-
164
- Run `pnpm demo:start` from the repository root to see the scene component in
165
- the demo application.
166
-
167
- ## Optional prefetch scripts
168
-
169
- The package publishes two dependency-free scripts for use in the document
170
- `<head>` before Angular starts:
171
-
172
- - `region-ping-prefetch.js` measures regions early and caches the closest one.
173
- - `stream-prefetch.js` opens and parks an eligible WebRTC connection so Angular
174
- can adopt it after bootstrap.
175
-
176
- <!-- prettier-ignore -->
177
- ```html
178
- <script
179
- src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/region-ping-prefetch.js"
180
- async
181
- ></script>
182
- <script
183
- src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/stream-prefetch.js"
184
- async
185
- ></script>
186
- ```
187
-
188
- `stream-prefetch.js` runs by default only on
189
- `metabox-configurator/{modular|basic}/...` routes. It reads the same-origin
190
- `assets/config.json`; override that path with `data-config-url` when needed.
191
- It also forwards the orchestration-issued `streamRequestId` from the polling
192
- response to Cirrus on the WebSocket URL (the session `connectionId`) and parks it
193
- for the Angular side to adopt.
194
- Pin an exact package version in production when deterministic CDN assets are
195
- required.
196
-
197
- ## Repository development
198
-
199
- Run commands from the repository root:
200
-
201
- ```shell
202
- pnpm unreal:build
203
- pnpm unreal:build:watch
204
- pnpm unreal:lint
205
- pnpm unreal:test
206
- pnpm unreal:test:watch
207
- pnpm unreal:test:signalling
208
- pnpm unreal:test:signalling:leaks
209
- ```
210
-
211
- `unreal:build` builds the local `types-unreal` and `utils` dependencies before
212
- this package.
213
-
214
- Release commands publish only this package:
215
-
216
- ```shell
217
- pnpm unreal:release:patch
218
- pnpm unreal:release:dev
219
- ```
220
-
221
- After a successful publish, the release automatically purges the matching
222
- jsDelivr tag. Retry a failed purge without rerunning the release:
223
-
224
- ```shell
225
- pnpm unreal:purge-cdn -- latest
226
- pnpm unreal:purge-cdn -- dev
227
- ```
228
-
229
- Repository tooling requires Node.js 24.16.0 or newer and pnpm 11.18.0.
1
+ # @3dsource/angular-unreal-module
2
+
3
+ Standalone Angular integration for Unreal Engine Pixel Streaming. The package
4
+ provides the scene component, WebRTC and signalling lifecycle, NgRx state,
5
+ command/callback APIs, reconnection, file transfer and telemetry.
6
+
7
+ ## Requirements
8
+
9
+ - Angular, Angular CDK and Angular Forms `>=19.0.0 <23.0.0`
10
+ - NgRx Store and Effects `>=19.0.0 <23.0.0`
11
+ - RxJS `>=7.8.0 <8.0.0`
12
+ - `@3dsource/types-unreal >=0.0.7`
13
+ - `@3dsource/utils >=1.0.21`
14
+ - `provideHttpClient()` in the host application
15
+
16
+ ## Installation
17
+
18
+ ```shell
19
+ pnpm add @3dsource/angular-unreal-module @3dsource/types-unreal @3dsource/utils
20
+ ```
21
+
22
+ The package is standalone and does not expose an NgModule.
23
+
24
+ ### Styling
25
+
26
+ The package ships its own styles and requires no UI library. Everything it draws
27
+ on top of the video is customisable through three levels — take the cheapest one
28
+ that solves your case.
29
+
30
+ | Need | Level | Cost |
31
+ | ---------------------------------- | ------------- | -------------------------- |
32
+ | different colour / radius / font | design tokens | 3 lines of CSS |
33
+ | your own look for a whole block | class hooks | one class in global styles |
34
+ | a different set of elements inside | slots | one template |
35
+
36
+ The three levels compose: tokens recolour what stays, a class hook restyles one
37
+ block, a slot replaces what is inside it. A slot makes the class hook for the
38
+ same part irrelevant — the markup inside is then entirely yours.
39
+
40
+ #### What you can customise
41
+
42
+ These are the blocks the scene draws over the video. Nothing else is themed —
43
+ the video itself, and the dev-only overlays, are out of scope.
44
+
45
+ | Block | When the user sees it | Token-only | Class hook parts | Slot |
46
+ | -------------- | ---------------------------------- | ---------- | ------------------------------------------ | ------------------ |
47
+ | Loading status | while connecting, shows percentage | ✓ | `statusCard`, `statusMessage` | `unrealStatusSlot` |
48
+ | Resume / Start | stream paused or not yet started | ✓ | `resumeCard`, `resumeText`, `resumeButton` | `unrealResumeSlot` |
49
+ | AFK timeout | inactivity countdown before drop | ✓ | `afkScrim`, `afkCard`, `afkButton` | `unrealAfkSlot` |
50
+
51
+ Error dialogs (`unreal-error-modal`, `webrtc-error-modal`) are **not** part of
52
+ this contract: they have no class hook, no slot, and they read the
53
+ `@3dsource/source-ui-native` tokens (`--src-*`) rather than `--unreal-*`. They
54
+ also render outside the scene DOM — see the note under Design tokens.
55
+
56
+ #### 1. Design tokens
57
+
58
+ Set the `--unreal-*` variables on `app-unreal-scene` — custom properties inherit
59
+ through the DOM and reach every block inside.
60
+
61
+ ```css
62
+ app-unreal-scene {
63
+ --unreal-surface-card: #101014;
64
+ --unreal-text-main: #f5f5f7;
65
+ --unreal-radius-card: 16px;
66
+ }
67
+ ```
68
+
69
+ | Token | Default |
70
+ | ------------------------- | -------------------------------------------------------- |
71
+ | `--unreal-surface-card` | `#fff` |
72
+ | `--unreal-surface-screen` | `#fff` |
73
+ | `--unreal-surface-scrim` | `rgba(100, 100, 100, .7)` |
74
+ | `--unreal-text-main` | `#1f2937` |
75
+ | `--unreal-text-secondary` | `#6b7280` |
76
+ | `--unreal-font-family` | `system-ui, sans-serif` |
77
+ | `--unreal-radius-card` | `8px` |
78
+ | `--unreal-radius-pill` | `9999px` |
79
+ | `--unreal-shadow-card` | `0 26px 80px 0 rgba(0,0,0,.2), 0 0 1px 0 rgba(0,0,0,.2)` |
80
+ | `--unreal-accent` | `#017bff` |
81
+ | `--unreal-accent-hover` | `#016fe6` |
82
+ | `--unreal-accent-text` | `#fff` |
83
+
84
+ If `@3dsource/source-ui-native` is loaded, its tokens are used automatically
85
+ wherever you do not set an `--unreal-*` value.
86
+
87
+ > **Anything opened through CDK Dialog is out of reach.** The error dialogs
88
+ > render in a separate overlay container, outside the scene DOM, so variables
89
+ > set on `app-unreal-scene` never reach them — declare those on `:root`. Note
90
+ > that those dialogs read `--src-*`, not `--unreal-*`.
91
+
92
+ #### 2. Class hooks
93
+
94
+ Supply your own class for a part; it **replaces** the built-in decoration class.
95
+ The positioning class stays, so the scene layout cannot break.
96
+
97
+ ```html
98
+ <app-unreal-scene [uiClasses]="{ resumeCard: 'my-card', statusCard: 'my-pill' }" />
99
+ ```
100
+
101
+ Parts: `resumeCard`, `resumeText`, `resumeButton`, `afkScrim`, `afkCard`,
102
+ `afkButton`, `statusCard`, `statusMessage`.
103
+
104
+ > **Your classes must live in global styles** (`styles.scss`). Declared in a
105
+ > component's own SCSS, Angular scopes them and they never reach the scene.
106
+
107
+ > **The two button parts behave differently.** `resumeButton` and `afkButton`
108
+ > put your class on the `<app-unreal-button>` host, but the `<button>` inside is
109
+ > a library component whose styles are scoped — and a scoped `.unreal-button`
110
+ > selector (0,2,0) outweighs a global `.my-cta button` (0,1,1). So the hook is
111
+ > good for the button's _outer_ box (width, margin, alignment); to change the
112
+ > button itself, use the `--unreal-accent*` tokens, or a slot if you want
113
+ > different markup altogether.
114
+
115
+ > **`afkScrim` and `--unreal-surface-scrim` have one caveat.** The package also
116
+ > ships a legacy partial, `src/lib/styles/unreal.scss`, which is not part of the
117
+ > published bundle and is not meant to be imported. It contains
118
+ > `.frame #videoPlayOverlay { background-color: … }` — an id selector that
119
+ > outweighs any class. If you import that partial anyway, it wins over both the
120
+ > class hook and the token for the AFK overlay background.
121
+
122
+ #### 3. Slots
123
+
124
+ Replace the contents of a block with your own markup. The module keeps the
125
+ wrapper and the visibility logic; you get the data through the template context.
126
+
127
+ ```html
128
+ <app-unreal-scene>
129
+ <ng-template unrealResumeSlot let-ctx>
130
+ <button (click)="ctx.start()" class="my-cta">{{ ctx.isSecondStart() ? 'Resume' : 'Start' }}</button>
131
+ </ng-template>
132
+
133
+ <ng-template unrealStatusSlot let-ctx>
134
+ <my-progress [value]="ctx.percents()" />
135
+ </ng-template>
136
+ </app-unreal-scene>
137
+ ```
138
+
139
+ | Directive | Context |
140
+ | ------------------ | --------------------------------------------- |
141
+ | `unrealResumeSlot` | `isSecondStart`, `isAfkDisconnect`, `start()` |
142
+ | `unrealAfkSlot` | `countdown` (seconds left), `reset()` |
143
+ | `unrealStatusSlot` | `percents`, `message` |
144
+
145
+ All context values except methods are Signals — call them in the template.
146
+ `percents` is `undefined` until the first value arrives — guard it if you render
147
+ it raw.
148
+
149
+ The context object is created once and never replaced; what changes are the
150
+ signals inside it. So `let-ctx` is stable and you can pass `ctx` around freely.
151
+
152
+ > **Markup inside a slot belongs to you, not to the library.** Angular scopes a
153
+ > template to the component that declares it, and your slot template is declared
154
+ > in _your_ component. Two consequences:
155
+ >
156
+ > - The library's own classes do nothing there. Writing `class="resume-box__text"`
157
+ > inside a slot will not pick up the module's styling — that class is scoped to
158
+ > the module.
159
+ > - Style slot content the way you style any of your own markup: your component's
160
+ > SCSS applies normally (no `::ng-deep`, no global stylesheet needed — unlike
161
+ > class hooks).
162
+ >
163
+ > The `--unreal-*` tokens **do** reach inside, because CSS custom properties
164
+ > inherit through the DOM. Read them if you want your markup to follow the same
165
+ > theme:
166
+ >
167
+ > ```scss
168
+ > .my-cta {
169
+ > background: var(--unreal-accent, #017bff);
170
+ > border-radius: var(--unreal-radius-card, 8px);
171
+ > }
172
+ > ```
173
+
174
+ #### Recipes
175
+
176
+ **Dark theme, nothing else.** One rule, no TypeScript:
177
+
178
+ ```css
179
+ app-unreal-scene {
180
+ --unreal-surface-card: #101014;
181
+ --unreal-surface-screen: #0b0b0f;
182
+ --unreal-text-main: #f5f5f7;
183
+ --unreal-text-secondary: #a1a1aa;
184
+ --unreal-shadow-card: 0 20px 60px 0 rgb(0 0 0 / 60%);
185
+ }
186
+ ```
187
+
188
+ **Your brand's accent on the buttons.** Buttons are recoloured with tokens, not
189
+ with a class hook — see the note above:
190
+
191
+ ```css
192
+ app-unreal-scene {
193
+ --unreal-accent: #ff5f6d;
194
+ --unreal-accent-hover: #f0454f;
195
+ --unreal-accent-text: #fff;
196
+ }
197
+ ```
198
+
199
+ If you need a button that is shaped differently, not just recoloured, replace
200
+ the whole block through `unrealResumeSlot` and render your own control.
201
+
202
+ **A completely different loading status.** Structure, not just skin — so this is
203
+ a slot. Visibility is still decided by the module:
204
+
205
+ ```html
206
+ <app-unreal-scene>
207
+ <ng-template unrealStatusSlot let-ctx>
208
+ <my-brand-progress [value]="ctx.percents() ?? 0" />
209
+ </ng-template>
210
+ </app-unreal-scene>
211
+ ```
212
+
213
+ #### What you cannot change
214
+
215
+ - **When a block appears.** Visibility stays with the module — the conditions
216
+ are non-trivial (connection state, reconnect flags, AFK timers) and duplicating
217
+ them in a host app is a reliable source of bugs.
218
+ - **Where a block sits in the scene.** Each customisable element keeps a
219
+ positioning class that a host class never replaces, so the scene layout cannot
220
+ be broken from outside. Move the block by restyling its container instead.
221
+ - **The video element and dev overlays** (`video-stats`, `stat-graph`).
222
+
223
+ ## Setup
224
+
225
+ ### 1. Register state and configuration once
226
+
227
+ Add the Unreal feature state, HTTP client and configuration at the application
228
+ root. `UNREAL_CONFIG` is required, although all its fields are optional.
229
+
230
+ <!-- prettier-ignore -->
231
+ ```typescript
232
+ import { provideHttpClient } from '@angular/common/http';
233
+ import type { ApplicationConfig } from '@angular/core';
234
+ import { provideStore } from '@ngrx/store';
235
+ import {
236
+ provideUnrealState,
237
+ UNREAL_CONFIG,
238
+ type UnrealInitialConfig,
239
+ } from '@3dsource/angular-unreal-module';
240
+
241
+ const unrealConfig = {
242
+ regionsPingUrl: 'https://datacenter.3dsource.com/regions/',
243
+ dataChannelConnectionTimeout: 8000,
244
+ fpsMonitor: false,
245
+ autoHighResolution: false,
246
+ } satisfies UnrealInitialConfig;
247
+
248
+ export const appConfig: ApplicationConfig = {
249
+ providers: [
250
+ provideHttpClient(),
251
+ provideStore(),
252
+ provideUnrealState(),
253
+ { provide: UNREAL_CONFIG, useValue: unrealConfig },
254
+ ],
255
+ };
256
+ ```
257
+
258
+ Omit `provideStore()` when the root NgRx store is already configured.
259
+
260
+ Available configuration fields:
261
+
262
+ | Field | Purpose |
263
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
264
+ | `regionsPingUrl` | Region latency endpoint |
265
+ | `dataChannelConnectionTimeout` | DataChannel connection timeout in ms |
266
+ | `customErrorsEndpoint` | Custom error reporting endpoint |
267
+ | `commandTelemetryReceiver` | Command telemetry endpoint |
268
+ | `streamTelemetryV2Url` | Stream lifecycle telemetry endpoint |
269
+ | `screenLockerContainerId` | Container used by the screen-locker overlay |
270
+ | `mode` | `'metabox'` (default) — full Metabox command protocol; `'default'` — stock Pixel Streaming app, the module sends no Metabox commands of its own |
271
+ | `fpsMonitor` | Enables FPS monitoring (currently off — the service is not instantiated) |
272
+ | `autoHighResolution` | Raises resolution after the scene becomes idle |
273
+ | `playwright` | Enables the test-specific service behaviour |
274
+
275
+ Use `{ provide: UNREAL_CONFIG, useValue: {} }` for the minimal configuration.
276
+
277
+ ### 2. Boot the engine on a lazy route
278
+
279
+ Lazy-load the route file from the application router:
280
+
281
+ <!-- prettier-ignore -->
282
+ ```typescript
283
+ import type { Routes } from '@angular/router';
284
+
285
+ export const APP_ROUTES: Routes = [
286
+ {
287
+ path: 'stream',
288
+ loadChildren: () =>
289
+ import('./stream/stream.routes').then((module) => module.STREAM_ROUTES),
290
+ },
291
+ ];
292
+ ```
293
+
294
+ Register `provideUnrealModule()` inside that lazy route file:
295
+
296
+ <!-- prettier-ignore -->
297
+ ```typescript
298
+ import type { Routes } from '@angular/router';
299
+ import { provideUnrealModule } from '@3dsource/angular-unreal-module';
300
+ import { StreamComponent } from './stream.component';
301
+
302
+ export const STREAM_ROUTES: Routes = [
303
+ {
304
+ path: '',
305
+ component: StreamComponent,
306
+ providers: [provideUnrealModule()],
307
+ },
308
+ ];
309
+ ```
310
+
311
+ Keep `provideUnrealState()` and `UNREAL_CONFIG` at the application root.
312
+ The `loadChildren()` boundary keeps the streaming engine out of the initial
313
+ bundle. Route-scoping `provideUnrealModule()` tears down its effects when the
314
+ route is left.
315
+
316
+ > `UNREAL_CONFIG` on a route's `providers` does **not** work: the engine
317
+ > services are `providedIn: 'root'`, so they are created by the root injector
318
+ > and read the token from there. A route-level value is invisible to them and
319
+ > `inject(UNREAL_CONFIG, { optional: true })` resolves to `null` — e.g. region
320
+ > pinging is skipped entirely (empty `regionsPingUrl`), the orchestration
321
+ > `requestStream` goes out without a region and the post-connection re-ping
322
+ > never runs. Importing only the token at the root does not pull the module into
323
+ > the initial bundle (the package is `sideEffects: false`).
324
+
325
+ ### 3. Render the scene
326
+
327
+ ```typescript
328
+ import { ChangeDetectionStrategy, Component } from '@angular/core';
329
+ import { UnrealSceneComponent } from '@3dsource/angular-unreal-module';
330
+
331
+ @Component({
332
+ selector: 'app-stream',
333
+ imports: [UnrealSceneComponent],
334
+ template: `<app-unreal-scene />`,
335
+ changeDetection: ChangeDetectionStrategy.OnPush,
336
+ })
337
+ export class StreamComponent {}
338
+ ```
339
+
340
+ `UnrealSceneComponent` also accepts `isStudio`,
341
+ `useContainerAsSizeProvider` and `resolutionSize` inputs, and emits
342
+ `changeMouseOverScene`.
343
+
344
+ ## Main API
345
+
346
+ - `provideUnrealState()` — registers the `unrealFeature` NgRx state.
347
+ - `provideUnrealModule()` — registers effects and boots streaming services.
348
+ - `UnrealSceneComponent` — renders and manages the Pixel Streaming scene.
349
+ - `UnrealCommunicatorService` — sends commands and UI interactions.
350
+ - `UnrealCallbackService` — observes Unreal callbacks and command responses.
351
+ - `unrealFeature`, exported selectors and actions — expose connection and scene
352
+ lifecycle state.
353
+
354
+ Command packet types are provided by `@3dsource/types-unreal`.
355
+
356
+ Run `pnpm demo:start` from the repository root to see the scene component in
357
+ the demo application.
358
+
359
+ ## Optional prefetch scripts
360
+
361
+ The package publishes two dependency-free scripts for use in the document
362
+ `<head>` before Angular starts:
363
+
364
+ - `region-ping-prefetch.js` measures regions early and caches the closest one.
365
+ - `stream-prefetch.js` opens and parks an eligible WebRTC connection so Angular
366
+ can adopt it after bootstrap.
367
+
368
+ <!-- prettier-ignore -->
369
+ ```html
370
+ <script
371
+ src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/region-ping-prefetch.js"
372
+ async
373
+ ></script>
374
+ <script
375
+ src="https://cdn.jsdelivr.net/npm/@3dsource/angular-unreal-module/js/stream-prefetch.js"
376
+ async
377
+ ></script>
378
+ ```
379
+
380
+ `stream-prefetch.js` runs by default only on
381
+ `metabox-configurator/{modular|basic}/...` routes. It reads the same-origin
382
+ `assets/config.json`; override that path with `data-config-url` when needed.
383
+ It also forwards the orchestration-issued `streamRequestId` from the polling
384
+ response to Cirrus on the WebSocket URL (the session `connectionId`) and parks it
385
+ for the Angular side to adopt.
386
+ Pin an exact package version in production when deterministic CDN assets are
387
+ required.
388
+
389
+ ## Repository development
390
+
391
+ Run commands from the repository root:
392
+
393
+ ```shell
394
+ pnpm unreal:build
395
+ pnpm unreal:build:watch
396
+ pnpm unreal:lint
397
+ pnpm unreal:test
398
+ pnpm unreal:test:watch
399
+ pnpm unreal:test:signalling
400
+ pnpm unreal:test:signalling:leaks
401
+ ```
402
+
403
+ `unreal:build` builds the local `types-unreal` and `utils` dependencies before
404
+ this package.
405
+
406
+ Release commands publish only this package:
407
+
408
+ ```shell
409
+ pnpm unreal:release:patch
410
+ pnpm unreal:release:dev
411
+ ```
412
+
413
+ After a successful publish, the release automatically purges the matching
414
+ jsDelivr tag. Retry a failed purge without rerunning the release:
415
+
416
+ ```shell
417
+ pnpm unreal:purge-cdn -- latest
418
+ pnpm unreal:purge-cdn -- dev
419
+ ```
420
+
421
+ Repository tooling requires Node.js 24.16.0 or newer and pnpm 11.21.0.