@3dsource/angular-unreal-module 0.0.163 → 0.0.164

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.
@@ -157,7 +157,6 @@ export interface RequestReservation extends InstanceMessageBase {
157
157
  }
158
158
  */
159
159
  const OrchestrationMessageTypes = {
160
- ping: 'ping',
161
160
  error: 'error',
162
161
  offer: 'offer',
163
162
  answer: 'answer',
@@ -171,8 +170,6 @@ const OrchestrationMessageTypes = {
171
170
  instanceReady: 'instanceReady',
172
171
  p2pEstablished: 'p2pEstablished',
173
172
  instanceReserved: 'instanceReserved',
174
- playerDisconnected: 'playerDisconnected',
175
- streamerDisconnected: 'streamerDisconnected',
176
173
  interruptClientStream: 'interruptClientStream',
177
174
  };
178
175
 
@@ -225,6 +222,14 @@ const OUTER_RETRY_DELAY = POLLING_TIME * 2;
225
222
  // blip, brief unavailability) without burning a second reservation.
226
223
  // Total WS attempts before URL rotation = 1 + WS_RETRY_PER_INSTANCE.
227
224
  const WS_RETRY_PER_INSTANCE = 1;
225
+ // Bound on `requestCirrusViaSocket`: how long the orchestration control
226
+ // socket may stay silent between `requestStream` and `instanceReady`.
227
+ // The orchestration itself reports an `eta` (default 150 s in
228
+ // `InstanceReservedHandler`) for a cold machine, so `WS_TIMEOUT` (6 s) is
229
+ // far too short here — this is a provisioning wait, not a handshake.
230
+ // On expiry the request fails like any other, so `connectAttempt`'s
231
+ // URL-rotating tier asks a different orchestration URL for a new machine.
232
+ const INSTANCE_READY_TIMEOUT = 180_000;
228
233
  const WS_OPEN_STATE = 1;
229
234
  const DEFAULT_AFK_TIMEOUT_PERIOD = 15;
230
235
  const DEFAULT_AFK_TIMEOUT = 300; // 5 minutes
@@ -479,7 +484,7 @@ const initialState = {
479
484
  };
480
485
  const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScene, (state, { isVideoPlaying }) => {
481
486
  return { ...state, isVideoPlaying };
482
- }), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl, streamRequestId }) => {
487
+ }), on(setAwsInstance, (state, { instanceName, wsUrl, pollingUrl, streamRequestId, kind }) => {
483
488
  return {
484
489
  ...state,
485
490
  awsInstance: {
@@ -488,7 +493,11 @@ const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScen
488
493
  wsUrl,
489
494
  pollingUrl,
490
495
  streamRequestId,
491
- progressComplete: 0,
496
+ kind: kind ?? state.awsInstance.kind,
497
+ // In the socket flow this action lands only once `instanceReady`
498
+ // arrived, i.e. the machine IS provisioned — resetting to 0 here would
499
+ // throw the bar back to the start right at the end of that phase.
500
+ progressComplete: kind === 'socket' ? 1 : 0,
492
501
  },
493
502
  };
494
503
  }), on(setViewportReady, (state) => {
@@ -594,6 +603,10 @@ const unrealReducer = createReducer(initialState, on(changeStatusMainVideoOnScen
594
603
  instanceName,
595
604
  streamRequestId,
596
605
  eta,
606
+ // `instanceReserved` exists only in the socket flow, and it arrives
607
+ // BEFORE `setAwsInstance` — so this is where the flow becomes known,
608
+ // early enough for `selectTotalProgress` to use the eta-driven bar.
609
+ kind: 'socket',
597
610
  },
598
611
  };
599
612
  }), on(setOrchestrationProgress, (state, { progressComplete }) => {
@@ -780,13 +793,16 @@ function InstanceReadyHandler(msg) {
780
793
  Logger.warn('instanceReady received without payload — ignoring');
781
794
  return;
782
795
  }
796
+ if (!payload.originInstanceUri) {
797
+ Logger.warn('instanceReady without originInstanceUri — ignoring');
798
+ return;
799
+ }
783
800
  // Emit for observability (telemetry) and store the stream-machine coords.
784
801
  this.store.dispatch(instanceReady(payload));
785
- // Open the SECOND socket directly to the stream machine and route the whole
786
- // WebRTC signalling through it. The orchestration socket stays open for
787
- // control/lifecycle. `streamRequestId` is handed over so the stream machine's
788
- // Cirrus reuses the orchestration-issued connection id.
789
- this.connectToStreamMachine(payload.originInstanceUri, payload.streamRequestId);
802
+ // This is the answer `requestCirrusViaSocket` is waiting for: the Cirrus
803
+ // address (`originInstanceUri`) plus the connection id to hand it. The socket
804
+ // itself is opened by the shared `connectToCirrus`, not here.
805
+ this.onInstanceReady$.next(payload);
790
806
  }
791
807
 
792
808
  function InstanceReservedHandler(msg) {
@@ -797,15 +813,6 @@ function InstanceReservedHandler(msg) {
797
813
  }));
798
814
  }
799
815
 
800
- function PingHandler() {
801
- // Reply pong on the active signalling socket (stream socket in the new flow,
802
- // relay socket in the old polling flow).
803
- this.sendPlayerSignal(JSON.stringify({
804
- type: 'pong',
805
- source: 'front',
806
- }));
807
- }
808
-
809
816
  function PlayerCountHandler(msg) {
810
817
  Logger.log(msg);
811
818
  }
@@ -861,9 +868,16 @@ function OnMessageHandler(e) {
861
868
  }
862
869
  }
863
870
 
871
+ /**
872
+ * Open handler of the CIRRUS socket only (`addWsHandlers`), so
873
+ * `setCirrusConnected` now means what it says. It used to also send
874
+ * `requestStream` here, which made the flag fire on the orchestration control
875
+ * socket — a whole provisioning wait (`eta`, ~150 s) before any Cirrus existed.
876
+ * `requestStream` belongs to the control socket and is sent by
877
+ * `requestCirrusViaSocket`.
878
+ */
864
879
  function OnOpenHandler() {
865
880
  this.store.dispatch(setCirrusConnected());
866
- this.sendRequestStream();
867
881
  }
868
882
 
869
883
  /**
@@ -1183,7 +1197,9 @@ const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unr
1183
1197
  info.push(`Unreal App Version: ${data.streamerVersion}`);
1184
1198
  info.push(`APP: ${data.streamPath}${data.streamApp}`);
1185
1199
  }
1186
- info.push(`Polling: ${instance.pollingUrl}`);
1200
+ // The endpoint that handed us the Cirrus address: an HTTP polling URL in one
1201
+ // flow, a `wss://` orchestration URL in the other.
1202
+ info.push(`Orchestration: ${instance.pollingUrl}`);
1187
1203
  info.push(`WS: ${instance.wsUrl}`);
1188
1204
  return info.join('<br>');
1189
1205
  });
@@ -1203,13 +1219,15 @@ const selectIsLoaderScreenVisible = createSelector(selectIsFullyConnected, (full
1203
1219
  */
1204
1220
  const selectTotalProgress = createSelector(unrealFeature.selectStatusPercentSignallingServer, selectCommandProgress, unrealFeature.selectAwsInstance, unrealFeature.selectDataChannelConnected, (statusPercentSignallingServer, commandProgress, awsInstance, dataChannelConnected) => {
1205
1221
  const SIGNALLING_MAX = SIGNALLING_PERCENT_VALUE / 100;
1206
- const ORCHESTRATION_WS_PATTERN = /^wss:\/\/orchestration-websocket/i;
1207
- const isNewOrchestration = ORCHESTRATION_WS_PATTERN.test(awsInstance?.wsUrl ?? '');
1222
+ // Stated by the service (`AwsInstance.kind`), not sniffed from the hostname.
1223
+ // In the socket flow it is set by `instanceReserved` — i.e. before
1224
+ // `setAwsInstance` — so the eta-driven bar is picked up from the start of
1225
+ // the provisioning wait.
1226
+ const isNewOrchestration = awsInstance?.kind === 'socket';
1208
1227
  // Once the data channel is connected, signaling is fully complete (1.0) —
1209
1228
  // regardless of orchestration type. This is what makes an ADOPTED stream
1210
- // (whose wsUrl is the machine host, not the orchestration-websocket URL,
1211
- // and which never set `statusPercentSignallingServer`) correctly show the
1212
- // whole first phase done instead of stalling near the command-only value.
1229
+ // (which never set `statusPercentSignallingServer`) correctly show the whole
1230
+ // first phase done instead of stalling near the command-only value.
1213
1231
  // Otherwise: new orchestration reports 0–1 progress; legacy uses the
1214
1232
  // polling percent.
1215
1233
  const signalingProgress = dataChannelConnected
@@ -1832,10 +1850,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImpor
1832
1850
  }], ctorParameters: () => [] });
1833
1851
 
1834
1852
  function StreamerListHandler(msg) {
1835
- // `subscribe` is player signalling — reply on the active signalling socket
1836
- // (stream socket in the new flow, relay socket in the old polling flow),
1837
- // matching the socket the `streamerList` arrived on.
1838
- this.sendPlayerSignal(JSON.stringify({
1853
+ // `subscribe` is player signalling → the Cirrus socket.
1854
+ this.send(JSON.stringify({
1839
1855
  type: 'subscribe',
1840
1856
  streamerId: msg.ids[0],
1841
1857
  }));
@@ -2042,19 +2058,6 @@ function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
2042
2058
  }));
2043
2059
  }
2044
2060
 
2045
- function PlayerDisconnectHandler() {
2046
- this.store.dispatch(disconnectStream({
2047
- reason: DisconnectReason.OrchestrationPlayerDisconnected,
2048
- message: 'Orchestration: PlayerDisconnectHandler',
2049
- }));
2050
- }
2051
- function StreamerDisconnectHandler() {
2052
- this.store.dispatch(disconnectStream({
2053
- reason: DisconnectReason.OrchestrationStreamerDisconnected,
2054
- message: 'Orchestration: StreamerDisconnectHandler',
2055
- }));
2056
- }
2057
-
2058
2061
  // Structured constants holding code + description
2059
2062
  const ORCHESTRATION_ERROR_CODES = {
2060
2063
  NO_AVAILABLE_INSTANCES: {
@@ -2079,6 +2082,16 @@ function OrchestrationErrorHandler(msg) {
2079
2082
  if (RETRY_CODES.has(msg.payload.code)) {
2080
2083
  const errorInfo = Object.values(ORCHESTRATION_ERROR_CODES).find(({ code }) => code === msg.payload.code);
2081
2084
  this.store.dispatch(setOrchestrationMessage({ message: errorInfo?.description }));
2085
+ // Same telemetry channel as the WS/URL/region retry tiers — this branch used
2086
+ // to resend silently, so `kind: 'orchestration'` was declared but never seen.
2087
+ // No count cap here on purpose: the wait is bounded by
2088
+ // `INSTANCE_READY_TIMEOUT` in `requestCirrusViaSocket`, and on expiry the
2089
+ // URL-rotating tier takes over.
2090
+ this.store.dispatch(signalingRetryAttempted({
2091
+ attempt: ++this.orchestrationRetryCount,
2092
+ reason: errorInfo?.description ?? `code ${msg.payload.code}`,
2093
+ kind: 'orchestration',
2094
+ }));
2082
2095
  this.orchestrationRetrySub?.unsubscribe();
2083
2096
  this.orchestrationRetrySub = timer(POLLING_TIME)
2084
2097
  .pipe(takeUntil(this.destroy$))
@@ -2102,21 +2115,27 @@ class SignallingService extends SubService {
2102
2115
  this.onConfig$ = new Subject();
2103
2116
  this.onWebRtcIce$ = new Subject();
2104
2117
  this.onWebRtcAnswer$ = new Subject();
2118
+ // Fed by `InstanceReadyHandler`. `requestCirrusViaSocket` awaits it the same
2119
+ // way `requestCirrusViaPolling` awaits a ready polling response — it is the
2120
+ // socket flow's "here is your Cirrus URL" answer.
2121
+ this.onInstanceReady$ = new Subject();
2105
2122
  // Pure signal — no side-effects. The `close()` is wired ONCE in init()
2106
2123
  // below so a single abort event triggers exactly one close, regardless
2107
2124
  // of how many pipelines have `takeUntil(this.abort$)` attached.
2108
2125
  this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
2126
+ // The CIRRUS signalling socket — the only socket that speaks WebRTC
2127
+ // (config/offer/answer/iceCandidate/ping). Same field in all three flows:
2128
+ // the polled `signallingServer`, the stream machine (`originInstanceUri`), or
2129
+ // a direct dev URL.
2109
2130
  this.ws = null;
2110
- // Second socket, opened directly to the stream machine (originInstanceUri
2111
- // from `instanceReady`). Carries the whole WebRTC signalling
2112
- // (config/offer/answer/iceCandidate/ping); `this.ws` stays for control.
2113
- this.streamWs = null;
2114
- // True once the stream-machine socket is established. After this the
2115
- // orchestration server intentionally closes the browser's orchestration
2116
- // socket (instance is "occupied") — that close must NOT be treated as a drop.
2117
- this.streamMachineConnected = false;
2118
- // Bounded retry counter for the stream-machine connection (new-flow only).
2119
- this.streamMachineRetryCount = 0;
2131
+ // The orchestration CONTROL socket (socket flow only). Opened by
2132
+ // `requestCirrusViaSocket` to ask for a machine and closed the moment
2133
+ // `instanceReady` arrives — per the backend contract nothing uses it after the
2134
+ // handover, so it is never left open with dangling handlers.
2135
+ this.controlWs = null;
2136
+ // `requestStream` resends driven by `OrchestrationErrorHandler` within one
2137
+ // `requestCirrusViaSocket` wait. Reported as `kind: 'orchestration'` retries.
2138
+ this.orchestrationRetryCount = 0;
2120
2139
  this.wsMsgHandlers = {};
2121
2140
  this.init();
2122
2141
  }
@@ -2124,7 +2143,7 @@ class SignallingService extends SubService {
2124
2143
  if (this.unrealConfig?.playwright) {
2125
2144
  return;
2126
2145
  }
2127
- this.setHandlersFromStream();
2146
+ this.setMessageHandlers();
2128
2147
  // Single owner of the "close on abort" side-effect. Previously this
2129
2148
  // lived as a `tap` inside `abort$` itself, which made every
2130
2149
  // `takeUntil(this.abort$)` subscriber fire its own close().
@@ -2139,13 +2158,12 @@ class SignallingService extends SubService {
2139
2158
  // and `init()` runs once in the constructor. If these subs were torn
2140
2159
  // down by abort$, a second in-injector session (e.g. navigating between
2141
2160
  // two routes that both host <app-unreal-scene>) would never send
2142
- // `p2pEstablished`. `sendPlayerSignal()` is already a no-op on a closed WS,
2161
+ // `p2pEstablished`. `send()` is already a no-op on a closed WS,
2143
2162
  // so there's no risk of stray messages.
2144
2163
  this.actions$
2145
2164
  .pipe(ofType(dataChannelReady), takeUntil(this.destroy$))
2146
2165
  .subscribe(() => {
2147
- // Player signalling → active signalling socket (stream/relay).
2148
- this.sendPlayerSignal({
2166
+ this.send({
2149
2167
  type: 'p2pEstablished',
2150
2168
  source: 'front',
2151
2169
  sessionId: generateUuid(),
@@ -2196,7 +2214,7 @@ class SignallingService extends SubService {
2196
2214
  }
2197
2215
  /**
2198
2216
  * One inner cycle: rotate `urlGen` over the pool, resolve via
2199
- * {@link getAwsInstance}, open WS via {@link connectToCirrus},
2217
+ * {@link requestCirrus}, open WS via {@link connectToCirrus},
2200
2218
  * retry up to {@link INNER_RETRY_LIMIT} on any hard failure. The
2201
2219
  * urlGen lives in this closure so retries advance through the
2202
2220
  * pool. Exhaustion bubbles to {@link connectToSignaling}'s outer
@@ -2204,12 +2222,20 @@ class SignallingService extends SubService {
2204
2222
  */
2205
2223
  connectAttempt(urls) {
2206
2224
  const urlGen = getActiveUrl(urls);
2207
- return defer(() => this.getAwsInstance(`${urlGen.next().value}`)).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl, streamRequestId }) =>
2225
+ return defer(() => {
2226
+ // One control socket per ATTEMPT, whatever transport this attempt picks —
2227
+ // a rotation from a `wss://` orchestration URL to an `http://` one would
2228
+ // otherwise leave the previous control socket behind. Deliberately NOT
2229
+ // inside the WS-only tier below: that tier reuses the same resolved
2230
+ // machine, so its control socket must survive.
2231
+ this.teardownControlWs();
2232
+ return this.requestCirrus(`${urlGen.next().value}`);
2233
+ }).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl, streamRequestId }) =>
2208
2234
  // ── WS-only retry tier ─────────────────────────────────────────────
2209
2235
  // Tries the SAME `wsUrl` up to `WS_RETRY_PER_INSTANCE` extra times
2210
2236
  // before letting the error bubble up to the outer (URL-rotating)
2211
2237
  // retry. This is important because the outer retry calls
2212
- // `getAwsInstance(url)` again, which polls orchestration again,
2238
+ // `requestCirrus(url)` again, which asks orchestration again,
2213
2239
  // which causes orchestrator to RESERVE A NEW instance — the
2214
2240
  // previous reservation is orphaned (server-side leak).
2215
2241
  //
@@ -2218,13 +2244,10 @@ class SignallingService extends SubService {
2218
2244
  // burning a second reservation. If WS still fails after these
2219
2245
  // extra attempts, the instance is genuinely unusable and we
2220
2246
  // accept the cost of rotating.
2221
- // POLLING flow only: the polled `signallingServer` IS Cirrus (single
2222
- // socket), so the orchestration-issued connection id rides on this
2223
- // upgrade URL. In the new WS flow this socket is orchestration-only
2224
- // control and `streamRequestId` is still unknown here (it arrives later
2225
- // in `instanceReserved`/`instanceReady`) → no-op, URL unchanged; the id
2226
- // is attached to the machine address instead, see
2227
- // `connectToStreamMachine`.
2247
+ // `wsUrl` is always a CIRRUS address here, whichever request method
2248
+ // produced it, so the orchestration-issued connection id always rides
2249
+ // on this upgrade URL and Cirrus adopts it instead of generating its
2250
+ // own. Absent id (older backend, direct dev URL) → URL unchanged.
2228
2251
  this.connectToCirrus(withStreamRequestId(wsUrl ?? '', streamRequestId)).pipe(filter(Truthy), take(1), retry({
2229
2252
  count: WS_RETRY_PER_INSTANCE,
2230
2253
  delay: (err, attempt) => {
@@ -2278,22 +2301,52 @@ class SignallingService extends SubService {
2278
2301
  });
2279
2302
  }
2280
2303
  /**
2281
- * Resolve one URL to an `AwsInstance`. Dispatches to
2282
- * {@link directWsInstance} for `ws://` / `wss://` URLs, otherwise
2283
- * to {@link pollOrchestration}.
2304
+ * Ask the orchestration for a Cirrus to connect to. All three
2305
+ * implementations answer the same question — "which Cirrus, and with which
2306
+ * `streamRequestId`?" — and emit ONE `AwsInstance` whose `wsUrl` is a Cirrus
2307
+ * address. What differs is only how the answer is obtained and therefore how
2308
+ * it can fail:
2309
+ *
2310
+ * | Method | Transport | Fails on |
2311
+ * | -------------------------- | -------------------- | ------------------------------ |
2312
+ * | {@link requestCirrusViaPolling} | HTTP poll loop | HTTP 4xx (dead URL) |
2313
+ * | {@link requestCirrusViaSocket} | control WS | socket drop / no `instanceReady` in {@link INSTANCE_READY_TIMEOUT} |
2314
+ * | {@link requestCirrusDirect} | none (dev) | never — nothing is requested |
2315
+ *
2316
+ * Whatever they return goes through the SAME downstream in
2317
+ * {@link connectAttempt}: `setAwsInstance` → `connectToCirrus` → the WS-only
2318
+ * and URL-rotating retry tiers. A failure inside any of them is just an
2319
+ * error on this observable, so those tiers handle it uniformly.
2284
2320
  */
2285
- getAwsInstance(url) {
2286
- return url.match(/^ws(s?):\/\//i)
2287
- ? this.directWsInstance(url)
2288
- : this.pollOrchestration(url);
2321
+ requestCirrus(url) {
2322
+ if (!url.match(/^ws(s?):\/\//i)) {
2323
+ return this.requestCirrusViaPolling(url);
2324
+ }
2325
+ // A `ws://` URL is the orchestration control endpoint when the app supplied
2326
+ // a stream-request context (the same precondition `sendRequestStream` needs);
2327
+ // without it there is nothing to negotiate and the URL is Cirrus itself.
2328
+ return this.hasOrchestrationContext()
2329
+ ? this.requestCirrusViaSocket(url)
2330
+ : this.requestCirrusDirect(url);
2331
+ }
2332
+ /** True when `requestStream` can be composed → new orchestration. */
2333
+ hasOrchestrationContext() {
2334
+ return !!this.streamRequestContext() && !!this.environmentId();
2335
+ }
2336
+ /**
2337
+ * Wait until `clientId` and `viewId` are known — every request method needs
2338
+ * them before it may talk to the backend.
2339
+ */
2340
+ waitForClientAndViewIds() {
2341
+ return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1));
2289
2342
  }
2290
2343
  /**
2291
- * Direct `ws://` / `wss://` short-circuit — no HTTP orchestration.
2292
- * Waits for `clientId` and `viewId` to be truthy in the store, then
2293
- * emits a `Direct Connection` `AwsInstance` and completes.
2344
+ * Direct `ws://` / `wss://` short-circuit — no orchestration at all, the URL
2345
+ * already points at Cirrus. Dev/playwright convenience, hence no
2346
+ * `kind`: nothing was requested.
2294
2347
  */
2295
- directWsInstance(url) {
2296
- return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1), map$1(() => ({
2348
+ requestCirrusDirect(url) {
2349
+ return this.waitForClientAndViewIds().pipe(map$1(() => ({
2297
2350
  wsUrl: url,
2298
2351
  pollingUrl: null,
2299
2352
  instanceName: 'Direct Connection',
@@ -2306,7 +2359,7 @@ class SignallingService extends SubService {
2306
2359
  * last response captured in the closure). Maps the final ready
2307
2360
  * response to an `AwsInstance`.
2308
2361
  */
2309
- pollOrchestration(url) {
2362
+ requestCirrusViaPolling(url) {
2310
2363
  return defer(() => {
2311
2364
  let lastResp;
2312
2365
  this.store.dispatch(pollingStarted());
@@ -2324,12 +2377,84 @@ class SignallingService extends SubService {
2324
2377
  // The orchestration issues the connection id here; it is handed to
2325
2378
  // Cirrus on the WS upgrade URL (see `connectAttempt`).
2326
2379
  streamRequestId: data.streamRequestId,
2380
+ kind: 'polling',
2327
2381
  })), finalize(() => {
2328
2382
  this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(lastResp ?? {}), multi: true });
2329
2383
  this.store.dispatch(pollingEnded());
2330
2384
  }));
2331
2385
  });
2332
2386
  }
2387
+ /**
2388
+ * WS-orchestration path — the socket-transport twin of
2389
+ * {@link requestCirrusViaPolling}. Opens the control socket, sends
2390
+ * `requestStream` and waits for `instanceReady`, whose `originInstanceUri`
2391
+ * IS the Cirrus address.
2392
+ *
2393
+ * Failure modes are handled here so they surface as a plain error and reach
2394
+ * `connectAttempt`'s retry tiers (previously this branch lived outside them
2395
+ * and a dead control socket silently hung the session):
2396
+ * - control socket closes/errors before `instanceReady` → error
2397
+ * - no `instanceReady` within {@link INSTANCE_READY_TIMEOUT} → error
2398
+ *
2399
+ * The control socket lives exactly as long as this wait — see
2400
+ * {@link awaitInstanceReady}.
2401
+ */
2402
+ requestCirrusViaSocket(url) {
2403
+ return defer(() => {
2404
+ let resolved;
2405
+ this.orchestrationRetryCount = 0;
2406
+ this.commandTelemetryService.trackStartCommand('EXT-getSignaling');
2407
+ return this.waitForClientAndViewIds().pipe(switchMap(() => createWebSocket(url)), switchMap((control) => this.awaitInstanceReady(control, url)), tap((instance) => {
2408
+ resolved = instance;
2409
+ }), finalize(() => this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(resolved ?? {}), multi: true })));
2410
+ });
2411
+ }
2412
+ /**
2413
+ * Send `requestStream` on the freshly opened control socket and resolve with
2414
+ * the machine coordinates from `instanceReady`.
2415
+ *
2416
+ * The socket lives exactly as long as this wait: its close/error are HARD
2417
+ * failures of the request, and on resolution it is closed with every handler
2418
+ * detached (the backend does not use it after the handover). So the control
2419
+ * socket has no post-handover state at all — nothing that could report a stale
2420
+ * "disconnect" against a live stream.
2421
+ */
2422
+ awaitInstanceReady(control, orchestrationUrl) {
2423
+ return new Observable((observer) => {
2424
+ control.onmessage = OnMessageHandler.bind(this);
2425
+ control.onclose = (e) => observer.error(new Error(`Orchestration socket closed before instanceReady (code ${e.code})`));
2426
+ control.onerror = () => observer.error(new Error('Orchestration socket error before instanceReady'));
2427
+ this.controlWs = control;
2428
+ Logger.log('ORCHESTRATION CONTROL WS OPEN');
2429
+ this.sendRequestStream();
2430
+ const sub = this.onInstanceReady$.pipe(take(1)).subscribe((payload) => {
2431
+ // The control socket has done its only job. Per the backend contract it
2432
+ // is not used after the handover, so it is closed here — with all its
2433
+ // handlers detached by `teardownSocket`, which also means the close
2434
+ // itself cannot come back as an error above.
2435
+ this.teardownControlWs();
2436
+ observer.next({
2437
+ wsUrl: payload.originInstanceUri,
2438
+ // The endpoint that answered — the orchestration URL here, the HTTP
2439
+ // polling URL in the other flow. Shown as `Orchestration:` in the overlay.
2440
+ pollingUrl: orchestrationUrl,
2441
+ instanceName: payload.originInstanceId,
2442
+ streamRequestId: payload.streamRequestId,
2443
+ kind: 'socket',
2444
+ });
2445
+ observer.complete();
2446
+ });
2447
+ // Errored, timed out or aborted before `instanceReady` — same teardown,
2448
+ // the socket must not be left behind either way.
2449
+ return () => {
2450
+ sub.unsubscribe();
2451
+ this.teardownControlWs();
2452
+ };
2453
+ }).pipe(timeout$1({
2454
+ first: INSTANCE_READY_TIMEOUT,
2455
+ with: () => throwError(() => new Error(`No instanceReady within ${INSTANCE_READY_TIMEOUT}ms — requesting another machine`)),
2456
+ }), takeUntil(this.abort$));
2457
+ }
2333
2458
  /**
2334
2459
  * Poll the SAME `url` until ready, or until HTTP 4xx says the URL
2335
2460
  * is dead.
@@ -2386,37 +2511,31 @@ class SignallingService extends SubService {
2386
2511
  // still create a new socket, but in tests would re-emit the same
2387
2512
  // (already-errored) mock observable.
2388
2513
  return defer(() => {
2389
- this.close({ code: 1000, reason: DisconnectReason.NewConnectionAttempt });
2514
+ // Only the Cirrus socket — the control socket belongs to
2515
+ // `requestCirrusViaSocket`, which already opened it to GET this `wsUrl`.
2516
+ this.closeCirrus({
2517
+ code: 1000,
2518
+ reason: DisconnectReason.NewConnectionAttempt,
2519
+ });
2390
2520
  return createWebSocket(wsUrl);
2391
2521
  }).pipe(takeUntil(this.abort$), tapLog('WS CREATED'), tap((ws) => {
2392
2522
  this.commandTelemetryService.trackStartCommand('EXT-iceCandidate');
2393
2523
  this.addWsHandlers(ws);
2394
2524
  }));
2395
2525
  }
2526
+ /**
2527
+ * Handlers for the CIRRUS socket — the only long-lived socket. Its close/error
2528
+ * are real stream events (`setCirrusDisconnected` / `dropConnection`), unlike
2529
+ * the control socket's, which never outlives {@link awaitInstanceReady}.
2530
+ */
2396
2531
  addWsHandlers(ws) {
2397
2532
  ws.onmessage = OnMessageHandler.bind(this);
2398
- // Orchestration socket close/error is benign ONCE the stream machine is
2399
- // connected (orchestration closes it after marking the instance occupied).
2400
- ws.onclose = (e) => this.onOrchestrationClose(e);
2401
- ws.onerror = (e) => this.onOrchestrationError(e);
2533
+ ws.onclose = OnCloseHandler.bind(this);
2534
+ ws.onerror = OnErrorHandler.bind(this);
2402
2535
  this.ws = ws;
2403
2536
  Logger.log('WS HANDLERS ADDED');
2404
2537
  OnOpenHandler.call(this);
2405
2538
  }
2406
- onOrchestrationClose(e) {
2407
- if (this.streamMachineConnected) {
2408
- Logger.log(`Orchestration socket closed after occupancy (benign): ${e.code}`);
2409
- return;
2410
- }
2411
- OnCloseHandler.call(this, e);
2412
- }
2413
- onOrchestrationError(e) {
2414
- if (this.streamMachineConnected) {
2415
- Logger.log('Orchestration socket error after occupancy (benign)');
2416
- return;
2417
- }
2418
- OnErrorHandler.call(this, e);
2419
- }
2420
2539
  /**
2421
2540
  * Adopt a live signalling socket opened by the `stream-prefetch.js`
2422
2541
  * head-script. The socket is already OPEN and `requestStream` was already
@@ -2433,104 +2552,32 @@ class SignallingService extends SubService {
2433
2552
  this.ws = ws;
2434
2553
  Logger.log('WS ADOPTED (handshake already done, requestStream already sent)');
2435
2554
  }
2436
- /**
2437
- * Open the SECOND socket, directly to the stream machine's Cirrus
2438
- * (`originInstanceUri` from `instanceReady`). The whole WebRTC signalling
2439
- * (config/offer/answer/iceCandidate/ping) flows over this socket; the
2440
- * orchestration socket (`this.ws`) stays open for control/lifecycle.
2441
- *
2442
- * "Just connect" — nothing is sent on open (the machine is already reserved
2443
- * for this session): Cirrus pushes `config` and Unreal sends the `offer`.
2444
- *
2445
- * The orchestration-issued `streamRequestId` (from the same `instanceReady`
2446
- * payload) rides on this upgrade URL: in the new flow THIS is the Cirrus
2447
- * socket, so it is the one that must carry the id (the first socket is
2448
- * orchestration-only control and needs nothing). Without it the stream
2449
- * machine's Cirrus falls back to its own `guid()` and the session can no
2450
- * longer be correlated. Absent id → URL unchanged.
2451
- */
2452
- connectToStreamMachine(originInstanceUri, streamRequestId) {
2453
- if (!originInstanceUri) {
2454
- Logger.warn('instanceReady without originInstanceUri — stream socket not opened');
2455
- return;
2456
- }
2457
- // Guard: a stream socket is already live. A duplicate / re-sent
2458
- // `instanceReady` must NOT tear down the active stream.
2459
- if (this.streamMachineConnected) {
2460
- Logger.warn('Stream machine already connected — ignoring duplicate instanceReady');
2461
- return;
2462
- }
2463
- // Drop any prior (half-open) stream socket AND cancel any in-flight connect
2464
- // before a fresh attempt — e.g. a retry that resolved to a different machine
2465
- // or two instanceReady messages before the first connect resolves.
2466
- this.teardownStreamWs();
2467
- this.streamWsSub = createWebSocket(withStreamRequestId(originInstanceUri, streamRequestId))
2468
- .pipe(takeUntil(this.abort$), takeUntil(this.destroy$), take(1))
2469
- .subscribe({
2470
- next: (ws) => {
2471
- this.streamMachineRetryCount = 0;
2472
- this.addStreamWsHandlers(ws);
2473
- },
2474
- error: (err) => {
2475
- Logger.error('Stream-machine WS failed to connect', err?.message);
2476
- this.retryStreamMachine();
2477
- },
2478
- });
2479
- }
2480
- /**
2481
- * The stream-machine (Cirrus) connection failed. Re-request a machine over
2482
- * the still-open orchestration socket: orchestration issues a new
2483
- * streamRequestId and a fresh `instanceReady(originInstanceUri)`, which calls
2484
- * {@link connectToStreamMachine} again on a different machine. Bounded by
2485
- * {@link INNER_RETRY_LIMIT}; on exhaustion the stream is dropped.
2486
- */
2487
- retryStreamMachine() {
2488
- if (this.streamMachineRetryCount >= INNER_RETRY_LIMIT) {
2489
- Logger.error('Stream-machine connection exhausted retries — dropping');
2490
- this.store.dispatch(disconnectStream({
2491
- reason: DisconnectReason.ReconnectFailed,
2492
- message: 'Could not connect to the stream machine',
2493
- }));
2494
- return;
2495
- }
2496
- this.streamMachineRetryCount++;
2497
- this.orchestrationRetrySub?.unsubscribe();
2498
- this.orchestrationRetrySub = timer(POLLING_TIME)
2499
- .pipe(takeUntil(this.abort$))
2500
- .subscribe(() => this.sendRequestStream());
2501
- }
2502
- addStreamWsHandlers(ws) {
2503
- ws.onmessage = OnMessageHandler.bind(this);
2504
- ws.onclose = OnCloseHandler.bind(this);
2505
- ws.onerror = OnErrorHandler.bind(this);
2506
- this.streamWs = ws;
2507
- this.streamMachineConnected = true;
2508
- Logger.log('STREAM-MACHINE WS CONNECTED');
2509
- }
2510
- teardownStreamWs() {
2511
- this.streamWsSub?.unsubscribe();
2512
- this.teardownSocket(this.streamWs, 1000, DisconnectReason.NewConnectionAttempt);
2513
- this.streamWs = null;
2514
- this.streamMachineConnected = false;
2515
- }
2516
2555
  showStatusMessage(statusMessage) {
2517
2556
  this.store.dispatch(setStatusMessage({ statusMessage }));
2518
2557
  }
2519
2558
  /**
2520
- * Close the connection with the signaling server
2521
- * @param data
2559
+ * Close everything: the Cirrus socket AND the orchestration control socket.
2560
+ * Used on abort/teardown. A new connection attempt inside a live cycle uses
2561
+ * {@link closeCirrus} instead, so it does not kill the control socket that
2562
+ * produced the address it is about to connect to.
2522
2563
  */
2523
2564
  close(data) {
2524
2565
  const { code, reason } = data;
2525
- this.onWebRtcAnswer$.next(null);
2526
2566
  this.orchestrationRetrySub?.unsubscribe();
2527
- this.streamWsSub?.unsubscribe();
2567
+ this.closeCirrus(data);
2568
+ this.teardownControlWs(code, reason);
2569
+ }
2570
+ /** Close only the Cirrus signalling socket. */
2571
+ closeCirrus(data) {
2572
+ const { code, reason } = data;
2573
+ this.onWebRtcAnswer$.next(null);
2528
2574
  this.teardownSocket(this.ws, code, reason);
2529
2575
  this.ws = null;
2530
- this.teardownSocket(this.streamWs, code, reason);
2531
- this.streamWs = null;
2532
- this.streamMachineConnected = false;
2533
- this.streamMachineRetryCount = 0;
2576
+ }
2577
+ teardownControlWs(code = 1000, reason = DisconnectReason.NewConnectionAttempt) {
2578
+ this.teardownSocket(this.controlWs, code, reason);
2579
+ this.controlWs = null;
2580
+ this.orchestrationRetryCount = 0;
2534
2581
  }
2535
2582
  teardownSocket(ws, code, reason) {
2536
2583
  if (!ws) {
@@ -2547,17 +2594,16 @@ class SignallingService extends SubService {
2547
2594
  ws.close(code, reason);
2548
2595
  }
2549
2596
  }
2597
+ /**
2598
+ * Player signalling (offer/answer/iceCandidate/pong/p2pEstablished/subscribe)
2599
+ * — always the Cirrus socket, in every flow.
2600
+ */
2550
2601
  send(data) {
2551
2602
  this.sendTo(this.ws, data);
2552
2603
  }
2553
- /**
2554
- * Send player signalling (offer/answer/iceCandidate/pong/p2pEstablished/
2555
- * subscribe) over the ACTIVE signalling socket: the stream-machine socket in
2556
- * the new flow (once connected), or the orchestration/relay socket in the old
2557
- * polling flow (where there is no separate stream socket).
2558
- */
2559
- sendPlayerSignal(data) {
2560
- this.sendTo(this.streamMachineConnected ? this.streamWs : this.ws, data);
2604
+ /** Orchestration control messages (`requestStream`) — the control socket. */
2605
+ sendToOrchestration(data) {
2606
+ this.sendTo(this.controlWs, data);
2561
2607
  }
2562
2608
  sendTo(ws, data) {
2563
2609
  let dataString = data;
@@ -2587,21 +2633,45 @@ class SignallingService extends SubService {
2587
2633
  Logger.colored(...COLOR_CODES.FROM_CIRRUS_ERROR, `Invalid CIRRUS message type: ${msg.type}`);
2588
2634
  }
2589
2635
  }
2590
- setHandlersFromStream() {
2591
- this.wsMsgHandlers.ping = PingHandler.bind(this);
2636
+ /**
2637
+ * One handler map shared by both sockets — `handleMessage` dispatches purely by
2638
+ * message type. Which socket a type can arrive on is fixed, so registration is
2639
+ * split by origin: {@link setOrchestrationHandlers} (control socket) and
2640
+ * {@link setStreamHandlers} (Cirrus).
2641
+ *
2642
+ * Keepalive is deliberately absent: both servers ping at the WS **protocol**
2643
+ * level (`ws.ping()`), which the browser answers on its own — the page never
2644
+ * sees a ping and must never reply with an application-level `pong`. An
2645
+ * unregistered type is dropped silently by `handleMessage`, so a `ping` from a
2646
+ * not-yet-updated backend is simply ignored.
2647
+ */
2648
+ setMessageHandlers() {
2649
+ this.setOrchestrationHandlers();
2650
+ this.setStreamHandlers();
2651
+ }
2652
+ /**
2653
+ * Messages from the ORCHESTRATION control socket. Only reachable during the
2654
+ * provisioning wait — `requestCirrusViaSocket` closes that socket the moment
2655
+ * `instanceReady` lands.
2656
+ */
2657
+ setOrchestrationHandlers() {
2658
+ // `instanceReserved` (eta/progress) and `instanceReady` (the Cirrus address)
2659
+ // ARE the request/response pair of `requestCirrusViaSocket`.
2660
+ this.wsMsgHandlers.instanceReserved = InstanceReservedHandler.bind(this);
2661
+ this.wsMsgHandlers.instanceReady = InstanceReadyHandler.bind(this);
2662
+ // Orchestration-side refusals (4001 no instances / 4002 limit reached) →
2663
+ // resend `requestStream`, bounded by `INSTANCE_READY_TIMEOUT`.
2592
2664
  this.wsMsgHandlers.error = OrchestrationErrorHandler.bind(this);
2665
+ }
2666
+ /** Messages from the CIRRUS socket — the actual stream signalling. */
2667
+ setStreamHandlers() {
2593
2668
  this.wsMsgHandlers.config = ConfigHandler.bind(this);
2669
+ this.wsMsgHandlers.offer = OfferHandler.bind(this);
2594
2670
  this.wsMsgHandlers.answer = AnswerHandler.bind(this);
2671
+ this.wsMsgHandlers.iceCandidate = IceCandidateHandler.bind(this);
2595
2672
  this.wsMsgHandlers.ssInfo = SSInfoHandler.bind(this);
2596
2673
  this.wsMsgHandlers.playerCount = PlayerCountHandler.bind(this);
2597
2674
  this.wsMsgHandlers.streamerList = StreamerListHandler.bind(this);
2598
- this.wsMsgHandlers.iceCandidate = IceCandidateHandler.bind(this);
2599
- this.wsMsgHandlers.instanceReady = InstanceReadyHandler.bind(this);
2600
- this.wsMsgHandlers.instanceReserved = InstanceReservedHandler.bind(this);
2601
- this.wsMsgHandlers.playerDisconnected = PlayerDisconnectHandler.bind(this);
2602
- this.wsMsgHandlers.streamerDisconnected =
2603
- StreamerDisconnectHandler.bind(this);
2604
- this.wsMsgHandlers.offer = OfferHandler.bind(this);
2605
2675
  }
2606
2676
  sendRequestStream() {
2607
2677
  const context = this.streamRequestContext();
@@ -2609,7 +2679,7 @@ class SignallingService extends SubService {
2609
2679
  if (!context || !environmentId) {
2610
2680
  return;
2611
2681
  }
2612
- this.send({
2682
+ this.sendToOrchestration({
2613
2683
  type: OrchestrationMessageTypes.requestStream,
2614
2684
  correlationId: this.correlationId,
2615
2685
  payload: {
@@ -2620,18 +2690,12 @@ class SignallingService extends SubService {
2620
2690
  });
2621
2691
  }
2622
2692
  /**
2623
- * Check if the ACTIVE player-signalling socket is open and ready. Mirrors
2624
- * {@link sendPlayerSignal}'s socket choice so peer-reconnect gating is correct
2625
- * in both flows:
2626
- * - NEW flow (`streamMachineConnected`): the stream-machine socket
2627
- * (`streamWs`). The orchestration socket (`this.ws`) is intentionally
2628
- * closed after occupancy, so checking it would wrongly report "not
2629
- * connected" and block reconnect.
2630
- * - OLD polling flow: the single relay socket (`this.ws`) — unchanged.
2693
+ * Is the player-signalling socket open? Always the Cirrus socket — the
2694
+ * control socket's state is irrelevant to the stream (orchestration closes it
2695
+ * once the instance is occupied), so it must not be part of this gate.
2631
2696
  */
2632
2697
  isConnected() {
2633
- const activeWs = this.streamMachineConnected ? this.streamWs : this.ws;
2634
- return !!(activeWs && activeWs.readyState === WS_OPEN_STATE);
2698
+ return !!(this.ws && this.ws.readyState === WS_OPEN_STATE);
2635
2699
  }
2636
2700
  getRegion() {
2637
2701
  Logger.log('Start Getting Region');
@@ -3550,7 +3614,7 @@ class WebRtcPlayerService extends SubService {
3550
3614
  });
3551
3615
  fromEvent(pcClient, 'icecandidate')
3552
3616
  .pipe(map$1((e) => e.candidate), filter(Truthy), tap$1((candidate) => Logger.info('[Browser ICE candidate]', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|')), takeUntil(merge(this.disconnect$, this.destroy$)))
3553
- .subscribe((candidate) => this.signallingSrv.sendPlayerSignal({
3617
+ .subscribe((candidate) => this.signallingSrv.send({
3554
3618
  type: 'iceCandidate',
3555
3619
  candidate,
3556
3620
  }));
@@ -3700,7 +3764,7 @@ class WebRtcPlayerService extends SubService {
3700
3764
  const offer = await pcClient.createOffer();
3701
3765
  offer.sdp = this.mungeSDP(offer.sdp ?? '');
3702
3766
  await pcClient.setLocalDescription(offer);
3703
- this.signallingSrv.sendPlayerSignal(offer);
3767
+ this.signallingSrv.send(offer);
3704
3768
  }
3705
3769
  catch {
3706
3770
  Logger.error("Couldn't create offer");
@@ -3715,7 +3779,7 @@ class WebRtcPlayerService extends SubService {
3715
3779
  const answer = await pcClient.createAnswer();
3716
3780
  answer.sdp = this.mungeSDP(answer.sdp ?? '');
3717
3781
  await pcClient.setLocalDescription(answer);
3718
- this.signallingSrv.sendPlayerSignal(answer);
3782
+ this.signallingSrv.send(answer);
3719
3783
  // Observability signal for the signaling waterfall — the handshake
3720
3784
  // went "answer sent" between `offer received` and `datachannel_ready`.
3721
3785
  this.store.dispatch(answerSent());
@@ -4169,7 +4233,15 @@ class UnrealEffects {
4169
4233
  userAgent: navigator.userAgent,
4170
4234
  error,
4171
4235
  };
4172
- return this.http.post(awsInstance.pollingUrl || '', body).pipe(catchError((postError) => {
4236
+ // Only the polling flow has an HTTP endpoint to report to;
4237
+ // `pollingUrl` is a `wss://` orchestration address otherwise.
4238
+ const reportUrl = awsInstance.pollingUrl?.startsWith('http')
4239
+ ? awsInstance.pollingUrl
4240
+ : null;
4241
+ const report$ = reportUrl
4242
+ ? this.http.post(reportUrl, body)
4243
+ : of(null);
4244
+ return report$.pipe(catchError((postError) => {
4173
4245
  Logger.error('Error sending timeout info:', postError);
4174
4246
  return of(null);
4175
4247
  }),
@@ -8100,5 +8172,5 @@ function provideUnrealModule() {
8100
8172
  * Generated bundle index. Do not edit.
8101
8173
  */
8102
8174
 
8103
- export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
8175
+ export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToPercents, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, smoothTransition, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
8104
8176
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map