@3dsource/angular-unreal-module 0.0.122 → 0.0.123
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.
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, ChangeDetectionStrategy, Component, Injectable, DestroyRef, effect, untracked, computed, signal, Pipe, input, ElementRef, viewChild, afterNextRender, HostListener, output, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
|
|
3
3
|
import { SourceButtonComponent, SourceLoadingComponent } from '@3dsource/source-ui-native';
|
|
4
|
-
import { merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, shareReplay, from, fromEvent, throwError, defer, retry, tap as tap$1, throttleTime, NEVER, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
4
|
+
import { merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, shareReplay, from, fromEvent, throwError, defer, retry, repeat, tap as tap$1, throttleTime, NEVER, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1 } from 'rxjs';
|
|
5
5
|
import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
|
|
6
6
|
import { concatLatestFrom } from '@ngrx/operators';
|
|
7
7
|
import { Actions, createEffect, ofType, provideEffects } from '@ngrx/effects';
|
|
8
8
|
import { whereNot, Logger, calculateMedian, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, Truthy, tapLog, Falsy, generateUuid, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
|
|
9
|
-
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
9
|
+
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, takeUntil, catchError, withLatestFrom, exhaustMap, switchMap as switchMap$1, timeout as timeout$1, finalize, first, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
10
10
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
11
11
|
import { HttpClient } from '@angular/common/http';
|
|
12
12
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
@@ -207,8 +207,31 @@ const CONSOLE_COMMAND_DISABLE_MESSAGES = 'DisableAllScreenMessages';
|
|
|
207
207
|
const CONSOLE_COMMAND_PIXEL_QUALITY = 'PixelStreaming.FreezeFrameQuality 95';
|
|
208
208
|
const FULL_HD_WIDTH = 1920;
|
|
209
209
|
const FULL_HD_HEIGHT = 1080;
|
|
210
|
-
|
|
210
|
+
// Bound on a single WS upgrade attempt (`createWebSocket`). On cold
|
|
211
|
+
// connections the TCP+TLS+WS handshake can legitimately take 3-5 s
|
|
212
|
+
// (cold DNS, TLS session miss). 2 s was too aggressive — first
|
|
213
|
+
// attempts kept timing out and the second attempt (with warm caches)
|
|
214
|
+
// would connect in <500 ms. 10 s gives slow networks room without
|
|
215
|
+
// letting a truly hung handshake hold up `connectToCirrus` forever.
|
|
216
|
+
const WS_TIMEOUT = 6000;
|
|
211
217
|
const POLLING_TIME = 4000;
|
|
218
|
+
// Staged retry policy for `SignallingService.connectToSignaling`:
|
|
219
|
+
// INNER_RETRY_LIMIT = max number of WS+orchestration attempts before
|
|
220
|
+
// the pipeline gives up on the current region and bubbles the error
|
|
221
|
+
// to the outer retry. URL rotation (`urlGen`) advances across these.
|
|
222
|
+
// OUTER_RETRY_DELAY = backoff between region re-picks. Intentionally
|
|
223
|
+
// longer than POLLING_TIME so region discovery is rare relative to
|
|
224
|
+
// WS retries.
|
|
225
|
+
const INNER_RETRY_LIMIT = 3;
|
|
226
|
+
const OUTER_RETRY_DELAY = POLLING_TIME * 2;
|
|
227
|
+
// Extra WS-only retries on the SAME wsUrl (cached AwsInstance) BEFORE
|
|
228
|
+
// falling through to a URL-rotating retry that re-polls orchestration.
|
|
229
|
+
// Why: a fresh `default` poll asks orchestrator to RESERVE a new
|
|
230
|
+
// instance — the previous reservation is orphaned (server-side leak).
|
|
231
|
+
// One quick same-wsUrl retry catches transient WS failures (network
|
|
232
|
+
// blip, brief unavailability) without burning a second reservation.
|
|
233
|
+
// Total WS attempts before URL rotation = 1 + WS_RETRY_PER_INSTANCE.
|
|
234
|
+
const WS_RETRY_PER_INSTANCE = 1;
|
|
212
235
|
const WS_OPEN_STATE = 1;
|
|
213
236
|
const DEFAULT_AFK_TIMEOUT_PERIOD = 15;
|
|
214
237
|
const DEFAULT_AFK_TIMEOUT = 600; // 10 minutes
|
|
@@ -1276,12 +1299,6 @@ class UnrealEffects {
|
|
|
1276
1299
|
onDataChannelClose: this.unrealInitialConfig?.reconnect?.onDataChannelClose ??
|
|
1277
1300
|
DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE,
|
|
1278
1301
|
};
|
|
1279
|
-
this.destroyConnectionsCleanup$ = createEffect(() => {
|
|
1280
|
-
return this.actions$.pipe(ofType(destroyUnrealScene), tap(() => {
|
|
1281
|
-
this.commandsSender.destroy();
|
|
1282
|
-
this.videoService.setContainer(null);
|
|
1283
|
-
}));
|
|
1284
|
-
}, { dispatch: false });
|
|
1285
1302
|
this.destroyConnections$ = createEffect(() => {
|
|
1286
1303
|
return this.actions$.pipe(ofType(destroyUnrealScene), map(() => destroyRemoteConnections({ reason: DisconnectReason.Destroy })));
|
|
1287
1304
|
});
|
|
@@ -1678,9 +1695,9 @@ class AFKService extends SubService {
|
|
|
1678
1695
|
resetAfkWarningTimer() {
|
|
1679
1696
|
if (this.active) {
|
|
1680
1697
|
this.clearTimers();
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1698
|
+
// `selectWarnTimeout` already falls back to DEFAULT_AFK_TIMEOUT when
|
|
1699
|
+
// config is missing, so warnTimeout() always returns a positive number.
|
|
1700
|
+
this.warnTimer = setTimeout(() => this.showAfkOverlay(), this.warnTimeout() * 1000);
|
|
1684
1701
|
}
|
|
1685
1702
|
}
|
|
1686
1703
|
/**
|
|
@@ -1700,7 +1717,8 @@ class AFKService extends SubService {
|
|
|
1700
1717
|
}
|
|
1701
1718
|
reset() {
|
|
1702
1719
|
this.hideOverlay();
|
|
1703
|
-
|
|
1720
|
+
// startAfkWarningTimer → resetAfkWarningTimer → clearTimers() already
|
|
1721
|
+
// clears the countdown interval, no separate clearInterval needed.
|
|
1704
1722
|
this.startAfkWarningTimer();
|
|
1705
1723
|
}
|
|
1706
1724
|
showAfkOverlay() {
|
|
@@ -1903,7 +1921,7 @@ const BITRATE_MONITOR = new DataFlowMonitor(FilterModel.yellowFlag, FilterModel.
|
|
|
1903
1921
|
|
|
1904
1922
|
class VideoService extends SubService {
|
|
1905
1923
|
constructor() {
|
|
1906
|
-
super(
|
|
1924
|
+
super();
|
|
1907
1925
|
this.isVideoPlaying = this.store.selectSignal(unrealFeature.selectIsVideoPlaying);
|
|
1908
1926
|
this.video = null;
|
|
1909
1927
|
this.audio = null;
|
|
@@ -1922,6 +1940,11 @@ class VideoService extends SubService {
|
|
|
1922
1940
|
? // IMPORTANT! DO NOT CHANGE THOSE NUMBERS, LBM Stats are based on those values
|
|
1923
1941
|
interval(250).pipe(map(() => track.pcClient), switchMap$1((pcClient) => from(this.getStats(pcClient))), filter(Truthy))
|
|
1924
1942
|
: of(null)), filter(Truthy), share());
|
|
1943
|
+
this.destroyAbort = new AbortController();
|
|
1944
|
+
this.destroyRef.onDestroy(() => {
|
|
1945
|
+
this.destroyAbort.abort();
|
|
1946
|
+
this.destroy();
|
|
1947
|
+
});
|
|
1925
1948
|
}
|
|
1926
1949
|
setContainer(container = null) {
|
|
1927
1950
|
this.container = container;
|
|
@@ -1933,7 +1956,9 @@ class VideoService extends SubService {
|
|
|
1933
1956
|
this.VideoEncoderQP = Number(new TextDecoder('utf-16').decode(data));
|
|
1934
1957
|
}
|
|
1935
1958
|
create() {
|
|
1936
|
-
this.disconnect
|
|
1959
|
+
this.disconnect$
|
|
1960
|
+
.pipe(take(1), takeUntil$1(this.destroy$))
|
|
1961
|
+
.subscribe(() => this.destroy());
|
|
1937
1962
|
this.destroy();
|
|
1938
1963
|
this.createWebRtcVideo();
|
|
1939
1964
|
this.createWebRtcAudio();
|
|
@@ -2006,8 +2031,13 @@ class VideoService extends SubService {
|
|
|
2006
2031
|
window.addEventListener('pointerdown', resume, {
|
|
2007
2032
|
once: true,
|
|
2008
2033
|
capture: true,
|
|
2034
|
+
signal: this.destroyAbort.signal,
|
|
2035
|
+
});
|
|
2036
|
+
window.addEventListener('keydown', resume, {
|
|
2037
|
+
once: true,
|
|
2038
|
+
capture: true,
|
|
2039
|
+
signal: this.destroyAbort.signal,
|
|
2009
2040
|
});
|
|
2010
|
-
window.addEventListener('keydown', resume, { once: true, capture: true });
|
|
2011
2041
|
}
|
|
2012
2042
|
}
|
|
2013
2043
|
async getStats(pcClient) {
|
|
@@ -2173,7 +2203,7 @@ class VideoService extends SubService {
|
|
|
2173
2203
|
.pipe(takeUntil$1(merge(this.disconnect$, this.destroy$)))
|
|
2174
2204
|
.subscribe(() => this.changeStatusMainVideoOnScene(true));
|
|
2175
2205
|
fromEvent(this.video, 'loadedmetadata')
|
|
2176
|
-
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'), filter(() => !!this.video))
|
|
2206
|
+
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'), filter(() => !!this.video), takeUntil$1(this.destroy$))
|
|
2177
2207
|
.subscribe(() => this.store.dispatch(videoStreamInitialized()));
|
|
2178
2208
|
}
|
|
2179
2209
|
createWebRtcAudio() {
|
|
@@ -2189,18 +2219,16 @@ class VideoService extends SubService {
|
|
|
2189
2219
|
error: () => ({}),
|
|
2190
2220
|
});
|
|
2191
2221
|
}
|
|
2192
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VideoService, deps:
|
|
2222
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2193
2223
|
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VideoService }); }
|
|
2194
2224
|
}
|
|
2195
2225
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: VideoService, decorators: [{
|
|
2196
2226
|
type: Injectable
|
|
2197
|
-
}] });
|
|
2227
|
+
}], ctorParameters: () => [] });
|
|
2198
2228
|
|
|
2199
|
-
class CommandTelemetryService {
|
|
2229
|
+
class CommandTelemetryService extends SubService {
|
|
2200
2230
|
constructor() {
|
|
2201
|
-
|
|
2202
|
-
this.destroyRef = inject(DestroyRef);
|
|
2203
|
-
this.destroy$ = new Subject();
|
|
2231
|
+
super();
|
|
2204
2232
|
this.appId = 'metabox-telemetry';
|
|
2205
2233
|
this.commandsSent = {};
|
|
2206
2234
|
this.exileTimeout = 60000;
|
|
@@ -2208,13 +2236,7 @@ class CommandTelemetryService {
|
|
|
2208
2236
|
this.commandSequenceNumber = 0;
|
|
2209
2237
|
this.payloads = [];
|
|
2210
2238
|
this.httpClient = inject(HttpClient);
|
|
2211
|
-
this.unrealConfig = inject(UNREAL_CONFIG, {
|
|
2212
|
-
optional: true,
|
|
2213
|
-
});
|
|
2214
|
-
this.destroyRef.onDestroy(() => {
|
|
2215
|
-
this.destroy$.next();
|
|
2216
|
-
this.destroy$.complete();
|
|
2217
|
-
});
|
|
2239
|
+
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
2218
2240
|
this.init();
|
|
2219
2241
|
}
|
|
2220
2242
|
init() {
|
|
@@ -2648,12 +2670,11 @@ class SignallingService extends SubService {
|
|
|
2648
2670
|
this.onConfig$ = new Subject();
|
|
2649
2671
|
this.onWebRtcIce$ = new Subject();
|
|
2650
2672
|
this.onWebRtcAnswer$ = new Subject();
|
|
2651
|
-
//
|
|
2652
|
-
//
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
})));
|
|
2673
|
+
// Pure signal — no side-effects. The `close()` is wired ONCE in init()
|
|
2674
|
+
// below so a single abort event triggers exactly one close, regardless
|
|
2675
|
+
// of how many pipelines have `takeUntil(this.abort$)` attached.
|
|
2676
|
+
this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
|
|
2677
|
+
this.ws = null;
|
|
2657
2678
|
this.wsMsgHandlers = {};
|
|
2658
2679
|
this.init();
|
|
2659
2680
|
}
|
|
@@ -2662,9 +2683,24 @@ class SignallingService extends SubService {
|
|
|
2662
2683
|
return;
|
|
2663
2684
|
}
|
|
2664
2685
|
this.setHandlersFromStream();
|
|
2665
|
-
|
|
2686
|
+
// Single owner of the "close on abort" side-effect. Previously this
|
|
2687
|
+
// lived as a `tap` inside `abort$` itself, which made every
|
|
2688
|
+
// `takeUntil(this.abort$)` subscriber fire its own close().
|
|
2689
|
+
this.abort$.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
2690
|
+
this.close({
|
|
2691
|
+
code: 1000,
|
|
2692
|
+
reason: DisconnectReason.AbortEstablishingConnection,
|
|
2693
|
+
});
|
|
2694
|
+
});
|
|
2695
|
+
// `dataChannelReady` is a per-session event. Bind ONLY to destroy$, not
|
|
2696
|
+
// to abort$ — `abort$` fires on every destroyUnrealScene (route swap),
|
|
2697
|
+
// and `init()` runs once in the constructor. If these subs were torn
|
|
2698
|
+
// down by abort$, a second in-injector session (e.g. navigating between
|
|
2699
|
+
// two routes that both host <app-unreal-scene>) would never send
|
|
2700
|
+
// `p2pEstablished` / `streamPreparedForUserInteraction`. `send()` is
|
|
2701
|
+
// already a no-op on a closed WS, so there's no risk of stray messages.
|
|
2666
2702
|
this.actions$
|
|
2667
|
-
.pipe(ofType(dataChannelReady), takeUntil(
|
|
2703
|
+
.pipe(ofType(dataChannelReady), takeUntil(this.destroy$))
|
|
2668
2704
|
.subscribe(() => {
|
|
2669
2705
|
this.send({
|
|
2670
2706
|
type: 'p2pEstablished',
|
|
@@ -2675,7 +2711,7 @@ class SignallingService extends SubService {
|
|
|
2675
2711
|
this.actions$
|
|
2676
2712
|
.pipe(ofType(dataChannelReady), switchMap(() => this.store
|
|
2677
2713
|
.select(unrealFeature.selectViewportReady)
|
|
2678
|
-
.pipe(filter(Truthy), take(1))), takeUntil(
|
|
2714
|
+
.pipe(filter(Truthy), take(1))), takeUntil(this.destroy$))
|
|
2679
2715
|
.subscribe(() => {
|
|
2680
2716
|
this.send({
|
|
2681
2717
|
type: 'streamPreparedForUserInteraction',
|
|
@@ -2692,36 +2728,93 @@ class SignallingService extends SubService {
|
|
|
2692
2728
|
return EMPTY;
|
|
2693
2729
|
}
|
|
2694
2730
|
Logger.log('CONNECT TO SIGNALLING');
|
|
2731
|
+
// Side-effects that belong to one logical `connectToSignaling`
|
|
2732
|
+
// invocation (NOT one retry attempt). Wrapping them in a `defer`
|
|
2733
|
+
// makes them fire on subscribe (lazy), but only ONCE — the retry
|
|
2734
|
+
// below sits INSIDE this defer's projection so re-subscribing the
|
|
2735
|
+
// inner pipeline does not re-trigger these.
|
|
2695
2736
|
return defer(() => {
|
|
2696
|
-
// Preparation for WebSocket Orchestration. Inside defer so these
|
|
2697
|
-
// side effects only fire once the caller actually subscribes.
|
|
2698
2737
|
this.correlationId = generateUuid();
|
|
2699
2738
|
this.commandTelemetryService.reset();
|
|
2700
2739
|
this.startEstablishingConnection();
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2740
|
+
// Staged retry:
|
|
2741
|
+
// * `getRegion()` is pulled in front of the retry — region
|
|
2742
|
+
// discovery happens once per outer cycle.
|
|
2743
|
+
// * `connectAttempt(urls)` owns inner retry + URL rotation +
|
|
2744
|
+
// WS connect. WS/orch failures stay inside it (capped at
|
|
2745
|
+
// `INNER_RETRY_LIMIT`).
|
|
2746
|
+
// * If the inner retry exhausts, the error propagates to the
|
|
2747
|
+
// OUTER retry below which re-runs `getRegion()` (rare, real
|
|
2748
|
+
// "region down" case). Outer delay > inner so escalation is
|
|
2749
|
+
// bounded.
|
|
2750
|
+
const connectCycle$ = this.getRegion().pipe(tap((region) => this.region.set(region ?? '')), map$1((region) => this.adaptUrlsToRegion(urlList, region)), switchMap((urls) => this.connectAttempt(urls)), takeUntil(this.abort$));
|
|
2751
|
+
return connectCycle$.pipe(retry({
|
|
2752
|
+
delay: (err, attempt) => {
|
|
2753
|
+
this.store.dispatch(signalingRetryAttempted({
|
|
2754
|
+
attempt,
|
|
2755
|
+
reason: err?.message ?? 'unknown',
|
|
2756
|
+
kind: 'region-cycle',
|
|
2757
|
+
}));
|
|
2758
|
+
Logger.info(`Outer retry (region re-pick) in ${OUTER_RETRY_DELAY}ms. Attempt# ${attempt}.`, err?.message);
|
|
2759
|
+
return timer(OUTER_RETRY_DELAY);
|
|
2760
|
+
},
|
|
2761
|
+
}));
|
|
2762
|
+
}).pipe(takeUntil(this.abort$));
|
|
2763
|
+
}
|
|
2764
|
+
/**
|
|
2765
|
+
* One inner cycle: rotate `urlGen` over the pool, resolve via
|
|
2766
|
+
* {@link getAwsInstance}, open WS via {@link connectToCirrus},
|
|
2767
|
+
* retry up to {@link INNER_RETRY_LIMIT} on any hard failure. The
|
|
2768
|
+
* urlGen lives in this closure so retries advance through the
|
|
2769
|
+
* pool. Exhaustion bubbles to {@link connectToSignaling}'s outer
|
|
2770
|
+
* retry which re-pings the region.
|
|
2771
|
+
*/
|
|
2772
|
+
connectAttempt(urls) {
|
|
2773
|
+
const urlGen = getActiveUrl(urls);
|
|
2774
|
+
return defer(() => this.getAwsInstance(`${urlGen.next().value}`)).pipe(tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl }) =>
|
|
2775
|
+
// ── WS-only retry tier ─────────────────────────────────────────────
|
|
2776
|
+
// Tries the SAME `wsUrl` up to `WS_RETRY_PER_INSTANCE` extra times
|
|
2777
|
+
// before letting the error bubble up to the outer (URL-rotating)
|
|
2778
|
+
// retry. This is important because the outer retry calls
|
|
2779
|
+
// `getAwsInstance(url)` again, which polls orchestration again,
|
|
2780
|
+
// which causes orchestrator to RESERVE A NEW instance — the
|
|
2781
|
+
// previous reservation is orphaned (server-side leak).
|
|
2782
|
+
//
|
|
2783
|
+
// The same-wsUrl retry catches transient WS failures (network
|
|
2784
|
+
// blip, brief region unavailability, slow cold handshake) without
|
|
2785
|
+
// burning a second reservation. If WS still fails after these
|
|
2786
|
+
// extra attempts, the instance is genuinely unusable and we
|
|
2787
|
+
// accept the cost of rotating.
|
|
2788
|
+
this.connectToCirrus(wsUrl ?? '').pipe(filter(Truthy), take(1), retry({
|
|
2789
|
+
count: WS_RETRY_PER_INSTANCE,
|
|
2790
|
+
delay: (err, attempt) => {
|
|
2791
|
+
this.store.dispatch(signalingRetryAttempted({
|
|
2792
|
+
attempt,
|
|
2793
|
+
reason: err?.message ?? 'unknown',
|
|
2794
|
+
kind: 'ws-retry',
|
|
2795
|
+
}));
|
|
2796
|
+
this.showStatusMessage(UnrealStatusMessage.CONNECTING_TO_SESSION);
|
|
2797
|
+
Logger.info(`WS-only retry on SAME url in ${POLLING_TIME}ms. Attempt# ${attempt}.`, err?.message);
|
|
2798
|
+
return timer(POLLING_TIME);
|
|
2799
|
+
},
|
|
2800
|
+
}))),
|
|
2801
|
+
// ── URL-rotating retry tier ─────────────────────────────────────────
|
|
2802
|
+
// Triggers only when the WS-only retries above are exhausted. Each
|
|
2803
|
+
// attempt advances `urlGen` → new `getAwsInstance(url)` → new
|
|
2804
|
+
// orchestration poll → new AwsInstance reservation.
|
|
2708
2805
|
retry({
|
|
2806
|
+
count: INNER_RETRY_LIMIT,
|
|
2709
2807
|
delay: (err, attempt) => {
|
|
2710
2808
|
this.store.dispatch(signalingRetryAttempted({
|
|
2711
2809
|
attempt,
|
|
2712
2810
|
reason: err?.message ?? 'unknown',
|
|
2713
2811
|
kind: 'connect',
|
|
2714
2812
|
}));
|
|
2715
|
-
|
|
2813
|
+
this.showStatusMessage(UnrealStatusMessage.CONNECTING_TO_SESSION);
|
|
2814
|
+
Logger.info(`Inner retry (WS+orch) in ${POLLING_TIME}ms. Attempt# ${attempt}.`, err?.message);
|
|
2716
2815
|
return timer(POLLING_TIME);
|
|
2717
2816
|
},
|
|
2718
|
-
|
|
2719
|
-
}),
|
|
2720
|
-
// abort$ sits OUTSIDE retry, so it kills the entire retry loop,
|
|
2721
|
-
// not just the current attempt. Each inner createWebSocket also
|
|
2722
|
-
// receives abort$ independently, so mid-connection attempts are
|
|
2723
|
-
// canceled too.
|
|
2724
|
-
takeUntil(this.abort$));
|
|
2817
|
+
}));
|
|
2725
2818
|
}
|
|
2726
2819
|
startEstablishingConnection() {
|
|
2727
2820
|
this.showStatusMessage(UnrealStatusMessage.STARTING_YOUR_SESSION);
|
|
@@ -2736,174 +2829,114 @@ class SignallingService extends SubService {
|
|
|
2736
2829
|
});
|
|
2737
2830
|
}
|
|
2738
2831
|
/**
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2741
|
-
*
|
|
2742
|
-
* so each subscription (including those triggered by `retry`) pulls the **next**
|
|
2743
|
-
* URL candidate from a generator created by `getActiveUrl(urlsPool)`.
|
|
2744
|
-
*
|
|
2745
|
-
* ### Execution flow
|
|
2746
|
-
*
|
|
2747
|
-
* 1. **Telemetry** — Starts a `getSignaling` span for end-to-end timing.
|
|
2748
|
-
* 2. **Candidate URL selection** — A stateful generator (`urlGen`) is created
|
|
2749
|
-
* from `urlsPool`. Each resubscription (e.g., via `retry`) advances to the next URL.
|
|
2750
|
-
* 3. **Short-circuit for WS URLs** — If the picked candidate already matches
|
|
2751
|
-
* `ws://` or `wss://`, emit `{ wsUrl, pollingUrl: null, instanceName: null }`
|
|
2752
|
-
* immediately and complete (no HTTP request).
|
|
2753
|
-
* 4. **Await client/view identifiers** — `this.store.select(selectClientAndViewIds)`
|
|
2754
|
-
* is tapped for logging (missing IDs) and then filtered to require both `clientId`
|
|
2755
|
-
* and `viewId` to be truthy.
|
|
2756
|
-
* 5. **Single in-flight HTTP orchestration call** — Uses `exhaustMap` to issue
|
|
2757
|
-
* `GET {signalingUrl}{clientId}/{viewId}`. New `{clientId, viewId}` emissions
|
|
2758
|
-
* while the request is in flight are **ignored** until completion, preventing overlap.
|
|
2759
|
-
* 6. **Per-attempt timeout** — `timeout(WS_TIMEOUT)` caps how long we wait for a response.
|
|
2760
|
-
* On timeout, the attempt errors and the outer `retry` schedules the next URL.
|
|
2761
|
-
* 7. **Response validation and progress reporting** — A `filter`:
|
|
2762
|
-
* - Calls `TelemetryStop('getSignaling', { ...data, multi: true })` on receipt.
|
|
2763
|
-
* - If `data.signallingServer === ''` or `data.error`:
|
|
2764
|
-
* shows a status message, optionally dispatches `setStatusPercentSignallingServer`
|
|
2765
|
-
* with `data.info.percent`, and **throws** to fail the attempt (triggers `retry`).
|
|
2766
|
-
* 8. **Mapping to `AwsInstance`** — Converts the validated orchestration payload into:
|
|
2767
|
-
* - `wsUrl`: via `httpUrlToWs(\`\${location.protocol}//\${data.signallingServer}\`)`
|
|
2768
|
-
* - `pollingUrl`: the base `signalingUrl` that succeeded
|
|
2769
|
-
* - `instanceName`: the (validated) `data.signallingServer`
|
|
2770
|
-
* 9. **Retry policy** — On *any* upstream error (timeout, HTTP error, invalid payload),
|
|
2771
|
-
* `retry({ delay })`:
|
|
2772
|
-
* - Shows a “connecting” status,
|
|
2773
|
-
* - Logs the attempt number,
|
|
2774
|
-
* - Waits `WS_TIMEOUT` ms (via `timer`) and **resubscribes**, advancing `urlGen`.
|
|
2775
|
-
*
|
|
2776
|
-
* ### Concurrency semantics
|
|
2777
|
-
*
|
|
2778
|
-
* - `exhaustMap` guarantees a **single** HTTP request per attempt; later
|
|
2779
|
-
* `{clientId, viewId}` emissions are ignored until the current request completes.
|
|
2780
|
-
*
|
|
2781
|
-
* ### Telemetry semantics
|
|
2782
|
-
*
|
|
2783
|
-
* - `trackStartCommand('EXT-getSignaling')` begins before work.
|
|
2784
|
-
* - `trackStopCommand('getSignaling', {...})` runs on receipt of an orchestration response.
|
|
2785
|
-
* If an attempt fails (e.g., timeout), stopping the span is the responsibility of
|
|
2786
|
-
* your telemetry layer or a `finalize` elsewhere if desired.
|
|
2787
|
-
*
|
|
2788
|
-
* ### Error & retry semantics
|
|
2789
|
-
*
|
|
2790
|
-
* - Missing/never-emitted IDs → timeout → retry with next URL.
|
|
2791
|
-
* - HTTP/network error → retry with next URL.
|
|
2792
|
-
* - Invalid orchestration payload (`error` set or empty `signallingServer`) → throw in
|
|
2793
|
-
* validation filter → retry with next URL.
|
|
2794
|
-
* - Short-circuit WS path **does not** retry (emits once, completes).
|
|
2795
|
-
*
|
|
2796
|
-
* @param urlsPool - Ordered list of base URLs to probe. Retries advance through this list
|
|
2797
|
-
* until a working signaling server is found. A candidate may also be a direct `ws://`/`wss://`
|
|
2798
|
-
* URL to short-circuit HTTP orchestration.
|
|
2799
|
-
*
|
|
2800
|
-
* @returns Observable that emits exactly one {@link AwsInstance} on success and then completes.
|
|
2801
|
-
* On failure, the stream errors; the built-in `retry` operator re-subscribes after `WS_TIMEOUT`
|
|
2802
|
-
* and advances to the next URL candidate.
|
|
2803
|
-
*
|
|
2804
|
-
* @throws Emits an error within the observable chain (not a thrown synchronous exception) when:
|
|
2805
|
-
* - The HTTP request fails or times out.
|
|
2806
|
-
* - The orchestration response indicates an error or an empty `signallingServer`.
|
|
2807
|
-
* - Any other operator in the chain surfaces an error.
|
|
2808
|
-
*
|
|
2809
|
-
* @remarks
|
|
2810
|
-
* - The URL generator (`urlGen`) is created **outside** `defer`, so resubscriptions
|
|
2811
|
-
* advance the pool. Creating it inside `defer` would restart from the first URL each retry.
|
|
2812
|
-
* - `location.protocol` is used to derive `ws` vs. `wss`. If this may run in non-browser
|
|
2813
|
-
* contexts (e.g., SSR), guard or abstract this logic.
|
|
2814
|
-
* - Consider capping retries with `count` in `retry({ count, delay })` when appropriate.
|
|
2815
|
-
*
|
|
2816
|
-
* @example
|
|
2817
|
-
* ```ts
|
|
2818
|
-
* getAwsInstance(['https://signaling.example.com/', 'wss://direct.example.com'])
|
|
2819
|
-
* .subscribe({
|
|
2820
|
-
* next: ({ wsUrl }) => connect(wsUrl),
|
|
2821
|
-
* error: (e) => console.error('Unable to establish signaling:', e),
|
|
2822
|
-
* });
|
|
2823
|
-
* ```
|
|
2824
|
-
*
|
|
2825
|
-
* @see httpUrlToWs
|
|
2826
|
-
* @see selectClientAndViewIds
|
|
2827
|
-
* @see setStatusPercentSignallingServer
|
|
2832
|
+
* Resolve one URL to an `AwsInstance`. Dispatches to
|
|
2833
|
+
* {@link directWsInstance} for `ws://` / `wss://` URLs, otherwise
|
|
2834
|
+
* to {@link pollOrchestration}.
|
|
2828
2835
|
*/
|
|
2829
|
-
getAwsInstance(
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2836
|
+
getAwsInstance(url) {
|
|
2837
|
+
return url.match(/^ws(s?):\/\//i)
|
|
2838
|
+
? this.directWsInstance(url)
|
|
2839
|
+
: this.pollOrchestration(url);
|
|
2840
|
+
}
|
|
2841
|
+
/**
|
|
2842
|
+
* Direct `ws://` / `wss://` short-circuit — no HTTP orchestration.
|
|
2843
|
+
* Waits for `clientId` and `viewId` to be truthy in the store, then
|
|
2844
|
+
* emits a `Direct Connection` `AwsInstance` and completes.
|
|
2845
|
+
*/
|
|
2846
|
+
directWsInstance(url) {
|
|
2847
|
+
return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1), map$1(() => ({
|
|
2848
|
+
wsUrl: url,
|
|
2849
|
+
pollingUrl: null,
|
|
2850
|
+
instanceName: 'Direct Connection',
|
|
2851
|
+
})));
|
|
2852
|
+
}
|
|
2853
|
+
/**
|
|
2854
|
+
* HTTP-orchestration path. Brackets {@link pollLoop} with the
|
|
2855
|
+
* `pollingStarted`/`pollingEnded` actions and the `EXT-getSignaling`
|
|
2856
|
+
* telemetry span (start in `defer`, stop in `finalize` with the
|
|
2857
|
+
* last response captured in the closure). Maps the final ready
|
|
2858
|
+
* response to an `AwsInstance`.
|
|
2859
|
+
*/
|
|
2860
|
+
pollOrchestration(url) {
|
|
2833
2861
|
return defer(() => {
|
|
2834
|
-
|
|
2835
|
-
// Short-circuit if the candidate is already a WS endpoint.
|
|
2836
|
-
if (signalingUrl.match(/^ws(s?):\/\//i)) {
|
|
2837
|
-
return this.store.select(selectClientAndViewIds).pipe(filter(({ clientId, viewId }) => !!clientId && !!viewId), map$1(() => {
|
|
2838
|
-
return {
|
|
2839
|
-
wsUrl: signalingUrl,
|
|
2840
|
-
pollingUrl: null,
|
|
2841
|
-
instanceName: 'Direct Connection',
|
|
2842
|
-
};
|
|
2843
|
-
}));
|
|
2844
|
-
}
|
|
2845
|
-
// Old (HTTP-polling) orchestration flow — mark the round trip so
|
|
2846
|
-
// the signaling-phase timeline can show it as a distinct segment.
|
|
2862
|
+
let lastResp;
|
|
2847
2863
|
this.store.dispatch(pollingStarted());
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
tap(({ clientId, viewId }) => {
|
|
2864
|
+
this.commandTelemetryService.trackStartCommand('EXT-getSignaling');
|
|
2865
|
+
return this.store.select(selectClientAndViewIds).pipe(tap(({ clientId, viewId }) => {
|
|
2851
2866
|
if (!clientId || !viewId) {
|
|
2852
2867
|
Logger.error('Client ID or View ID is not set');
|
|
2853
2868
|
}
|
|
2854
|
-
}),
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
timeout(POLLING_TIME),
|
|
2863
|
-
// Validate response; report progress; fail fast on invalid payloads.
|
|
2864
|
-
filter((data) => {
|
|
2865
|
-
this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...data, multi: true });
|
|
2866
|
-
if (data.signallingServer === '' || data.error) {
|
|
2867
|
-
this.showStatusMessage(data?.mm_message || 'Server not found');
|
|
2868
|
-
if (data?.info) {
|
|
2869
|
-
this.store.dispatch(setStatusPercentSignallingServer({
|
|
2870
|
-
percent: data.info.percent,
|
|
2871
|
-
}));
|
|
2872
|
-
}
|
|
2873
|
-
// Throwing here fails the attempt and activates the retry strategy.
|
|
2874
|
-
throw new Error(`Orchestration message => ${data?.mm_message || 'Polling retry'}`);
|
|
2875
|
-
}
|
|
2876
|
-
return true;
|
|
2877
|
-
}),
|
|
2878
|
-
// Map a valid orchestration payload into the final AwsInstance.
|
|
2879
|
-
map$1((data) => {
|
|
2869
|
+
}), filter(({ clientId, viewId }) => !!clientId && !!viewId), take(1), switchMap(({ clientId, viewId }) => this.pollLoop(`${url}${clientId}/${viewId}`).pipe(tap((data) => {
|
|
2870
|
+
lastResp = data;
|
|
2871
|
+
}))), map$1((data) => ({
|
|
2872
|
+
wsUrl: httpUrlToWs(`${location.protocol}//${data.signallingServer}`),
|
|
2873
|
+
pollingUrl: url,
|
|
2874
|
+
instanceName: data.signallingServer ?? '',
|
|
2875
|
+
})), finalize(() => {
|
|
2876
|
+
this.commandTelemetryService.trackStopCommand('EXT-getSignaling', true, { ...(lastResp ?? {}), multi: true });
|
|
2880
2877
|
this.store.dispatch(pollingEnded());
|
|
2881
|
-
const wsUrl = httpUrlToWs(`${location.protocol}//${data.signallingServer}`);
|
|
2882
|
-
return {
|
|
2883
|
-
wsUrl,
|
|
2884
|
-
pollingUrl: signalingUrl,
|
|
2885
|
-
instanceName: data.signallingServer ?? '',
|
|
2886
|
-
};
|
|
2887
2878
|
}));
|
|
2888
|
-
})
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Poll the SAME `url` until ready, or until HTTP 4xx says the URL
|
|
2883
|
+
* is dead.
|
|
2884
|
+
*
|
|
2885
|
+
* | Response | Class | Action |
|
|
2886
|
+
* | ---------------------------------------- | ---------- | ---------------------------- |
|
|
2887
|
+
* | 200 `{ signallingServer: 'cirrus', ... }`| ✅ Ready | `first()` emits, loop ends |
|
|
2888
|
+
* | 200 empty `signallingServer` (any error/info/mm_message) | ⏳ Progress | Dispatch status+percent, re-poll same URL |
|
|
2889
|
+
* | HTTP 4xx (except 429) | ❌ Hard | Re-throw → caller rotates URL |
|
|
2890
|
+
* | HTTP 5xx / 429 / timeout / network | ⚠️ Soft | `EMPTY` → `repeat` same URL |
|
|
2891
|
+
*
|
|
2892
|
+
* `data.error` is NOT treated as fatal — the backend reuses it for
|
|
2893
|
+
* progress strings like `"Ready in ~2m 23s"`. The only fatal exits
|
|
2894
|
+
* are HTTP 4xx and `abort$` upstream.
|
|
2895
|
+
*/
|
|
2896
|
+
pollLoop(url) {
|
|
2897
|
+
return defer(() => this.httpClient.get(url)).pipe(timeout(POLLING_TIME), catchError((err) => {
|
|
2898
|
+
// HTTP 4xx (except 429) = the URL itself is dead → rotate.
|
|
2899
|
+
const status = err?.status;
|
|
2900
|
+
if (typeof status === 'number' &&
|
|
2901
|
+
status >= 400 &&
|
|
2902
|
+
status < 500 &&
|
|
2903
|
+
status !== 429) {
|
|
2904
|
+
Logger.warn(`Polling HARD error on ${url} (HTTP ${status}); rotating URL`);
|
|
2905
|
+
return throwError(() => err);
|
|
2906
|
+
}
|
|
2907
|
+
// Everything else = soft → re-poll same URL.
|
|
2908
|
+
Logger.warn(`Polling soft error on ${url} (${err?.message ?? err}); re-polling same URL`);
|
|
2909
|
+
return EMPTY;
|
|
2910
|
+
}), tap((data) => {
|
|
2911
|
+
// Ready response is handled by `first()` below — nothing to
|
|
2912
|
+
// dispatch here. Empty `signallingServer` = progress.
|
|
2913
|
+
if (data.signallingServer)
|
|
2914
|
+
return;
|
|
2915
|
+
this.showStatusMessage(data?.mm_message || 'Server not found');
|
|
2916
|
+
if (data?.info) {
|
|
2917
|
+
this.store.dispatch(setStatusPercentSignallingServer({
|
|
2918
|
+
percent: data.info.percent,
|
|
2896
2919
|
}));
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2920
|
+
}
|
|
2921
|
+
}),
|
|
2922
|
+
// Re-subscribe (= poll again) after each completed response OR
|
|
2923
|
+
// after `catchError` swallowed a transient failure.
|
|
2924
|
+
repeat({ delay: () => timer(POLLING_TIME) }),
|
|
2925
|
+
// Stop the poll loop as soon as we get a ready response.
|
|
2926
|
+
first((data) => !!data.signallingServer));
|
|
2903
2927
|
}
|
|
2904
2928
|
connectToCirrus(wsUrl) {
|
|
2905
|
-
|
|
2906
|
-
|
|
2929
|
+
// Wrapped in `defer` so each subscription (including ws-retry
|
|
2930
|
+
// re-subscriptions from `connectAttempt`) starts a FRESH WS attempt:
|
|
2931
|
+
// the previous socket is closed and a new one is created. Without
|
|
2932
|
+
// this, the WS-only retry would just re-subscribe the SAME WS
|
|
2933
|
+
// observable, which in production-defer'd `createWebSocket` would
|
|
2934
|
+
// still create a new socket, but in tests would re-emit the same
|
|
2935
|
+
// (already-errored) mock observable.
|
|
2936
|
+
return defer(() => {
|
|
2937
|
+
this.close({ code: 1000, reason: DisconnectReason.NewConnectionAttempt });
|
|
2938
|
+
return createWebSocket(wsUrl);
|
|
2939
|
+
}).pipe(takeUntil(this.abort$), tapLog('WS CREATED'), tap((ws) => {
|
|
2907
2940
|
this.commandTelemetryService.trackStartCommand('EXT-iceCandidate');
|
|
2908
2941
|
this.addWsHandlers(ws);
|
|
2909
2942
|
}));
|
|
@@ -3004,7 +3037,7 @@ class SignallingService extends SubService {
|
|
|
3004
3037
|
* Check if the WebSocket connection is open and ready.
|
|
3005
3038
|
*/
|
|
3006
3039
|
isConnected() {
|
|
3007
|
-
return this.ws && this.ws.readyState === WS_OPEN_STATE;
|
|
3040
|
+
return !!(this.ws && this.ws.readyState === WS_OPEN_STATE);
|
|
3008
3041
|
}
|
|
3009
3042
|
getRegion() {
|
|
3010
3043
|
Logger.log('Start Getting Region');
|
|
@@ -3042,6 +3075,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3042
3075
|
this.actions$
|
|
3043
3076
|
.pipe(ofType(dropPC), takeUntil(this.destroy$))
|
|
3044
3077
|
.subscribe(() => this.pcClient?.close());
|
|
3078
|
+
this.destroyRef.onDestroy(() => this.closePC());
|
|
3045
3079
|
}
|
|
3046
3080
|
init() {
|
|
3047
3081
|
this.signallingSrv.onConfig$
|
|
@@ -3176,7 +3210,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3176
3210
|
this.commandTelemetryService.trackStartCommand('EXT-Browser ICE connection');
|
|
3177
3211
|
this.commandTelemetryService.trackStartCommand('EXT-Signaling state change');
|
|
3178
3212
|
this.handlePeerConnectionEvents(this.pcClient);
|
|
3179
|
-
this.disconnect$.pipe(take(1)).subscribe(() => {
|
|
3213
|
+
this.disconnect$.pipe(take(1), takeUntil(this.destroy$)).subscribe(() => {
|
|
3180
3214
|
this.closePC();
|
|
3181
3215
|
});
|
|
3182
3216
|
}
|
|
@@ -3383,39 +3417,39 @@ class WebRtcPlayerService extends SubService {
|
|
|
3383
3417
|
}
|
|
3384
3418
|
handlePeerConnectionEvents(pcClient) {
|
|
3385
3419
|
fromEvent(pcClient, 'signalingstatechange')
|
|
3386
|
-
.pipe(map$1(() => this.pcClient?.signalingState), takeUntil(this.disconnect$))
|
|
3420
|
+
.pipe(map$1(() => this.pcClient?.signalingState), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3387
3421
|
.subscribe((state) => {
|
|
3388
3422
|
Logger.info('[Signaling state change] |', state, '|');
|
|
3389
3423
|
this.commandTelemetryService.trackStopCommand('EXT-Signaling state change', true, { state, multi: true });
|
|
3390
3424
|
});
|
|
3391
3425
|
fromEvent(pcClient, 'iceconnectionstatechange')
|
|
3392
|
-
.pipe(map$1(() => this.pcClient?.iceConnectionState), takeUntil(this.disconnect$))
|
|
3426
|
+
.pipe(map$1(() => this.pcClient?.iceConnectionState), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3393
3427
|
.subscribe((state) => {
|
|
3394
3428
|
Logger.info('[Browser ICE connection] |', state, '|');
|
|
3395
3429
|
this.commandTelemetryService.trackStopCommand('EXT-Browser ICE connection', true, { state, multi: true });
|
|
3396
3430
|
});
|
|
3397
3431
|
fromEvent(pcClient, 'icegatheringstatechange')
|
|
3398
|
-
.pipe(map$1(() => this.pcClient?.iceGatheringState), takeUntil(this.disconnect$))
|
|
3432
|
+
.pipe(map$1(() => this.pcClient?.iceGatheringState), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3399
3433
|
.subscribe((state) => {
|
|
3400
3434
|
Logger.info('[Browser ICE gathering] |', state, '|');
|
|
3401
3435
|
this.commandTelemetryService.trackStopCommand('EXT-Browser ICE gathering', true, { state, multi: true });
|
|
3402
3436
|
});
|
|
3403
3437
|
fromEvent(pcClient, 'connectionstatechange')
|
|
3404
|
-
.pipe(map$1(() => this.pcClient?.connectionState), takeUntil(this.disconnect$))
|
|
3438
|
+
.pipe(map$1(() => this.pcClient?.connectionState), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3405
3439
|
.subscribe((state) => {
|
|
3406
3440
|
Logger.info('[RTCPeerConnection state] |', state, '|');
|
|
3407
3441
|
});
|
|
3408
3442
|
fromEvent(pcClient, 'icecandidate')
|
|
3409
|
-
.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(this.disconnect$))
|
|
3443
|
+
.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$)))
|
|
3410
3444
|
.subscribe((candidate) => this.signallingSrv.send({ type: 'iceCandidate', candidate }));
|
|
3411
3445
|
// When the server creates the DataChannel (server-offer flow),
|
|
3412
3446
|
// we receive it here via the 'datachannel' event
|
|
3413
3447
|
fromEvent(pcClient, 'datachannel')
|
|
3414
|
-
.pipe(tap$1((e) => Logger.info(`[DATACHANNEL] Received remote DC: label="${e.channel.label}" id=${e.channel.id} protocol="${e.channel.protocol}" ordered=${e.channel.ordered}`)), map$1((e) => e.channel), takeUntil(this.disconnect$))
|
|
3448
|
+
.pipe(tap$1((e) => Logger.info(`[DATACHANNEL] Received remote DC: label="${e.channel.label}" id=${e.channel.id} protocol="${e.channel.protocol}" ordered=${e.channel.ordered}`)), map$1((e) => e.channel), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3415
3449
|
.subscribe((datachannel) => {
|
|
3416
3450
|
this.dcClient = this.setupDataChannelCallbacks(datachannel);
|
|
3417
3451
|
});
|
|
3418
|
-
const onTrack$ = fromEvent(pcClient, 'track').pipe(takeUntil(this.disconnect$), filter((e) => !!e.track), tap$1((e) => Logger.info('Got track - ' +
|
|
3452
|
+
const onTrack$ = fromEvent(pcClient, 'track').pipe(takeUntil(merge(this.disconnect$, this.destroy$)), filter((e) => !!e.track), tap$1((e) => Logger.info('Got track - ' +
|
|
3419
3453
|
e.track.kind +
|
|
3420
3454
|
' id=' +
|
|
3421
3455
|
e.track.id +
|
|
@@ -3496,7 +3530,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3496
3530
|
fromEvent(datachannel, 'close')
|
|
3497
3531
|
.pipe(
|
|
3498
3532
|
// Only dispatch disconnect if not already reconnecting to avoid infinite loops
|
|
3499
|
-
filter(() => !this.isReconnecting()), take(1))
|
|
3533
|
+
filter(() => !this.isReconnecting()), take(1), takeUntil(this.destroy$))
|
|
3500
3534
|
.subscribe((e) => {
|
|
3501
3535
|
Logger.warn(`[DATACHANNEL] Data channel disconnected: ${datachannel.label}(${datachannel.id})`, e);
|
|
3502
3536
|
this.store.dispatch(disconnectStream({
|
|
@@ -3505,7 +3539,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3505
3539
|
}));
|
|
3506
3540
|
});
|
|
3507
3541
|
fromEvent(datachannel, 'error')
|
|
3508
|
-
.pipe(takeUntil(this.disconnect$))
|
|
3542
|
+
.pipe(takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3509
3543
|
.subscribe((e) => {
|
|
3510
3544
|
const message = `[DATACHANNEL] Data channel "${datachannel.label}" error. id:${datachannel.id}`;
|
|
3511
3545
|
Logger.error(message, e);
|
|
@@ -3513,7 +3547,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3513
3547
|
fromEvent(datachannel, 'open')
|
|
3514
3548
|
.pipe(tap$1(() => {
|
|
3515
3549
|
Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
|
|
3516
|
-
}), takeUntil(this.disconnect$))
|
|
3550
|
+
}), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3517
3551
|
.subscribe(() => {
|
|
3518
3552
|
this.commandTelemetryService.trackStopCommand('EXT-DataChannel Connection');
|
|
3519
3553
|
this.onDataChannelConnected();
|
|
@@ -3526,7 +3560,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3526
3560
|
`[DATACHANNEL] Message received on "${datachannel.label}" id=${datachannel.id}, byteCode=${view[0]}, size=${view.byteLength}`,
|
|
3527
3561
|
);
|
|
3528
3562
|
}),*/
|
|
3529
|
-
map$1((e) => e.data), takeUntil(this.disconnect$))
|
|
3563
|
+
map$1((e) => e.data), takeUntil(merge(this.disconnect$, this.destroy$)))
|
|
3530
3564
|
.subscribe((data) => this.onDataChannelMessage$.next(data));
|
|
3531
3565
|
return datachannel;
|
|
3532
3566
|
}
|
|
@@ -4025,25 +4059,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4025
4059
|
}]
|
|
4026
4060
|
}] });
|
|
4027
4061
|
|
|
4028
|
-
class UnrealCommunicatorService {
|
|
4062
|
+
class UnrealCommunicatorService extends SubService {
|
|
4029
4063
|
constructor() {
|
|
4030
|
-
|
|
4031
|
-
this.actions$ = inject(Actions);
|
|
4064
|
+
super();
|
|
4032
4065
|
this.commandTelemetryService = inject(CommandTelemetryService);
|
|
4033
4066
|
this.webRtcPlayerService = inject(WebRtcPlayerService);
|
|
4034
4067
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4035
|
-
this.destroyRef = inject(DestroyRef);
|
|
4036
|
-
this.destroy$ = new Subject();
|
|
4037
4068
|
this.cirrusConnected = this.store.selectSignal(unrealFeature.selectCirrusConnected);
|
|
4038
4069
|
this.afkIgnoredCommands = new Set(['executeConsoleCommand']);
|
|
4039
4070
|
this.skipAfkReset = false;
|
|
4040
4071
|
// Throttle: input events fire 60-200x/sec, but AFK timeouts are measured in
|
|
4041
4072
|
// seconds. Leading-edge throttle dispatches once, then suppresses for 1s.
|
|
4042
4073
|
this.resetAfk$ = new Subject();
|
|
4043
|
-
this.destroyRef.onDestroy(() => {
|
|
4044
|
-
this.destroy$.next();
|
|
4045
|
-
this.destroy$.complete();
|
|
4046
|
-
});
|
|
4047
4074
|
this.resetAfk$
|
|
4048
4075
|
.pipe(throttleTime(1000), takeUntil$1(this.destroy$))
|
|
4049
4076
|
.subscribe(() => this.store.dispatch(resetAfk()));
|
|
@@ -4065,12 +4092,6 @@ class UnrealCommunicatorService {
|
|
|
4065
4092
|
this.requestQualityControl();
|
|
4066
4093
|
});
|
|
4067
4094
|
}
|
|
4068
|
-
destroy() {
|
|
4069
|
-
this.destroy$.next();
|
|
4070
|
-
// Recreate the Subject so it remains usable for the next session.
|
|
4071
|
-
// A completed Subject silently ignores all future next/complete calls.
|
|
4072
|
-
this.destroy$ = new Subject();
|
|
4073
|
-
}
|
|
4074
4095
|
sendCommandToUnreal(data) {
|
|
4075
4096
|
if (this.isPlaywrightMode()) {
|
|
4076
4097
|
return;
|
|
@@ -4655,15 +4676,21 @@ class RegionsPingService {
|
|
|
4655
4676
|
constructor() {
|
|
4656
4677
|
this.httpClient = inject(HttpClient);
|
|
4657
4678
|
this.store = inject(Store);
|
|
4658
|
-
this.
|
|
4679
|
+
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4680
|
+
this.destroyRef = inject(DestroyRef);
|
|
4681
|
+
// Aborted when the injector is destroyed. Forwarded into every
|
|
4682
|
+
// `startPinging` call's winnerController so in-flight fetches cancel
|
|
4683
|
+
// immediately on route exit instead of running to natural completion.
|
|
4684
|
+
this.destroyController = new AbortController();
|
|
4659
4685
|
this.config = {
|
|
4660
4686
|
ping_count: 2,
|
|
4661
4687
|
ping_timeout: 2000,
|
|
4662
4688
|
max_parallel_requests: 10,
|
|
4663
4689
|
bust_cache: true,
|
|
4664
4690
|
};
|
|
4691
|
+
this.destroyRef.onDestroy(() => this.destroyController.abort());
|
|
4665
4692
|
}
|
|
4666
|
-
getFastest(regionListUrl = this.
|
|
4693
|
+
getFastest(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
4667
4694
|
// Self-announce every lifecycle waypoint of the region-discovery
|
|
4668
4695
|
// flow (start / providers-fetched / resolved). The final
|
|
4669
4696
|
// `regionResolved` forwards the winner's full ping-sample array
|
|
@@ -4680,15 +4707,32 @@ class RegionsPingService {
|
|
|
4680
4707
|
const defaultTimeout = providers.timeout ?? this.config.ping_timeout;
|
|
4681
4708
|
return from(this.startPinging(providers.regions, defaultTimeout));
|
|
4682
4709
|
}
|
|
4683
|
-
getProviders(regionListUrl = this.
|
|
4710
|
+
getProviders(regionListUrl = this.unrealConfig?.regionsPingUrl || '') {
|
|
4684
4711
|
return this.httpClient.get(regionListUrl);
|
|
4685
4712
|
}
|
|
4686
4713
|
async startPinging(endpoints, defaultTimeout) {
|
|
4687
4714
|
const semaphore = new Semaphore(this.config.max_parallel_requests);
|
|
4715
|
+
// Shared "stop" signal. Aborted when EITHER:
|
|
4716
|
+
// - one region finishes successfully (winner found → cancel losers); OR
|
|
4717
|
+
// - the service's injector is destroyed (route exit → cancel everything).
|
|
4718
|
+
// Without this, losing regions keep pinging to their own `ping_timeout`,
|
|
4719
|
+
// generating up to `(endpoints.length - 1) × ping_count` wasted requests
|
|
4720
|
+
// every time `getFastest` runs, and in-flight fetches outlive the route.
|
|
4721
|
+
const winnerController = new AbortController();
|
|
4722
|
+
const destroySignal = this.destroyController.signal;
|
|
4723
|
+
let onDestroyAbort;
|
|
4724
|
+
if (destroySignal.aborted) {
|
|
4725
|
+
// Service already destroyed — bail every task synchronously.
|
|
4726
|
+
winnerController.abort(destroySignal.reason);
|
|
4727
|
+
}
|
|
4728
|
+
else {
|
|
4729
|
+
onDestroyAbort = () => winnerController.abort(destroySignal.reason);
|
|
4730
|
+
destroySignal.addEventListener('abort', onDestroyAbort, { once: true });
|
|
4731
|
+
}
|
|
4688
4732
|
const tasks = endpoints.map(async ({ region_code, url, timeout }) => {
|
|
4689
4733
|
await semaphore.acquire();
|
|
4690
4734
|
try {
|
|
4691
|
-
const result = await this.checkLatency(url, timeout ?? defaultTimeout);
|
|
4735
|
+
const result = await this.checkLatency(url, timeout ?? defaultTimeout, winnerController.signal);
|
|
4692
4736
|
if (result.length < 2)
|
|
4693
4737
|
throw new Error('insufficient pings');
|
|
4694
4738
|
return { region_code, result };
|
|
@@ -4703,11 +4747,26 @@ class RegionsPingService {
|
|
|
4703
4747
|
catch {
|
|
4704
4748
|
return null;
|
|
4705
4749
|
}
|
|
4750
|
+
finally {
|
|
4751
|
+
// Cancel losing in-flight pings. Safe to call on success (we already
|
|
4752
|
+
// captured the winner) AND on all-failed (no-op, all tasks settled).
|
|
4753
|
+
winnerController.abort();
|
|
4754
|
+
// Detach the destroy listener so it doesn't linger on the long-lived
|
|
4755
|
+
// destroyController.signal across multiple `getFastest` invocations.
|
|
4756
|
+
if (onDestroyAbort) {
|
|
4757
|
+
destroySignal.removeEventListener('abort', onDestroyAbort);
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4706
4760
|
}
|
|
4707
|
-
async checkLatency(endpoint, timeout) {
|
|
4761
|
+
async checkLatency(endpoint, timeout, abortSignal) {
|
|
4708
4762
|
const pingResults = [];
|
|
4709
4763
|
const sep = endpoint.includes('?') ? '&' : '?';
|
|
4710
4764
|
for (let i = 0; i < this.config.ping_count; i++) {
|
|
4765
|
+
// Bail out as soon as another region's task has won — no point
|
|
4766
|
+
// burning further `ping_timeout` budget here.
|
|
4767
|
+
if (abortSignal?.aborted) {
|
|
4768
|
+
break;
|
|
4769
|
+
}
|
|
4711
4770
|
const startTime = performance.now();
|
|
4712
4771
|
try {
|
|
4713
4772
|
await this.fetchWithTimeout(this.config.bust_cache
|
|
@@ -4721,7 +4780,7 @@ class RegionsPingService {
|
|
|
4721
4780
|
Expires: '0',
|
|
4722
4781
|
},
|
|
4723
4782
|
credentials: 'omit',
|
|
4724
|
-
}, timeout);
|
|
4783
|
+
}, timeout, abortSignal);
|
|
4725
4784
|
}
|
|
4726
4785
|
catch {
|
|
4727
4786
|
// intentional: finally pushes the elapsed time as the latency sample
|
|
@@ -4733,18 +4792,31 @@ class RegionsPingService {
|
|
|
4733
4792
|
}
|
|
4734
4793
|
return pingResults;
|
|
4735
4794
|
}
|
|
4736
|
-
async fetchWithTimeout(url, options = {}, timeout) {
|
|
4795
|
+
async fetchWithTimeout(url, options = {}, timeout, abortSignal) {
|
|
4737
4796
|
const controller = new AbortController();
|
|
4738
4797
|
options.signal = controller.signal;
|
|
4798
|
+
// Forward an external abort into our local controller so a winner-found signal cancels the in-flight `fetch` immediately. If the
|
|
4799
|
+
// external signal is already aborted, abort synchronously before
|
|
4800
|
+
// even starting the fetch.
|
|
4801
|
+
let onExternalAbort;
|
|
4802
|
+
if (abortSignal) {
|
|
4803
|
+
if (abortSignal.aborted) {
|
|
4804
|
+
controller.abort(abortSignal.reason);
|
|
4805
|
+
}
|
|
4806
|
+
else {
|
|
4807
|
+
onExternalAbort = () => controller.abort(abortSignal.reason);
|
|
4808
|
+
abortSignal.addEventListener('abort', onExternalAbort, { once: true });
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4739
4811
|
const timeoutId = setTimeout(() => controller.abort(new DOMException('Request timed out', 'TimeoutError')), timeout);
|
|
4740
4812
|
try {
|
|
4741
|
-
|
|
4742
|
-
clearTimeout(timeoutId);
|
|
4743
|
-
return response;
|
|
4813
|
+
return await fetch(url, options);
|
|
4744
4814
|
}
|
|
4745
|
-
|
|
4815
|
+
finally {
|
|
4746
4816
|
clearTimeout(timeoutId);
|
|
4747
|
-
|
|
4817
|
+
if (abortSignal && onExternalAbort) {
|
|
4818
|
+
abortSignal.removeEventListener('abort', onExternalAbort);
|
|
4819
|
+
}
|
|
4748
4820
|
}
|
|
4749
4821
|
}
|
|
4750
4822
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
@@ -4752,7 +4824,7 @@ class RegionsPingService {
|
|
|
4752
4824
|
}
|
|
4753
4825
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: RegionsPingService, decorators: [{
|
|
4754
4826
|
type: Injectable
|
|
4755
|
-
}] });
|
|
4827
|
+
}], ctorParameters: () => [] });
|
|
4756
4828
|
|
|
4757
4829
|
class StreamStatusTelemetryService extends SubService {
|
|
4758
4830
|
constructor() {
|
|
@@ -5711,7 +5783,7 @@ class AnalyticsService extends SubService {
|
|
|
5711
5783
|
});
|
|
5712
5784
|
}
|
|
5713
5785
|
monitorMouse() {
|
|
5714
|
-
const stop$ = this.#stopMouse
|
|
5786
|
+
const stop$ = merge(this.#stopMouse$, this.destroy$);
|
|
5715
5787
|
const element = document.getElementById('streamingVideo');
|
|
5716
5788
|
if (!element) {
|
|
5717
5789
|
return;
|
|
@@ -7047,5 +7119,5 @@ function provideUnrealModule() {
|
|
|
7047
7119
|
* Generated bundle index. Do not edit.
|
|
7048
7120
|
*/
|
|
7049
7121
|
|
|
7050
|
-
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, 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_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, 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, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
7122
|
+
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DESTROY_DISCONNECT_REASONS, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LoadingProgressService, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, 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, 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, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
7051
7123
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|