@3dsource/angular-unreal-module 0.0.154 → 0.0.156-dev.0
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
|
@@ -20,26 +20,29 @@ This package provides:
|
|
|
20
20
|
|
|
21
21
|
### Prerequisites
|
|
22
22
|
|
|
23
|
-
- Angular
|
|
24
|
-
- NgRx store and effects
|
|
25
|
-
- Angular CDK
|
|
23
|
+
- Angular `>=19.0.0 <23.0.0`
|
|
24
|
+
- NgRx store and effects `>=19.0.0 <23.0.0`
|
|
25
|
+
- Angular CDK `>=19.0.0 <23.0.0` — used for dialog overlays
|
|
26
|
+
- RxJS `>=7.8.0 <8.0.0`
|
|
26
27
|
- `provideHttpClient()` — required by internal services (telemetry, signalling, regions ping, error reporting)
|
|
27
28
|
|
|
28
29
|
### Peer Dependencies
|
|
29
30
|
|
|
30
|
-
This library requires the following peer dependencies
|
|
31
|
+
This library requires the following peer dependencies:
|
|
31
32
|
|
|
32
33
|
```json
|
|
33
34
|
{
|
|
34
35
|
"@3dsource/source-ui-native": ">=1.0.9",
|
|
35
36
|
"@3dsource/types-unreal": ">=0.0.7",
|
|
36
37
|
"@3dsource/utils": ">=1.0.21",
|
|
37
|
-
"@angular/cdk": ">=19.0.0",
|
|
38
|
-
"@angular/common": ">=19.0.0",
|
|
39
|
-
"@angular/core": ">=19.0.0",
|
|
40
|
-
"@angular/forms": ">=19.0.0",
|
|
41
|
-
"@
|
|
42
|
-
"@ngrx/
|
|
38
|
+
"@angular/cdk": ">=19.0.0 <23.0.0",
|
|
39
|
+
"@angular/common": ">=19.0.0 <23.0.0",
|
|
40
|
+
"@angular/core": ">=19.0.0 <23.0.0",
|
|
41
|
+
"@angular/forms": ">=19.0.0 <23.0.0",
|
|
42
|
+
"@angular/platform-browser": ">=19.0.0 <23.0.0",
|
|
43
|
+
"@ngrx/effects": ">=19.0.0 <23.0.0",
|
|
44
|
+
"@ngrx/store": ">=19.0.0 <23.0.0",
|
|
45
|
+
"rxjs": ">=7.8.0 <8.0.0"
|
|
43
46
|
}
|
|
44
47
|
```
|
|
45
48
|
|
|
@@ -53,7 +56,7 @@ npm i @3dsource/angular-unreal-module
|
|
|
53
56
|
|
|
54
57
|
The API is fully standalone (no NgModule). Use providers and components as shown below.
|
|
55
58
|
|
|
56
|
-
### 1) Provide the
|
|
59
|
+
### 1) Provide the state and config at the application root
|
|
57
60
|
|
|
58
61
|
Add providers in your application bootstrap (e.g., `app.config.ts`):
|
|
59
62
|
|
|
@@ -64,8 +67,7 @@ import { ApplicationConfig } from '@angular/core';
|
|
|
64
67
|
import { provideRouter } from '@angular/router';
|
|
65
68
|
import { provideHttpClient } from '@angular/common/http';
|
|
66
69
|
import { provideStore } from '@ngrx/store';
|
|
67
|
-
import {
|
|
68
|
-
import { provideUnrealModule, UNREAL_CONFIG } from '@3dsource/angular-unreal-module';
|
|
70
|
+
import { provideUnrealState, UNREAL_CONFIG } from '@3dsource/angular-unreal-module';
|
|
69
71
|
|
|
70
72
|
export const appConfig: ApplicationConfig = {
|
|
71
73
|
providers: [
|
|
@@ -74,7 +76,7 @@ export const appConfig: ApplicationConfig = {
|
|
|
74
76
|
|
|
75
77
|
// Root NgRx (if not already added in your app)
|
|
76
78
|
provideStore(),
|
|
77
|
-
|
|
79
|
+
provideUnrealState(),
|
|
78
80
|
|
|
79
81
|
// Required: Unreal initial configuration
|
|
80
82
|
{
|
|
@@ -85,14 +87,12 @@ export const appConfig: ApplicationConfig = {
|
|
|
85
87
|
regionsPingUrl: '', // URL prefix for regions latency ping
|
|
86
88
|
screenLockerContainerId: '', // DOM container id for screen locker overlay
|
|
87
89
|
dataChannelConnectionTimeout: 8000, // Timeout in ms for data channel connection (default: 8000)
|
|
90
|
+
streamTelemetryV2Url: '', // Endpoint for stream-status telemetry
|
|
91
|
+
fpsMonitor: false, // Enable FPS monitoring
|
|
92
|
+
autoHighResolution: false, // Raise resolution after the scene becomes idle
|
|
88
93
|
playwright: false, // Mirrors the provider flag for services/effects
|
|
89
94
|
},
|
|
90
95
|
},
|
|
91
|
-
|
|
92
|
-
// Core Unreal providers (adds feature state + effects internally).
|
|
93
|
-
// playwright/testing mode is set via UNREAL_CONFIG above.
|
|
94
|
-
// Opt into extra features as needed — see "Bundle size & features" below.
|
|
95
|
-
provideUnrealModule(),
|
|
96
96
|
],
|
|
97
97
|
};
|
|
98
98
|
```
|
|
@@ -103,10 +103,38 @@ If you don't need custom endpoints, provide `UNREAL_CONFIG` with an empty object
|
|
|
103
103
|
|
|
104
104
|
```ts
|
|
105
105
|
{ provide: UNREAL_CONFIG, useValue: {} },
|
|
106
|
-
|
|
106
|
+
provideUnrealState(),
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Every `UnrealInitialConfig` field is optional, but the `UNREAL_CONFIG` provider
|
|
110
|
+
itself is required. Available fields are `playwright`,
|
|
111
|
+
`customErrorsEndpoint`, `commandTelemetryReceiver`, `regionsPingUrl`,
|
|
112
|
+
`screenLockerContainerId`, `dataChannelConnectionTimeout`,
|
|
113
|
+
`streamTelemetryV2Url`, `fpsMonitor` and `autoHighResolution`.
|
|
114
|
+
|
|
115
|
+
### 2) Boot the engine in each lazy 3D route
|
|
116
|
+
|
|
117
|
+
`provideUnrealModule()` registers the effects and initializes the streaming
|
|
118
|
+
services. Put it on every lazy route that renders a 3D scene:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { Routes } from '@angular/router';
|
|
122
|
+
import { provideUnrealModule } from '@3dsource/angular-unreal-module';
|
|
123
|
+
|
|
124
|
+
export const STREAM_ROUTES: Routes = [
|
|
125
|
+
{
|
|
126
|
+
path: ':id',
|
|
127
|
+
loadComponent: () => import('./stream.component').then((module) => module.StreamComponent),
|
|
128
|
+
providers: [provideUnrealModule()],
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Keep `provideUnrealState()` and `UNREAL_CONFIG` at the root. Keeping
|
|
134
|
+
`provideUnrealModule()` in lazy routes also keeps the streaming engine out of
|
|
135
|
+
the initial application bundle.
|
|
136
|
+
|
|
137
|
+
### 3) Use the Unreal scene component
|
|
110
138
|
|
|
111
139
|
Import the component into a standalone component and use it in the template.
|
|
112
140
|
|
|
@@ -118,7 +146,7 @@ import { UnrealSceneComponent } from '@3dsource/angular-unreal-module';
|
|
|
118
146
|
selector: 'app-root',
|
|
119
147
|
standalone: true,
|
|
120
148
|
imports: [UnrealSceneComponent],
|
|
121
|
-
template: ` <app-unreal-scene [isStudio]="false" [useContainerAsSizeProvider]="true" [
|
|
149
|
+
template: ` <app-unreal-scene [isStudio]="false" [useContainerAsSizeProvider]="true" [resolutionSize]="{ width: 1920, height: 1080 }" (changeMouseOverScene)="onHover($event)" /> `,
|
|
122
150
|
})
|
|
123
151
|
export class AppComponent {
|
|
124
152
|
onHover(isOver: boolean) {
|
|
@@ -135,7 +163,7 @@ Inputs:
|
|
|
135
163
|
| ---------------------------- | ----------------------------------- | ------------------------------- |
|
|
136
164
|
| `isStudio` | `boolean` | `false` |
|
|
137
165
|
| `useContainerAsSizeProvider` | `boolean` | `true` |
|
|
138
|
-
| `
|
|
166
|
+
| `resolutionSize` | `{ width: number; height: number }` | `{ width: 1920, height: 1080 }` |
|
|
139
167
|
|
|
140
168
|
Outputs:
|
|
141
169
|
|
|
@@ -143,7 +171,7 @@ Outputs:
|
|
|
143
171
|
| ---------------------- | --------------------------- |
|
|
144
172
|
| `changeMouseOverScene` | `OutputEmitterRef<boolean>` |
|
|
145
173
|
|
|
146
|
-
###
|
|
174
|
+
### 4) Send commands / interactions to Unreal
|
|
147
175
|
|
|
148
176
|
Inject `UnrealCommunicatorService` to send commands or UI interactions. Types for command packets are provided by `@3dsource/types-unreal`.
|
|
149
177
|
|
|
@@ -184,14 +212,15 @@ export class MyComponent {
|
|
|
184
212
|
}
|
|
185
213
|
```
|
|
186
214
|
|
|
187
|
-
###
|
|
215
|
+
### 5) Store integration (actions & selectors)
|
|
188
216
|
|
|
189
|
-
|
|
217
|
+
`provideUnrealState()` registers the NgRx feature state `unrealFeature`. You
|
|
218
|
+
can dispatch actions and select state in your components:
|
|
190
219
|
|
|
191
220
|
```ts
|
|
192
221
|
import { inject } from '@angular/core';
|
|
193
222
|
import { Store } from '@ngrx/store';
|
|
194
|
-
import {
|
|
223
|
+
import { disconnectStream, selectShowLoader, selectTotalProgress, setConfig, setOrchestrationContext, startStream, unrealFeature } from '@3dsource/angular-unreal-module';
|
|
195
224
|
|
|
196
225
|
// Dispatch actions
|
|
197
226
|
const store = inject(Store);
|
|
@@ -199,8 +228,8 @@ store.dispatch(startStream({ config: { autoStart: true, warnTimeout: 120 } }));
|
|
|
199
228
|
|
|
200
229
|
// Select state
|
|
201
230
|
const progress = store.selectSignal(selectTotalProgress);
|
|
202
|
-
const isVideoPlaying = store.selectSignal(
|
|
203
|
-
const dataChannelConnected = store.selectSignal(
|
|
231
|
+
const isVideoPlaying = store.selectSignal(unrealFeature.selectIsVideoPlaying);
|
|
232
|
+
const dataChannelConnected = store.selectSignal(unrealFeature.selectDataChannelConnected);
|
|
204
233
|
```
|
|
205
234
|
|
|
206
235
|
**Key selectors:**
|
|
@@ -210,9 +239,9 @@ const dataChannelConnected = store.selectSignal(commandsFeature.selectDataChanne
|
|
|
210
239
|
- `selectShowReconnectPopup` — Whether reconnect popup should be shown
|
|
211
240
|
- `selectIsVideoPlayingAndDataChannelConnected` — Combined readiness check
|
|
212
241
|
- `selectStreamConfig` — Current stream configuration
|
|
213
|
-
- `
|
|
214
|
-
- `
|
|
215
|
-
- `
|
|
242
|
+
- `unrealFeature.selectCirrusConnected` — Signalling server connection status
|
|
243
|
+
- `unrealFeature.selectDataChannelConnected` — Data channel status
|
|
244
|
+
- `unrealFeature.selectViewportReady` — Viewport readiness
|
|
216
245
|
|
|
217
246
|
**Key actions:**
|
|
218
247
|
|
|
@@ -223,7 +252,7 @@ const dataChannelConnected = store.selectSignal(commandsFeature.selectDataChanne
|
|
|
223
252
|
- `destroyUnrealScene` — Full teardown
|
|
224
253
|
- `reconnectPeer` — Trigger peer reconnection
|
|
225
254
|
|
|
226
|
-
###
|
|
255
|
+
### 6) Listen for Unreal callbacks
|
|
227
256
|
|
|
228
257
|
Inject `UnrealCallbackService` to listen for callback events from Unreal Engine and to observe command responses.
|
|
229
258
|
|
|
@@ -282,18 +311,18 @@ export class MyComponent {
|
|
|
282
311
|
|
|
283
312
|
### Components
|
|
284
313
|
|
|
285
|
-
| Component
|
|
286
|
-
|
|
|
287
|
-
| `UnrealSceneComponent`
|
|
288
|
-
| `AfkTimeoutModalComponent`
|
|
289
|
-
| `FreezeFrameComponent`
|
|
290
|
-
| `LowBandwidthModalComponent`
|
|
291
|
-
| `
|
|
292
|
-
| `ImageLoadingSrcComponent`
|
|
293
|
-
| `IntroSrcComponent`
|
|
294
|
-
| `VideoStatsComponent`
|
|
295
|
-
| `StatGraphComponent`
|
|
296
|
-
| `WebrtcErrorModalComponent`
|
|
314
|
+
| Component | Selector | Description |
|
|
315
|
+
| ------------------------------- | ------------------ | -------------------------------------- |
|
|
316
|
+
| `UnrealSceneComponent` | `app-unreal-scene` | Main scene container with video stream |
|
|
317
|
+
| `AfkTimeoutModalComponent` | — | AFK timeout warning modal |
|
|
318
|
+
| `FreezeFrameComponent` | — | Freeze frame overlay |
|
|
319
|
+
| `LowBandwidthModalComponent` | — | Low bandwidth warning modal |
|
|
320
|
+
| `LowBandwidthDetectorComponent` | — | Low bandwidth detector |
|
|
321
|
+
| `ImageLoadingSrcComponent` | — | Loading image overlay |
|
|
322
|
+
| `IntroSrcComponent` | — | Intro image/video overlay |
|
|
323
|
+
| `VideoStatsComponent` | — | Video statistics display |
|
|
324
|
+
| `StatGraphComponent` | — | Statistics graph |
|
|
325
|
+
| `WebrtcErrorModalComponent` | — | WebRTC error modal |
|
|
297
326
|
|
|
298
327
|
### Services
|
|
299
328
|
|
|
@@ -318,9 +347,9 @@ export class MyComponent {
|
|
|
318
347
|
|
|
319
348
|
### Pipes
|
|
320
349
|
|
|
321
|
-
| Pipe
|
|
322
|
-
|
|
|
323
|
-
| `
|
|
350
|
+
| Pipe | Description |
|
|
351
|
+
| ---------- | ------------------------------------------------------------ |
|
|
352
|
+
| `SafePipe` | Sanitizes a value for the requested Angular security context |
|
|
324
353
|
|
|
325
354
|
### Interfaces
|
|
326
355
|
|
|
@@ -350,12 +379,34 @@ export class MyComponent {
|
|
|
350
379
|
Check the demo application for complete usage examples:
|
|
351
380
|
|
|
352
381
|
```shell
|
|
353
|
-
|
|
382
|
+
pnpm demo:start
|
|
354
383
|
```
|
|
355
384
|
|
|
356
385
|
See also: `projects/demo/src/app/demo-layout/info-pages/unreal-scene-demo/constants/unreal.routes.ts` for a real-world provider configuration example.
|
|
357
386
|
|
|
387
|
+
## Repository development
|
|
388
|
+
|
|
389
|
+
Run package commands from the repository root:
|
|
390
|
+
|
|
391
|
+
```shell
|
|
392
|
+
pnpm --filter @3dsource/angular-unreal-module build
|
|
393
|
+
pnpm --filter @3dsource/angular-unreal-module build:watch
|
|
394
|
+
pnpm --filter @3dsource/angular-unreal-module test:signalling
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
To version and publish:
|
|
398
|
+
|
|
399
|
+
```shell
|
|
400
|
+
pnpm release:package -- angular-unreal-module patch latest
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The release command publishes to npm. After verifying the npm release, purge
|
|
404
|
+
the matching jsDelivr tag explicitly:
|
|
405
|
+
|
|
406
|
+
```shell
|
|
407
|
+
pnpm --filter @3dsource/angular-unreal-module purge-cdn -- latest
|
|
408
|
+
```
|
|
409
|
+
|
|
358
410
|
## Engine requirements
|
|
359
411
|
|
|
360
412
|
- Node.js: >=20
|
|
361
|
-
- npm: >9
|
|
@@ -6,7 +6,6 @@ import { createAction, props, createReducer, on, createFeature, Store, createSel
|
|
|
6
6
|
import { whereNot, Falsy, Logger, calculateMedian, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, generateUuid, Truthy, tapLog, Semaphore, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
|
|
7
7
|
import { filter, map as map$1, distinctUntilChanged, takeWhile, takeUntil, catchError, tap, timeout, finalize, first, switchMap as switchMap$1, withLatestFrom, exhaustMap, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
8
8
|
import { Actions, ofType, createEffect, provideEffects } from '@ngrx/effects';
|
|
9
|
-
import { concatLatestFrom } from '@ngrx/operators';
|
|
10
9
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
11
10
|
import { HttpClient } from '@angular/common/http';
|
|
12
11
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
@@ -3329,7 +3328,8 @@ class WebRtcPlayerService extends SubService {
|
|
|
3329
3328
|
* (`pc.onicecandidate`, `dc.onmessage`, …) and buffered inbound DC messages;
|
|
3330
3329
|
* here we detach those, wire our own (addEventListener-based) handlers, replay
|
|
3331
3330
|
* the buffer in order, then drive `onDataChannelConnected` manually because the
|
|
3332
|
-
* DataChannel is already open
|
|
3331
|
+
* DataChannel is already open. A native `open` event may still be queued, so the
|
|
3332
|
+
* adopted channel deliberately does not subscribe to it.
|
|
3333
3333
|
*/
|
|
3334
3334
|
adopt(pc, dc, stream, config, dcBuffer) {
|
|
3335
3335
|
// No prior pc/dc should exist on a fresh boot; closePC is idempotent.
|
|
@@ -3354,7 +3354,9 @@ class WebRtcPlayerService extends SubService {
|
|
|
3354
3354
|
dc.onmessage = null;
|
|
3355
3355
|
dc.onopen = null;
|
|
3356
3356
|
dc.onclose = null;
|
|
3357
|
-
|
|
3357
|
+
// Adoption announces the already-open channel manually below. Do not
|
|
3358
|
+
// subscribe to a native `open` event that may still be queued.
|
|
3359
|
+
this.dcClient = this.setupDataChannelCallbacks(dc, false);
|
|
3358
3360
|
for (const buffered of dcBuffer) {
|
|
3359
3361
|
this.onDataChannelMessage$.next(buffered);
|
|
3360
3362
|
}
|
|
@@ -3498,7 +3500,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3498
3500
|
}
|
|
3499
3501
|
}
|
|
3500
3502
|
}
|
|
3501
|
-
setupDataChannelCallbacks(datachannel) {
|
|
3503
|
+
setupDataChannelCallbacks(datachannel, subscribeToOpen = true) {
|
|
3502
3504
|
try {
|
|
3503
3505
|
Logger.log(`[DATACHANNEL] Setting up datachannel "${datachannel.label}" callbacks...`);
|
|
3504
3506
|
this.commandTelemetryService.trackStartCommand('EXT-DataChannel Connection');
|
|
@@ -3521,14 +3523,16 @@ class WebRtcPlayerService extends SubService {
|
|
|
3521
3523
|
const message = `[DATACHANNEL] Data channel "${datachannel.label}" error. id:${datachannel.id}`;
|
|
3522
3524
|
Logger.error(message, e);
|
|
3523
3525
|
});
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3526
|
+
if (subscribeToOpen) {
|
|
3527
|
+
fromEvent(datachannel, 'open')
|
|
3528
|
+
.pipe(tap$1(() => {
|
|
3529
|
+
Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
|
|
3530
|
+
}), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3531
|
+
.subscribe(() => {
|
|
3532
|
+
this.commandTelemetryService.trackStopCommand('EXT-DataChannel Connection');
|
|
3533
|
+
this.onDataChannelConnected();
|
|
3534
|
+
});
|
|
3535
|
+
}
|
|
3532
3536
|
fromEvent(datachannel, 'message')
|
|
3533
3537
|
.pipe(
|
|
3534
3538
|
/* tap((e: MessageEvent) => {
|
|
@@ -4058,10 +4062,10 @@ class UnrealEffects {
|
|
|
4058
4062
|
* a visible flicker (video flashing to black).
|
|
4059
4063
|
*/
|
|
4060
4064
|
this.forceViewportNotReady$ = createEffect(() => {
|
|
4061
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections),
|
|
4065
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), withLatestFrom(this.store.select(unrealFeature.selectViewportReady)), filter(([, viewportReady]) => viewportReady), map(() => setViewportNotReady()));
|
|
4062
4066
|
});
|
|
4063
4067
|
this.resetCirrusOnDestroy$ = createEffect(() => {
|
|
4064
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections),
|
|
4068
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), withLatestFrom(this.store.select(unrealFeature.selectCirrusConnected)), filter(([, cirrusConnected]) => cirrusConnected), map(() => setCirrusDisconnected()));
|
|
4065
4069
|
});
|
|
4066
4070
|
this.destroyRemoteConnections$ = createEffect(() => {
|
|
4067
4071
|
return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ reason }) => {
|
|
@@ -4069,7 +4073,7 @@ class UnrealEffects {
|
|
|
4069
4073
|
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map(() => dropConnection()));
|
|
4070
4074
|
});
|
|
4071
4075
|
this.destroyConnectionAndRestart$ = createEffect(() => {
|
|
4072
|
-
return this.actions$.pipe(ofType(setCirrusDisconnected),
|
|
4076
|
+
return this.actions$.pipe(ofType(setCirrusDisconnected), withLatestFrom(this.store.select(unrealFeature.selectDisconnectReason)), filter(([, reason]) => reason === DisconnectReason.DataChannelTimeout), map(() => initSignalling()));
|
|
4073
4077
|
});
|
|
4074
4078
|
this.showUnrealError$ = createEffect(() => {
|
|
4075
4079
|
return this.actions$.pipe(ofType(showUnrealErrorMessage), map(({ code }) => getRtcErrorMessage(code)), distinctUntilChanged(), filter(Truthy), switchMap((content) => this.dialog.open(UnrealErrorModalComponent, {
|
|
@@ -4078,7 +4082,7 @@ class UnrealEffects {
|
|
|
4078
4082
|
}).closed), map(() => showUnrealErrorMessage({ code: null })));
|
|
4079
4083
|
});
|
|
4080
4084
|
this.unrealReceiverReady$ = createEffect(() => {
|
|
4081
|
-
return this.actions$.pipe(ofType(dataChannelConnected),
|
|
4085
|
+
return this.actions$.pipe(ofType(dataChannelConnected), withLatestFrom(this.store.select(unrealFeature.selectIsReconnecting)), filter(([, isReconnecting]) => !isReconnecting), map(() => dataChannelReady()));
|
|
4082
4086
|
});
|
|
4083
4087
|
this.initAppByDataChannelReady$ = createEffect(() => {
|
|
4084
4088
|
return this.actions$.pipe(ofType(dataChannelReady), tap(() => {
|