@3dsource/angular-unreal-module 0.0.113-dev.1 → 0.0.113-dev.2
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,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, ChangeDetectionStrategy, Component, Injectable, DestroyRef, effect, untracked, signal, computed, 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, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, from, fromEvent, throwError, defer, retry, tap as tap$1,
|
|
4
|
+
import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, skip, switchMap, concat, delay, debounceTime, timer, take, timeout, mergeMap, EMPTY, Subject, share, takeUntil as takeUntil$1, from, fromEvent, throwError, defer, retry, tap as tap$1, NEVER, startWith, throttleTime, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, pairwise, 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 { Actions, createEffect, ofType, provideEffects } from '@ngrx/effects';
|
|
7
7
|
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';
|
|
@@ -211,7 +211,7 @@ const WS_TIMEOUT = 2000;
|
|
|
211
211
|
const POLLING_TIME = 4000;
|
|
212
212
|
const WS_OPEN_STATE = 1;
|
|
213
213
|
const DEFAULT_AFK_TIMEOUT_PERIOD = 15;
|
|
214
|
-
const DEFAULT_AFK_TIMEOUT =
|
|
214
|
+
const DEFAULT_AFK_TIMEOUT = 120;
|
|
215
215
|
const DATA_CHANNEL_CONNECTION_TIMEOUT = 8000; //ms;
|
|
216
216
|
const SIGNALLING_PERCENT_VALUE = 56;
|
|
217
217
|
const SCREEN_LOCKER_CONTAINER_ID = '3dsource_start_screen';
|
|
@@ -232,7 +232,7 @@ const commandStarted = createAction(scoped$1 `command started`, props());
|
|
|
232
232
|
const commandCompleted = createAction(scoped$1 `command completed`, props());
|
|
233
233
|
|
|
234
234
|
const scoped = (templateString) => `[UNREAL] ${templateString[0]}`;
|
|
235
|
-
const trackMixpanelEvent = createAction(scoped `track
|
|
235
|
+
const trackMixpanelEvent = createAction(scoped `track Mixpanel event`, props());
|
|
236
236
|
const trackTelemetryData = createAction(scoped `track telemetry data`, props());
|
|
237
237
|
const setMaxFps = createAction(scoped `change fps`, props());
|
|
238
238
|
const destroyRemoteConnections = createAction(scoped `destroyRemoteConnections`, props());
|
|
@@ -278,9 +278,9 @@ const instanceReady = createAction(scoped `instance ready`);
|
|
|
278
278
|
const regionPingFetchStart = createAction(scoped `region ping fetch start`);
|
|
279
279
|
/**
|
|
280
280
|
* Dispatched once `RegionsPingService.getProviders()` returns the region
|
|
281
|
-
* list — i.e
|
|
281
|
+
* list — i.e., the fetch step is done and the actual per-region ping
|
|
282
282
|
* sweep is about to start. Splits the region-discovery waterfall into
|
|
283
|
-
* two legs: config fetch vs ping sweep. Observability-only.
|
|
283
|
+
* two legs: config fetch vs. ping sweep. Observability-only.
|
|
284
284
|
*/
|
|
285
285
|
const regionPingFetchEnd = createAction(scoped `region ping fetch end`);
|
|
286
286
|
/**
|
|
@@ -292,9 +292,9 @@ const regionPingFetchEnd = createAction(scoped `region ping fetch end`);
|
|
|
292
292
|
*/
|
|
293
293
|
const regionResolved = createAction(scoped `region resolved`, props());
|
|
294
294
|
/**
|
|
295
|
-
* Dispatched around the HTTP polling
|
|
295
|
+
* Dispatched around the HTTP polling roundup inside `getAwsInstance`
|
|
296
296
|
* (old orchestration flow). Only fires when polling is actually used
|
|
297
|
-
* (i.e
|
|
297
|
+
* (i.e., signaling URL is not a direct `ws://` / `wss://` endpoint).
|
|
298
298
|
* Observability-only — no state change.
|
|
299
299
|
*/
|
|
300
300
|
const pollingStarted = createAction(scoped `polling started`);
|
|
@@ -1131,7 +1131,6 @@ const commandsFeature = createFeature({
|
|
|
1131
1131
|
reducer: commandsReducer,
|
|
1132
1132
|
});
|
|
1133
1133
|
|
|
1134
|
-
const selectCommandsInProgress = createSelector(commandsFeature.selectCommandsInProgress, (commandsInProgress) => commandsInProgress?.length ?? 0);
|
|
1135
1134
|
const selectCommandProgress = createSelector(commandsFeature.selectTotalCommandsStarted, commandsFeature.selectTotalCommandsCompleted, (totalCommandsStarted, totalCommandsCompleted) => totalCommandsStarted ? totalCommandsCompleted / totalCommandsStarted : 0);
|
|
1136
1135
|
|
|
1137
1136
|
const selectFreezeFrameDataUrlFromVideo = createSelector(unrealFeature.selectFreezeFrameFromVideo, (freezeFrameFromVideo) => freezeFrameFromVideo?.dataUrl || null);
|
|
@@ -1147,7 +1146,6 @@ const selectIsExistMatchUrls = createSelector(unrealFeature.selectMatchUrls, (ur
|
|
|
1147
1146
|
const selectIsAutostart = createSelector(selectStreamConfig, (config) => config?.autoStart);
|
|
1148
1147
|
const selectShowReconnectPopup = createSelector(unrealFeature.selectCirrusConnected, unrealFeature.selectEstablishingConnection, (cirrusConnected, establishingConnection) => !cirrusConnected && !establishingConnection);
|
|
1149
1148
|
const selectShowLoader = createSelector(selectShowReconnectPopup, unrealFeature.selectViewportReady, (showPopup, viewportReady) => !showPopup && !viewportReady);
|
|
1150
|
-
const selectIsVideoPlayingAndDataChannelConnected = createSelector(unrealFeature.selectIsVideoPlaying, unrealFeature.selectDataChannelConnected, (isVideoPlaying, isDataChannelConnected) => isVideoPlaying && isDataChannelConnected);
|
|
1151
1149
|
const selectSignalingParameters = createSelector(unrealFeature.selectSsData, unrealFeature.selectAwsInstance, (data, instance) => {
|
|
1152
1150
|
const info = [];
|
|
1153
1151
|
if (data) {
|
|
@@ -1243,34 +1241,12 @@ class UnrealEffects {
|
|
|
1243
1241
|
onDataChannelClose: this.unrealInitialConfig?.reconnect?.onDataChannelClose ??
|
|
1244
1242
|
DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE,
|
|
1245
1243
|
};
|
|
1246
|
-
this.disconnectReasonHandling$ = createEffect(() => {
|
|
1247
|
-
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => {
|
|
1248
|
-
// Skip DataChannelClosed if peer reconnection will handle this case
|
|
1249
|
-
if (reason === DisconnectReason.DataChannelClosed &&
|
|
1250
|
-
this.canPeerReconnect()) {
|
|
1251
|
-
Logger.info('[Reconnect] Skipping destroy for DataChannelClosed - peer reconnection will handle');
|
|
1252
|
-
return false;
|
|
1253
|
-
}
|
|
1254
|
-
return DESTROY_DISCONNECT_REASONS.includes(reason);
|
|
1255
|
-
}), tap(({ reason, message }) => {
|
|
1256
|
-
Logger.log(`Disconnect Call=> reason:'${reason}', message:'${message}'`);
|
|
1257
|
-
}), map(({ reason }) => destroyRemoteConnections({ reason })));
|
|
1258
|
-
});
|
|
1259
1244
|
this.destroyConnections$ = createEffect(() => {
|
|
1260
1245
|
return this.actions$.pipe(ofType(destroyUnrealScene), tap(() => {
|
|
1261
1246
|
this.commandsSender.destroy();
|
|
1262
1247
|
this.videoService.setContainer(null);
|
|
1263
1248
|
}), map(() => destroyRemoteConnections({ reason: DisconnectReason.Destroy })));
|
|
1264
1249
|
});
|
|
1265
|
-
this.destroyRemoteConnections$ = createEffect(() => {
|
|
1266
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ reason }) => {
|
|
1267
|
-
this.signallingService.close({
|
|
1268
|
-
code: WSCloseCode_FORCE_CIRRUS_CLOSE,
|
|
1269
|
-
reason,
|
|
1270
|
-
});
|
|
1271
|
-
this.webRtcPlayerService.closePC();
|
|
1272
|
-
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map(() => dropConnection()));
|
|
1273
|
-
});
|
|
1274
1250
|
this.changeStreamResolution$ = createEffect(() => {
|
|
1275
1251
|
return this.actions$.pipe(ofType(changeStreamResolutionAction), filter((size) => !!(size?.width && size?.height)), concatLatestFrom(() => this.store.select(unrealFeature.selectStreamResolution)), filter(([newSizes, savedSizes]) => !(newSizes.width === savedSizes.width &&
|
|
1276
1252
|
newSizes.height === savedSizes.height)), map(([sizes]) => changeStreamResolutionSuccessAction({
|
|
@@ -1282,7 +1258,7 @@ class UnrealEffects {
|
|
|
1282
1258
|
return this.actions$.pipe(ofType(setConfig, setUserInactivityTimeout, resetWarnTimeout, resetConfig), map(() => resetAfk()));
|
|
1283
1259
|
});
|
|
1284
1260
|
this.abortEstablishingConnection$ = createEffect(() => {
|
|
1285
|
-
return
|
|
1261
|
+
return this.actions$.pipe(ofType(setCirrusDisconnected, dropConnection, destroyUnrealScene), map(() => abortEstablishingConnection()));
|
|
1286
1262
|
});
|
|
1287
1263
|
this.iframeMessaging$ = createEffect(() => {
|
|
1288
1264
|
const cirrusConnected$ = this.store
|
|
@@ -1347,8 +1323,35 @@ class UnrealEffects {
|
|
|
1347
1323
|
}));
|
|
1348
1324
|
}));
|
|
1349
1325
|
});
|
|
1326
|
+
/**
|
|
1327
|
+
* Effect: `forceViewportNotReady$`
|
|
1328
|
+
*
|
|
1329
|
+
* Ensures the viewport is explicitly marked as *not ready* to prevent
|
|
1330
|
+
* a temporary black screen flash when disconnecting.
|
|
1331
|
+
*
|
|
1332
|
+
* ### Why
|
|
1333
|
+
* - The video stream is disconnected immediately.
|
|
1334
|
+
* - The DataChannel closes slightly later.
|
|
1335
|
+
* - Closing the DataChannel resets the Unreal scene,
|
|
1336
|
+
* which also sets `viewPortReady` to `false`.
|
|
1337
|
+
*
|
|
1338
|
+
* Without this effect, the order of these events may cause
|
|
1339
|
+
* a visible flicker (video flashing to black).
|
|
1340
|
+
*/
|
|
1341
|
+
this.forceViewportNotReady$ = createEffect(() => {
|
|
1342
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), concatLatestFrom(() => this.store.select(unrealFeature.selectViewportReady)), filter(([, viewportReady]) => viewportReady), map(() => setViewportNotReady()));
|
|
1343
|
+
});
|
|
1350
1344
|
this.resetCirrusOnDestroy$ = createEffect(() => {
|
|
1351
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), map(() => setCirrusDisconnected()));
|
|
1345
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), concatLatestFrom(() => this.store.select(unrealFeature.selectCirrusConnected)), filter(([, cirrusConnected]) => cirrusConnected), map(() => setCirrusDisconnected()));
|
|
1346
|
+
});
|
|
1347
|
+
this.destroyRemoteConnections$ = createEffect(() => {
|
|
1348
|
+
return this.actions$.pipe(ofType(destroyRemoteConnections), tap(({ reason }) => {
|
|
1349
|
+
this.signallingService.close({
|
|
1350
|
+
code: WSCloseCode_FORCE_CIRRUS_CLOSE,
|
|
1351
|
+
reason,
|
|
1352
|
+
});
|
|
1353
|
+
this.webRtcPlayerService.closePC();
|
|
1354
|
+
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map(() => dropConnection()));
|
|
1352
1355
|
});
|
|
1353
1356
|
this.destroyConnectionAndRestart$ = createEffect(() => {
|
|
1354
1357
|
return this.actions$.pipe(ofType(setCirrusDisconnected), concatLatestFrom(() => this.store.select(unrealFeature.selectDisconnectReason)), filter(([, reason]) => reason === DisconnectReason.DataChannelTimeout), map(() => initSignalling()));
|
|
@@ -1390,24 +1393,6 @@ class UnrealEffects {
|
|
|
1390
1393
|
this.resetAfkOnVideoStart$ = createEffect(() => {
|
|
1391
1394
|
return this.actions$.pipe(ofType(videoStreamInitialized), filter(() => !!this.videoService.video?.paused), tap(() => Logger.info('Simulate Press Play')), map(() => resetAfk()));
|
|
1392
1395
|
});
|
|
1393
|
-
/**
|
|
1394
|
-
* Effect: `forceViewportNotReady$`
|
|
1395
|
-
*
|
|
1396
|
-
* Ensures the viewport is explicitly marked as *not ready* to prevent
|
|
1397
|
-
* a temporary black screen flash when disconnecting.
|
|
1398
|
-
*
|
|
1399
|
-
* ### Why
|
|
1400
|
-
* - The video stream is disconnected immediately.
|
|
1401
|
-
* - The DataChannel closes slightly later.
|
|
1402
|
-
* - Closing the DataChannel resets the Unreal scene,
|
|
1403
|
-
* which also sets `viewPortReady` to `false`.
|
|
1404
|
-
*
|
|
1405
|
-
* Without this effect, the order of these events may cause
|
|
1406
|
-
* a visible flicker (video flashing to black).
|
|
1407
|
-
*/
|
|
1408
|
-
this.forceViewportNotReady$ = createEffect(() => {
|
|
1409
|
-
return this.actions$.pipe(ofType(destroyRemoteConnections), map(() => setViewportNotReady()));
|
|
1410
|
-
});
|
|
1411
1396
|
/**
|
|
1412
1397
|
* Effect: `handleDataChannelClosed$`
|
|
1413
1398
|
*
|
|
@@ -1415,6 +1400,19 @@ class UnrealEffects {
|
|
|
1415
1400
|
* Only triggers if reconnection is enabled, onDataChannelClose is true,
|
|
1416
1401
|
* and the signaling WebSocket is still connected.
|
|
1417
1402
|
*/
|
|
1403
|
+
this.disconnectReasonHandling$ = createEffect(() => {
|
|
1404
|
+
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => {
|
|
1405
|
+
// Skip DataChannelClosed if peer reconnection will handle this case
|
|
1406
|
+
if (reason === DisconnectReason.DataChannelClosed &&
|
|
1407
|
+
this.canPeerReconnect()) {
|
|
1408
|
+
Logger.info('[Reconnect] Skipping destroy for DataChannelClosed - peer reconnection will handle');
|
|
1409
|
+
return false;
|
|
1410
|
+
}
|
|
1411
|
+
return DESTROY_DISCONNECT_REASONS.includes(reason);
|
|
1412
|
+
}), tap(({ reason, message }) => {
|
|
1413
|
+
Logger.log(`Disconnect Call=> reason:'${reason}', message:'${message}'`);
|
|
1414
|
+
}), map(({ reason }) => destroyRemoteConnections({ reason })));
|
|
1415
|
+
});
|
|
1418
1416
|
this.handleDataChannelClosed$ = createEffect(() => {
|
|
1419
1417
|
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.DataChannelClosed &&
|
|
1420
1418
|
this.canPeerReconnect()), tap(() => {
|
|
@@ -1425,6 +1423,11 @@ class UnrealEffects {
|
|
|
1425
1423
|
delay: 0,
|
|
1426
1424
|
})));
|
|
1427
1425
|
});
|
|
1426
|
+
this.webrtcErrorModalComponent$ = createEffect(() => {
|
|
1427
|
+
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.WebRTCError), debounceTime(400), switchMap(() => this.store
|
|
1428
|
+
.select(unrealFeature.selectDataChannelConnected)
|
|
1429
|
+
.pipe(take(1), filter(Falsy))), tap(() => this.dialog.open(WebrtcErrorModalComponent)));
|
|
1430
|
+
}, { dispatch: false });
|
|
1428
1431
|
/**
|
|
1429
1432
|
* Effect: `resetStateForPeerReconnect$`
|
|
1430
1433
|
*
|
|
@@ -1496,11 +1499,6 @@ class UnrealEffects {
|
|
|
1496
1499
|
});
|
|
1497
1500
|
}));
|
|
1498
1501
|
}, { dispatch: false });
|
|
1499
|
-
this.webrtcErrorModalComponent$ = createEffect(() => {
|
|
1500
|
-
return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.WebRTCError), debounceTime(400), switchMap(() => this.store
|
|
1501
|
-
.select(unrealFeature.selectDataChannelConnected)
|
|
1502
|
-
.pipe(take(1), filter(Falsy))), tap(() => this.dialog.open(WebrtcErrorModalComponent)));
|
|
1503
|
-
}, { dispatch: false });
|
|
1504
1502
|
this.sendStatisticsToUnreal$ = createEffect(() => {
|
|
1505
1503
|
return this.actions$.pipe(ofType(showUnrealErrorMessage), filter(({ code }) => code === 404), withLatestFrom(this.store.select(unrealFeature.selectAwsInstance), this.store.select(unrealFeature.selectStreamClientCompanyId), this.store.select(unrealFeature.selectStreamViewId)), switchMap(([action, instance, companyId, viewId]) => this.http
|
|
1506
1504
|
.post(this.unrealInitialConfig?.customErrorsEndpoint || '', {
|
|
@@ -1590,14 +1588,12 @@ class AFKService extends SubService {
|
|
|
1590
1588
|
this.active = false; // Whether the AFK system is currently looking for inactivity.
|
|
1591
1589
|
this.countdown = 0;
|
|
1592
1590
|
this.warnTimeout = this.store.selectSignal(selectWarnTimeout);
|
|
1591
|
+
this.isAfkTimerVisible = this.store.selectSignal(unrealFeature.selectIsAfkTimerVisible);
|
|
1593
1592
|
this.isViewportReady = this.store.selectSignal(unrealFeature.selectViewportReady);
|
|
1594
1593
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
1595
1594
|
/** Ticks every time warnTimeout changes or resetAfk action fires */
|
|
1596
1595
|
this.afkResetTrigger = toSignal(merge(this.store.select(selectWarnTimeout), this.actions$.pipe(ofType(resetAfk))));
|
|
1597
|
-
/** Ticks when cirrus disconnects */
|
|
1598
1596
|
this.disconnected = toSignal(this.disconnect$);
|
|
1599
|
-
/** Ticks when resetUserInactivityTimeout action fires */
|
|
1600
|
-
this.resetUserInactivityTrigger = toSignal(this.actions$.pipe(ofType(resetUserInactivityTimeout)));
|
|
1601
1597
|
this.destroyRef.onDestroy(() => this.clearTimers());
|
|
1602
1598
|
if (this.unrealConfig?.playwright) {
|
|
1603
1599
|
return;
|
|
@@ -1618,20 +1614,20 @@ class AFKService extends SubService {
|
|
|
1618
1614
|
untracked(() => this.startAfkWarningTimer());
|
|
1619
1615
|
}
|
|
1620
1616
|
});
|
|
1621
|
-
// Stop on disconnect
|
|
1622
1617
|
effect(() => {
|
|
1623
1618
|
const disconnected = this.disconnected();
|
|
1624
1619
|
if (disconnected !== undefined) {
|
|
1625
1620
|
untracked(() => this.stop());
|
|
1626
1621
|
}
|
|
1627
1622
|
});
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
this.
|
|
1631
|
-
untracked(() => this.reset());
|
|
1632
|
-
});
|
|
1623
|
+
this.actions$
|
|
1624
|
+
.pipe(ofType(resetUserInactivityTimeout), takeUntil$1(this.destroy$))
|
|
1625
|
+
.subscribe(() => this.reset());
|
|
1633
1626
|
}
|
|
1634
1627
|
hideOverlay() {
|
|
1628
|
+
if (!this.isAfkTimerVisible()) {
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1635
1631
|
this.store.dispatch(setAfkTimerHide());
|
|
1636
1632
|
}
|
|
1637
1633
|
/**
|
|
@@ -1842,6 +1838,7 @@ const BITRATE_MONITOR = new DataFlowMonitor(FilterModel.yellowFlag, FilterModel.
|
|
|
1842
1838
|
class VideoService extends SubService {
|
|
1843
1839
|
constructor() {
|
|
1844
1840
|
super(...arguments);
|
|
1841
|
+
this.isVideoPlaying = this.store.selectSignal(unrealFeature.selectIsVideoPlaying);
|
|
1845
1842
|
this.video = null;
|
|
1846
1843
|
this.audio = null;
|
|
1847
1844
|
this.container = null;
|
|
@@ -2065,6 +2062,7 @@ class VideoService extends SubService {
|
|
|
2065
2062
|
if (!this.video) {
|
|
2066
2063
|
return;
|
|
2067
2064
|
}
|
|
2065
|
+
this.changeStatusMainVideoOnScene(false);
|
|
2068
2066
|
this.video = null;
|
|
2069
2067
|
this.audio = null;
|
|
2070
2068
|
this.videoTrack$.next(null);
|
|
@@ -2076,7 +2074,12 @@ class VideoService extends SubService {
|
|
|
2076
2074
|
while (this.container?.firstChild) {
|
|
2077
2075
|
this.container.removeChild(this.container.firstChild);
|
|
2078
2076
|
}
|
|
2079
|
-
|
|
2077
|
+
}
|
|
2078
|
+
changeStatusMainVideoOnScene(isVideoPlaying) {
|
|
2079
|
+
if (this.isVideoPlaying() === isVideoPlaying) {
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
this.store.dispatch(changeStatusMainVideoOnScene({ isVideoPlaying }));
|
|
2080
2083
|
}
|
|
2081
2084
|
createWebRtcVideo() {
|
|
2082
2085
|
dispatchResize();
|
|
@@ -2089,14 +2092,10 @@ class VideoService extends SubService {
|
|
|
2089
2092
|
video.disablePictureInPicture = true;
|
|
2090
2093
|
this.video = video;
|
|
2091
2094
|
this.container?.appendChild(this.video);
|
|
2092
|
-
this.video.addEventListener('play', () => this.
|
|
2095
|
+
this.video.addEventListener('play', () => this.changeStatusMainVideoOnScene(true));
|
|
2093
2096
|
fromEvent(this.video, 'loadedmetadata')
|
|
2094
|
-
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'))
|
|
2095
|
-
.subscribe(() =>
|
|
2096
|
-
if (this.video) {
|
|
2097
|
-
this.store.dispatch(videoStreamInitialized());
|
|
2098
|
-
}
|
|
2099
|
-
});
|
|
2097
|
+
.pipe(take(1), filter((data) => this.video === data.target), tapLog('VideoService loadedmetadata:'), filter(() => !!this.video))
|
|
2098
|
+
.subscribe(() => this.store.dispatch(videoStreamInitialized()));
|
|
2100
2099
|
}
|
|
2101
2100
|
createWebRtcAudio() {
|
|
2102
2101
|
const audio = new Audio();
|
|
@@ -3898,6 +3897,10 @@ class DevModeService {
|
|
|
3898
3897
|
get isDevMode() {
|
|
3899
3898
|
return Truthy(localStorage.getItem('devMode'));
|
|
3900
3899
|
}
|
|
3900
|
+
/**
|
|
3901
|
+
* Called from the browser console to toggle dev mode at runtime.
|
|
3902
|
+
* @preserve
|
|
3903
|
+
*/
|
|
3901
3904
|
setDevMode(value) {
|
|
3902
3905
|
if (value) {
|
|
3903
3906
|
localStorage.setItem('devMode', value.toString());
|
|
@@ -4727,7 +4730,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4727
4730
|
/**
|
|
4728
4731
|
* Whether a session has already started during the current page lifetime.
|
|
4729
4732
|
* Used to distinguish a fresh page load (bootstrap measurement is
|
|
4730
|
-
* meaningful) from a
|
|
4733
|
+
* meaningful) from a Résumé/restart (the document is already loaded,
|
|
4731
4734
|
* so "bootstrap" is a no-op). Only the first session gets the bootstrap
|
|
4732
4735
|
* phase anchored at navigation start (perf.now() === 0).
|
|
4733
4736
|
*/
|
|
@@ -4736,7 +4739,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4736
4739
|
* Rolling snapshot of the currently selected WebRTC ICE candidate pair
|
|
4737
4740
|
* (local/remote type + url). Scanned over `videoStats$` so the stream
|
|
4738
4741
|
* *keeps* the first non-null pair even if later stats emit null — WebRTC
|
|
4739
|
-
* doesn't
|
|
4742
|
+
* doesn't unselect a pair mid-session, but the source is nullable. The
|
|
4740
4743
|
* `startWith(null)` seed makes `withLatestFrom` on this stream safe even
|
|
4741
4744
|
* before the first video stat arrives (common at data-channel-open).
|
|
4742
4745
|
*/
|
|
@@ -4745,7 +4748,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4745
4748
|
* Correlation-matched Observable: emits once when the first `loopBack`
|
|
4746
4749
|
* command's corresponding `command completed` action fires. Built from
|
|
4747
4750
|
* the two related actions — the command-started captures the correlation
|
|
4748
|
-
* id
|
|
4751
|
+
* id; the command-completed with that id emits.
|
|
4749
4752
|
*/
|
|
4750
4753
|
this.loopbackReceived$ = this.actions$.pipe(ofType(commandStarted), filter$1(({ command }) => command === 'loopBack'), take(1), switchMap(({ id }) => this.actions$.pipe(ofType(commandCompleted), filter$1((b) => b.id === id), take(1))));
|
|
4751
4754
|
// Overlay-referenced milestones — hoisted as fields so both
|
|
@@ -4776,7 +4779,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4776
4779
|
/**
|
|
4777
4780
|
* Unified phase catalog — the single source of truth for everything
|
|
4778
4781
|
* this service tracks after `session_started`. Each entry is a phase;
|
|
4779
|
-
* its `milestones` array is ordered chronologically and the *last*
|
|
4782
|
+
* its `milestones` array is ordered chronologically, and the *last*
|
|
4780
4783
|
* entry is the phase closer. When that closer's observable fires, the
|
|
4781
4784
|
* service emits one generic `stream.phase_ended` wire event with every
|
|
4782
4785
|
* display field the viewer needs.
|
|
@@ -4882,7 +4885,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4882
4885
|
// time) into phase-3 params:
|
|
4883
4886
|
// - `iceCandidateCount` (total ICE candidates from server — too
|
|
4884
4887
|
// few hints at a restrictive network)
|
|
4885
|
-
// - `handshakeFlow` (server-offer vs client-offer; we only use
|
|
4888
|
+
// - `handshakeFlow` (server-offer vs. client-offer; we only use
|
|
4886
4889
|
// server-offer today, but the detector stays in place)
|
|
4887
4890
|
// - `instanceType` / `branchInfo` / version / path / app /
|
|
4888
4891
|
// `connectionId` (streamer identity, from `ssInfo` message)
|
|
@@ -4918,7 +4921,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4918
4921
|
this.loopbackReceivedEntry,
|
|
4919
4922
|
// CLOSER — flattens the selected ICE candidate pair into two
|
|
4920
4923
|
// string params (local / remote), and snapshots the running
|
|
4921
|
-
// command counter so phase 5 shows how many commands the client
|
|
4924
|
+
// command counter, so phase 5 shows how many commands the client
|
|
4922
4925
|
// fired at Unreal before the viewport was ready.
|
|
4923
4926
|
{
|
|
4924
4927
|
label: 'Viewport ready',
|
|
@@ -4962,7 +4965,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4962
4965
|
.select(unrealFeature.selectCirrusConnected)
|
|
4963
4966
|
.pipe(pairwise(), filter$1(([prev, curr]) => prev && !curr))
|
|
4964
4967
|
.subscribe(() => this.endSession('disconnected'));
|
|
4965
|
-
// Close the session when the tab is hidden / being evicted from
|
|
4968
|
+
// Close the session when the tab is hidden / being evicted from cache.
|
|
4966
4969
|
// `pagehide` fires in more cases than `beforeunload` (covers iOS Safari, bfcache).
|
|
4967
4970
|
fromEvent(window, 'pagehide').subscribe(() => this.endSession('pagehide'));
|
|
4968
4971
|
}
|
|
@@ -4977,7 +4980,7 @@ class StreamStatusTelemetryService2 {
|
|
|
4977
4980
|
// - First session on this page → anchor at navigation start (0).
|
|
4978
4981
|
// `performance.now()` is already "ms since navigation start", so
|
|
4979
4982
|
// `session_started.duration` becomes the full page-load + idle-time
|
|
4980
|
-
// leading up to the first stream start (i.e
|
|
4983
|
+
// leading up to the first stream start (i.e., the real bootstrap).
|
|
4981
4984
|
// - Resume/restart sessions → anchor at the session's own start.
|
|
4982
4985
|
// The document is already loaded — a "bootstrap" phase would either
|
|
4983
4986
|
// balloon (to include all prior idle time) or be misleading, so we
|
|
@@ -5027,7 +5030,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5027
5030
|
this.session.handshakeFlow = 'client-offer';
|
|
5028
5031
|
}));
|
|
5029
5032
|
// Unreal-command counter — increments on every `commandStarted`
|
|
5030
|
-
// action.
|
|
5033
|
+
// action. Snapshot ted into the `viewport_ready` closer payload as
|
|
5031
5034
|
// phase-5's `commandCount`.
|
|
5032
5035
|
sub.add(this.actions$.pipe(ofType(commandStarted)).subscribe(() => {
|
|
5033
5036
|
if (this.session)
|
|
@@ -5128,7 +5131,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5128
5131
|
* Emit the unified `stream.phase_ended` wire event for a closer. Packs
|
|
5129
5132
|
* every display field the viewer consumes — phaseNo (derived from the
|
|
5130
5133
|
* catalog index), label, color, milestones, params, overlays. Boundary
|
|
5131
|
-
* labels aren't on the wire — the viewer derives them from
|
|
5134
|
+
* labels aren't on the wire — the viewer derives them from neighbor
|
|
5132
5135
|
* phases' `label` fields.
|
|
5133
5136
|
*/
|
|
5134
5137
|
emitPhaseEnded(bucket, bucketIdx, extrasValue) {
|
|
@@ -5199,7 +5202,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5199
5202
|
qpP95: qp.p95,
|
|
5200
5203
|
});
|
|
5201
5204
|
}
|
|
5202
|
-
// FPS low vs current target.
|
|
5205
|
+
// FPS low vs. current target.
|
|
5203
5206
|
const ref = s.maxFpsTarget > 0 ? s.maxFpsTarget : UNLIMITED_REFERENCE_FPS;
|
|
5204
5207
|
const fpsLowNow = fps.p50 < ref * FPS_LOW_RATIO;
|
|
5205
5208
|
if (fpsLowNow && !s.fpsLow) {
|
|
@@ -5329,7 +5332,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5329
5332
|
// Nearest-rank method: p-th percentile ≡ sorted[ceil(p/100 * n) - 1].
|
|
5330
5333
|
// Using `floor` would make p95 collapse to `max` for n=20
|
|
5331
5334
|
// (floor(0.95*20) = 19 = max index), hiding the max line behind p95
|
|
5332
|
-
// in charts. `ceil - 1` keeps them distinct:
|
|
5335
|
+
// in charts. `ceil - 1` keeps them distinct: p. 95 = sorted[18], max = sorted[19].
|
|
5333
5336
|
const pick = (p) => {
|
|
5334
5337
|
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
5335
5338
|
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
|
|
@@ -5342,7 +5345,7 @@ class StreamStatusTelemetryService2 {
|
|
|
5342
5345
|
};
|
|
5343
5346
|
}
|
|
5344
5347
|
track(event, data) {
|
|
5345
|
-
// `userId` is attached to every event so the server can bucket files
|
|
5348
|
+
// `userId` is attached to every event, so the server can bucket files
|
|
5346
5349
|
// under `data/<userId>/<sessionId>.jsonl` without maintaining a
|
|
5347
5350
|
// session→user index.
|
|
5348
5351
|
this.store.dispatch(trackTelemetryData({
|
|
@@ -5362,8 +5365,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5362
5365
|
/** Compact one side of an ICE candidate pair to `"type (url)"` or just
|
|
5363
5366
|
* `type` (host candidates have no URL). */
|
|
5364
5367
|
function fmtCandidate(c) {
|
|
5365
|
-
if (!c)
|
|
5368
|
+
if (!c) {
|
|
5366
5369
|
return null;
|
|
5370
|
+
}
|
|
5367
5371
|
return c.url ? `${c.type} (${c.url})` : c.type;
|
|
5368
5372
|
}
|
|
5369
5373
|
/**
|
|
@@ -5540,7 +5544,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5540
5544
|
* ## How it works
|
|
5541
5545
|
*
|
|
5542
5546
|
* The service subscribes to `VideoService.videoStats$` (emitted every 250 ms)
|
|
5543
|
-
* and collects samples of FPS, bitrate, and QP (
|
|
5547
|
+
* and collects samples of FPS, bitrate, and QP (Quantization Parameter).
|
|
5544
5548
|
* Every {@link SAMPLE_WINDOW} ticks (~2 s) it averages the collected samples
|
|
5545
5549
|
* and feeds them into a set of **fuzzy-logic membership functions** that
|
|
5546
5550
|
* classify each metric into overlapping quality bands.
|
|
@@ -5549,28 +5553,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5549
5553
|
*
|
|
5550
5554
|
* **1. FPS ratio** = `actualFPS / currentTargetFPS` (0..1+)
|
|
5551
5555
|
*
|
|
5552
|
-
* | Band
|
|
5556
|
+
* | Band | Range | Meaning |
|
|
5553
5557
|
* |--------|---------------------|-----------------------------------------------|
|
|
5554
|
-
* | low
|
|
5555
|
-
* |
|
|
5556
|
-
* | high
|
|
5558
|
+
* | low | ratio < 0.7 → 1.0, linear ramp 0.7..0.9 → 0 | Stream cannot keep up with the target |
|
|
5559
|
+
* | okays | triangle 0.8..1.0, peak at 0.95 | Stream roughly matches the target |
|
|
5560
|
+
* | high | ratio 0.9..1.0 → ramp, >1.0 → 1.0 | Stream meets or exceeds the target |
|
|
5557
5561
|
*
|
|
5558
5562
|
* **2. Bitrate load** = `currentBitrate / MAX_BITRATE` (0..1+)
|
|
5559
5563
|
*
|
|
5560
5564
|
* `MAX_BITRATE` = 20 Mbit/s — the reference ceiling for the encoder.
|
|
5561
5565
|
*
|
|
5562
|
-
* | Band
|
|
5566
|
+
* | Band | Range | Meaning |
|
|
5563
5567
|
* |--------|-------------------------------------------------|-----------------------------------------------|
|
|
5564
|
-
* | high
|
|
5565
|
-
* | not high | load < 0.5 → 0.0
|
|
5568
|
+
* | high | load 0.5..0.8 → ramp, >0.8 → 1.0 | Encoder is using most of the available bandwidth |
|
|
5569
|
+
* | not high | load < 0.5 → 0.0 | Bandwidth headroom is available |
|
|
5566
5570
|
*
|
|
5567
5571
|
* **3. Quality** — mapped from QP via `mapQpToQuality()` (see {@link Quality}):
|
|
5568
5572
|
*
|
|
5569
|
-
* | Quality
|
|
5573
|
+
* | Quality | QP range | Meaning |
|
|
5570
5574
|
* |----------|-----------|-------------------------------------------------|
|
|
5571
|
-
* | `lime`
|
|
5572
|
-
* | `orange` | QP 27..35 | Fair — encoder is under moderate pressure
|
|
5573
|
-
* | `red`
|
|
5575
|
+
* | `lime` | QP <= 26 | Good — encoder has enough headroom |
|
|
5576
|
+
* | `orange` | QP 27..35 | Fair — encoder is under moderate pressure |
|
|
5577
|
+
* | `red` | QP > 35 | Poor — heavy compression, visible artifacts |
|
|
5574
5578
|
*
|
|
5575
5579
|
* ### Decision rules (evaluated in priority order, first match wins)
|
|
5576
5580
|
*
|
|
@@ -5584,17 +5588,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
5584
5588
|
*
|
|
5585
5589
|
* ## Cooldown-based throttling
|
|
5586
5590
|
*
|
|
5587
|
-
* The service continuously evaluates fuzzy rules every sample window.
|
|
5591
|
+
* The service continuously evaluates fuzzy rules in every sample window.
|
|
5588
5592
|
* After each FPS step change it enforces a cooldown before the next
|
|
5589
5593
|
* change can be applied. The cooldown duration depends on the
|
|
5590
5594
|
* (previous decision → next decision) pair:
|
|
5591
5595
|
*
|
|
5592
|
-
* | Previous
|
|
5596
|
+
* | Previous | Next | Cooldown |
|
|
5593
5597
|
* |-----------|-----------|-----------------------------------------------------|
|
|
5594
|
-
* | decrease
|
|
5595
|
-
* | decrease
|
|
5596
|
-
* | increase
|
|
5597
|
-
* | increase
|
|
5598
|
+
* | decrease | decrease | {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS} (2 s) |
|
|
5599
|
+
* | decrease | increase | {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} (10 s) |
|
|
5600
|
+
* | increase | decrease | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
|
|
5601
|
+
* | increase | increase | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
|
|
5598
5602
|
*
|
|
5599
5603
|
* This means the system reacts quickly after an increase (2 s in any
|
|
5600
5604
|
* direction) but is cautious about stepping back up after a decrease
|
|
@@ -5611,7 +5615,7 @@ class FpsMonitorService {
|
|
|
5611
5615
|
this.videoService = inject(VideoService);
|
|
5612
5616
|
this.store = inject(Store);
|
|
5613
5617
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
5614
|
-
/** Discrete FPS targets the controller can switch between (ascending order). Last 0 is unlimited framerate indicator*/
|
|
5618
|
+
/** Discrete FPS targets the controller can switch between (ascending order). Last 0 is an unlimited framerate indicator*/
|
|
5615
5619
|
this.FPS_STEPS = [30, 40, 50, 0];
|
|
5616
5620
|
/**
|
|
5617
5621
|
* Reference FPS used to compute the FPS ratio when `currentFpsTarget` is 0
|
|
@@ -5632,7 +5636,7 @@ class FpsMonitorService {
|
|
|
5632
5636
|
this.COOLDOWN_DECREASE_AFTER_DECREASE_MS = 2_000;
|
|
5633
5637
|
/**
|
|
5634
5638
|
* Cooldown (ms) for an increase following a previous decrease.
|
|
5635
|
-
* The stream needs more time to
|
|
5639
|
+
* The stream needs more time to stabilize before stepping back up.
|
|
5636
5640
|
*/
|
|
5637
5641
|
this.COOLDOWN_INCREASE_AFTER_DECREASE_MS = 10_000;
|
|
5638
5642
|
/**
|
|
@@ -5711,9 +5715,9 @@ class FpsMonitorService {
|
|
|
5711
5715
|
* Rules are evaluated in priority order — first match wins.
|
|
5712
5716
|
*
|
|
5713
5717
|
* Inputs:
|
|
5714
|
-
* - `fpsRatio`
|
|
5715
|
-
* - `bitrateLoad` = avgBitrate / 20 Mbit/s
|
|
5716
|
-
* - `quality`
|
|
5718
|
+
* - `fpsRatio` = avgFPS / currentTargetFPS (e.g., 45/60 = 0.75)
|
|
5719
|
+
* - `bitrateLoad` = avgBitrate / 20 Mbit/s (e.g., 12M/20M = 0.6)
|
|
5720
|
+
* - `quality` = mapQpToQuality(avgQP) → 'lime' | 'orange' | 'red'
|
|
5717
5721
|
*/
|
|
5718
5722
|
evaluateDecision(avg, currentTarget) {
|
|
5719
5723
|
const referenceTarget = currentTarget > 0 ? currentTarget : this.UNLIMITED_REFERENCE_FPS;
|
|
@@ -5730,17 +5734,17 @@ class FpsMonitorService {
|
|
|
5730
5734
|
return 'decrease';
|
|
5731
5735
|
}
|
|
5732
5736
|
// Rule 2: FPS looks acceptable numerically, but encoder quality is 'red'
|
|
5733
|
-
// (QP > 35 — heavy compression
|
|
5737
|
+
// (QP > 35 — heavy compression artifacts). Reducing target FPS frees
|
|
5734
5738
|
// encoder headroom and improves visual quality.
|
|
5735
5739
|
if (fpsOk > 0.4 && quality === 'red') {
|
|
5736
5740
|
return 'decrease';
|
|
5737
5741
|
}
|
|
5738
|
-
// Rule 3: FPS is ok, but bitrate is above ~50-80% of 20 Mbit/s ceiling
|
|
5742
|
+
// Rule 3: FPS is ok, but the bitrate is above ~50-80% of 20 Mbit/s ceiling
|
|
5739
5743
|
// and quality is 'orange' (QP 27..35). Near capacity — don't push higher.
|
|
5740
5744
|
if (fpsOk > 0.4 && bitrateHigh > 0.5 && quality === 'orange') {
|
|
5741
5745
|
return 'hold';
|
|
5742
5746
|
}
|
|
5743
|
-
// Rule 4: FPS meets/exceeds target (ratio >= ~0.95), quality is 'lime'
|
|
5747
|
+
// Rule 4: FPS meets/exceeds the target (ratio >= ~0.95), quality is 'lime'
|
|
5744
5748
|
// (QP <= 26), and bitrate is below 50% of ceiling — safe to step up.
|
|
5745
5749
|
// Note: the last FPS_STEPS value (0) means "no limit", so compare by
|
|
5746
5750
|
// position in FPS_STEPS rather than numerically.
|
|
@@ -5763,11 +5767,11 @@ class FpsMonitorService {
|
|
|
5763
5767
|
// ---------------------------------------------------------------------------
|
|
5764
5768
|
/**
|
|
5765
5769
|
* FPS ratio membership "low".
|
|
5766
|
-
* Input: ratio = actualFPS / targetFPS (e.g
|
|
5770
|
+
* Input: a ratio = actualFPS / targetFPS (e.g., 42/60 = 0.70).
|
|
5767
5771
|
*
|
|
5768
|
-
* ratio < 0.7
|
|
5769
|
-
* ratio 0.7..0.9 → linear ramp down (e.g
|
|
5770
|
-
* ratio > 0.9
|
|
5772
|
+
* ratio < 0.7 → 1.0 (clearly failing, e.g., 40/60)
|
|
5773
|
+
* ratio 0.7..0.9 → linear ramp down (e.g., 0.8 → 0.5)
|
|
5774
|
+
* a ratio > 0.9 → 0.0 (keeping up)
|
|
5771
5775
|
*/
|
|
5772
5776
|
membershipFpsLow(ratio) {
|
|
5773
5777
|
if (ratio < 0.7)
|
|
@@ -5780,10 +5784,10 @@ class FpsMonitorService {
|
|
|
5780
5784
|
* FPS ratio membership "ok" — triangle shape.
|
|
5781
5785
|
* Input: ratio = actualFPS / targetFPS.
|
|
5782
5786
|
*
|
|
5783
|
-
* ratio < 0.8
|
|
5784
|
-
* ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g
|
|
5787
|
+
* ratio < 0.8 → 0.0
|
|
5788
|
+
* ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g., 57/60)
|
|
5785
5789
|
* ratio 0.95..1.0 → ramp down
|
|
5786
|
-
* ratio > 1.0
|
|
5790
|
+
* a ratio > 1.0 → 0.0
|
|
5787
5791
|
*/
|
|
5788
5792
|
membershipFpsOk(ratio) {
|
|
5789
5793
|
if (ratio < 0.8)
|
|
@@ -5798,9 +5802,9 @@ class FpsMonitorService {
|
|
|
5798
5802
|
* FPS ratio membership "high".
|
|
5799
5803
|
* Input: ratio = actualFPS / targetFPS.
|
|
5800
5804
|
*
|
|
5801
|
-
* ratio < 0.9
|
|
5802
|
-
* ratio 0.9..1.0 → linear ramp up (e.g
|
|
5803
|
-
* ratio >= 1.0 → 1.0
|
|
5805
|
+
* ratio < 0.9 → 0.0
|
|
5806
|
+
* ratio 0.9..1.0 → linear ramp up (e.g., 0.95 → 0.5)
|
|
5807
|
+
* a ratio >= 1.0 → 1.0 (meeting or exceeding target, e.g., 60/60)
|
|
5804
5808
|
*/
|
|
5805
5809
|
membershipFpsHigh(ratio) {
|
|
5806
5810
|
if (ratio < 0.9)
|
|
@@ -5811,11 +5815,11 @@ class FpsMonitorService {
|
|
|
5811
5815
|
}
|
|
5812
5816
|
/**
|
|
5813
5817
|
* Bitrate load membership "high".
|
|
5814
|
-
* Input: load = currentBitrate / MAX_BITRATE (20 Mbit/s).
|
|
5818
|
+
* Input: a load = currentBitrate / MAX_BITRATE (20 Mbit/s).
|
|
5815
5819
|
*
|
|
5816
|
-
* load < 0.5
|
|
5817
|
-
* load 0.5..0.8 → linear ramp (e.g
|
|
5818
|
-
* load > 0.8
|
|
5820
|
+
* load < 0.5 → 0.0 (below 10 Mbit/s — plenty of headroom)
|
|
5821
|
+
* load 0.5..0.8 → linear ramp (e.g., 13M/20M = 0.65 → 0.5)
|
|
5822
|
+
* load > 0.8 → 1.0 (above 16 Mbit/s — near capacity)
|
|
5819
5823
|
*/
|
|
5820
5824
|
membershipBitrateHigh(load) {
|
|
5821
5825
|
if (load < 0.5)
|
|
@@ -5847,14 +5851,14 @@ class FpsMonitorService {
|
|
|
5847
5851
|
Logger.info(`[FpsMonitor] Decision: ${decision} → FPS: ${newTarget}, Quality: ${quality}`);
|
|
5848
5852
|
if (newTarget !== this.currentFpsTarget) {
|
|
5849
5853
|
this.currentFpsTarget = newTarget;
|
|
5850
|
-
this.store.dispatch(setMaxFps({ maxFps: newTarget }));
|
|
5854
|
+
// this.store.dispatch(setMaxFps({ maxFps: newTarget }));
|
|
5851
5855
|
}
|
|
5852
5856
|
this.lastDecisionTime = now;
|
|
5853
5857
|
this.samples = [];
|
|
5854
5858
|
}
|
|
5855
5859
|
/**
|
|
5856
|
-
* Get the next FPS target based on current target and decision.
|
|
5857
|
-
* Clamps to valid FPS_STEPS range.
|
|
5860
|
+
* Get the next FPS target based on the current target and decision.
|
|
5861
|
+
* Clamps to the valid FPS_STEPS range.
|
|
5858
5862
|
*/
|
|
5859
5863
|
getNextFpsTarget(currentTarget, decision) {
|
|
5860
5864
|
const currentIndex = this.FPS_STEPS.indexOf(currentTarget);
|
|
@@ -6779,5 +6783,5 @@ function provideUnrealModule() {
|
|
|
6779
6783
|
* Generated bundle index. Do not edit.
|
|
6780
6784
|
*/
|
|
6781
6785
|
|
|
6782
|
-
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, 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, changeStreamResolutionSuccessAction, commandCompleted, commandStarted, commandsFeature, commandsReducer, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, 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,
|
|
6786
|
+
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, 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, changeStreamResolutionSuccessAction, commandCompleted, commandStarted, commandsFeature, commandsReducer, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, 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, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, 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, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
6783
6787
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|