@3dsource/angular-unreal-module 0.0.171 → 0.0.173
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/LICENSE +14 -14
- package/README.md +421 -229
- package/fesm2022/3dsource-angular-unreal-module.mjs +1277 -1003
- package/fesm2022/3dsource-angular-unreal-module.mjs.map +1 -1
- package/package.json +1 -1
- package/types/3dsource-angular-unreal-module.d.ts +130 -7
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
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
`
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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.
|