@3dsource/angular-unreal-module 0.0.177 → 0.0.182

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@3dsource/angular-unreal-module",
3
- "version": "0.0.177",
3
+ "version": "0.0.182",
4
4
  "description": "Angular integration for Unreal Engine Pixel Streaming",
5
5
  "keywords": [
6
6
  "3dsource",
@@ -16,11 +16,11 @@
16
16
  "author": "3dsource",
17
17
  "sideEffects": false,
18
18
  "dependencies": {
19
- "tslib": "^2.8.1"
19
+ "tslib": "^2.8.1",
20
+ "uuid": "^14.0.2"
20
21
  },
21
22
  "peerDependencies": {
22
- "@3dsource/types-unreal": ">=0.0.7",
23
- "@3dsource/utils": ">=1.0.21",
23
+ "@3dsource/types-unreal": ">=0.0.14 <0.1.0",
24
24
  "@angular/cdk": ">=19.0.0 <23.0.0",
25
25
  "@angular/common": ">=19.0.0 <23.0.0",
26
26
  "@angular/core": ">=19.0.0 <23.0.0",
@@ -9,7 +9,6 @@ import * as rxjs from 'rxjs';
9
9
  import { Subject, Observable, Subscription, OperatorFunction } from 'rxjs';
10
10
  import * as _ngrx_effects from '@ngrx/effects';
11
11
  import { Actions } from '@ngrx/effects';
12
- import { IKeyCode, ImageOutputValues, Area } from '@3dsource/utils';
13
12
  import { SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl } from '@angular/platform-browser';
14
13
  import { MetaBoxCommandPacket, MetaBoxCommandList, MetaBoxCallbackList, FOnSceneStateCallback, FOnFocusObjectCallback, FCameraChangedCallback, FOnObjectTransformChangedCallback, FOnChangeSequenceChangedCallback, FOnFinishSequenceChangedCallback } from '@3dsource/types-unreal';
15
14
 
@@ -260,6 +259,167 @@ declare const MouseButtonsMask: {
260
259
  FifthButton: number;
261
260
  };
262
261
 
262
+ /**
263
+ * `KeyboardEvent.code` → the Windows virtual-key code Unreal expects.
264
+ *
265
+ * Only keys whose `event.keyCode` cannot be trusted belong here. The browser
266
+ * already resolves `keyCode` correctly for everything else, so the table exists
267
+ * for two cases: browsers that disagree on a value (Gecko reports the
268
+ * punctuation keys by ASCII — `;` as 59, `=` as 61, `-` as 173) and keys Unreal
269
+ * numbers differently from the DOM (the right-hand modifiers, the Cmd/Win keys).
270
+ *
271
+ * The numpad digits, `NumpadDecimal` and `NumpadEnter` are the exception to the
272
+ * rule above, and the reason it is worded as a rule. A numpad key reports the
273
+ * same `code` in both NumLock states but a different virtual key — `Numpad4` is
274
+ * 37/`VK_LEFT` with NumLock off and 100/`VK_NUMPAD4` with it on. Their entries
275
+ * here hold the NumLock-*off* values, so an entry wins over the value the
276
+ * browser resolved and pins that state: with NumLock on the engine is told
277
+ * `VK_LEFT`. Read `event.keyCode` directly if you need the other state.
278
+ * `NumpadAdd`/`Subtract`/`Multiply`/`Divide` carry no such caveat — they do not
279
+ * change with NumLock.
280
+ *
281
+ * `SpecialKeyCodes` in `./buttons-codes` repeats seven of these entries with
282
+ * the same values. The two are kept apart because both are public API; merging
283
+ * them would be a breaking change.
284
+ *
285
+ * `InvertedKeyMap` is consequently lossy. `NumpadEnter` and `Numpad4/8/6/2`
286
+ * share their values with `Enter` and `Arrow*`, and the derivation keeps the
287
+ * last name written for a value, so those five cannot be reached through it —
288
+ * which is what makes them unnameable through the numeric form of
289
+ * `useKeyboardKeys()`. The `OSLeft`/`MetaLeft` and `OSRight`/`MetaRight` pairs
290
+ * share a value on purpose: they are legacy and current names for the same
291
+ * physical key.
292
+ */
293
+ declare const KeyboardNumericCode: {
294
+ readonly Backspace: 8;
295
+ readonly Tab: 9;
296
+ readonly Numpad5: 12;
297
+ readonly NumpadEnter: 13;
298
+ readonly Numpad9: 33;
299
+ readonly Numpad3: 34;
300
+ readonly Numpad1: 35;
301
+ readonly Numpad7: 36;
302
+ readonly Numpad4: 37;
303
+ readonly Numpad8: 38;
304
+ readonly Numpad6: 39;
305
+ readonly Numpad2: 40;
306
+ readonly Numpad0: 45;
307
+ readonly NumpadDecimal: 46;
308
+ readonly Enter: 13;
309
+ readonly ShiftLeft: 16;
310
+ readonly ControlLeft: 17;
311
+ readonly AltLeft: 18;
312
+ readonly Escape: 27;
313
+ readonly Space: 32;
314
+ readonly ArrowLeft: 37;
315
+ readonly ArrowUp: 38;
316
+ readonly ArrowRight: 39;
317
+ readonly ArrowDown: 40;
318
+ readonly Digit0: 48;
319
+ readonly Digit1: 49;
320
+ readonly Digit2: 50;
321
+ readonly Digit3: 51;
322
+ readonly Digit4: 52;
323
+ readonly Digit5: 53;
324
+ readonly Digit6: 54;
325
+ readonly Digit7: 55;
326
+ readonly Digit8: 56;
327
+ readonly Digit9: 57;
328
+ readonly KeyA: 65;
329
+ readonly KeyB: 66;
330
+ readonly KeyC: 67;
331
+ readonly KeyD: 68;
332
+ readonly KeyE: 69;
333
+ readonly KeyF: 70;
334
+ readonly KeyG: 71;
335
+ readonly KeyH: 72;
336
+ readonly KeyI: 73;
337
+ readonly KeyJ: 74;
338
+ readonly KeyK: 75;
339
+ readonly KeyL: 76;
340
+ readonly KeyM: 77;
341
+ readonly KeyN: 78;
342
+ readonly KeyO: 79;
343
+ readonly KeyP: 80;
344
+ readonly KeyQ: 81;
345
+ readonly KeyR: 82;
346
+ readonly KeyS: 83;
347
+ readonly KeyT: 84;
348
+ readonly KeyU: 85;
349
+ readonly KeyV: 86;
350
+ readonly KeyW: 87;
351
+ readonly KeyX: 88;
352
+ readonly KeyY: 89;
353
+ readonly KeyZ: 90;
354
+ readonly OSLeft: 91;
355
+ readonly MetaLeft: 91;
356
+ readonly OSRight: 92;
357
+ readonly MetaRight: 92;
358
+ readonly ContextMenu: 93;
359
+ readonly NumpadMultiply: 106;
360
+ readonly NumpadAdd: 107;
361
+ readonly NumpadSubtract: 109;
362
+ readonly NumpadDivide: 111;
363
+ readonly F1: 112;
364
+ readonly F2: 113;
365
+ readonly F3: 114;
366
+ readonly F4: 115;
367
+ readonly F5: 116;
368
+ readonly F6: 117;
369
+ readonly F7: 118;
370
+ readonly F8: 119;
371
+ readonly F9: 120;
372
+ readonly F10: 121;
373
+ readonly F11: 122;
374
+ readonly F12: 123;
375
+ readonly NumLock: 144;
376
+ readonly ScrollLock: 145;
377
+ readonly Semicolon: 186;
378
+ readonly Equal: 187;
379
+ readonly Comma: 188;
380
+ readonly Minus: 189;
381
+ readonly Period: 190;
382
+ readonly Slash: 191;
383
+ readonly Backquote: 192;
384
+ readonly BracketLeft: 219;
385
+ readonly Backslash: 220;
386
+ readonly BracketRight: 221;
387
+ readonly Quote: 222;
388
+ readonly IntlBackslash: 226;
389
+ readonly ShiftRight: 253;
390
+ readonly ControlRight: 254;
391
+ readonly AltRight: 255;
392
+ };
393
+ /**
394
+ * The `keyCode` a browser reports while an IME is composing (`VK_PROCESSKEY`).
395
+ *
396
+ * `event.code` keeps naming the *physical* key during composition — `KeyW`,
397
+ * `KeyA` and friends — so a consumer that forwards keys has to drop these
398
+ * events explicitly or typing CJK text turns into keystrokes.
399
+ */
400
+ declare const IME_PROCESS_KEY = 229;
401
+ /**
402
+ * Codes of the keys that are themselves modifiers. A modifier's own `keydown`
403
+ * already reports `ctrlKey`/`metaKey` as pressed, so a "browser shortcut in
404
+ * flight" check has to exempt them or `ControlRight` would block itself.
405
+ */
406
+ declare const MODIFIER_KEY_CODES: ReadonlySet<string>;
407
+ type IKeyName = keyof typeof KeyboardNumericCode;
408
+ type IKeyCode = (typeof KeyboardNumericCode)[IKeyName];
409
+ /**
410
+ * A physical key name as reported by `KeyboardEvent.code`.
411
+ *
412
+ * The union lists the names this table knows a virtual-key code for, so editors
413
+ * complete them. The `string` branch keeps every other DOM code assignable —
414
+ * `'Delete'`, `'Home'`, `'PageUp'` — since a key needs no entry here to be
415
+ * matched against `event.code`.
416
+ */
417
+ type IKeyCodeName = IKeyName | (string & Record<never, never>);
418
+ type InvertedKeyMapType = {
419
+ [K in IKeyName as (typeof KeyboardNumericCode)[K]]: K;
420
+ };
421
+ declare const InvertedKeyMap: InvertedKeyMapType;
422
+
263
423
  declare const ControlScheme: {
264
424
  readonly LockedMouse: 0;
265
425
  readonly HoveringMouse: 1;
@@ -356,6 +516,33 @@ interface FreezeFrameMessage {
356
516
  progress: number | null;
357
517
  }
358
518
 
519
+ /**
520
+ * Geometry primitives used by the resize helpers.
521
+ *
522
+ * Collapsed into one file from the four these were split across, so the
523
+ * package no longer names another library in its public type surface.
524
+ */
525
+ interface Size {
526
+ w: number;
527
+ h: number;
528
+ }
529
+ interface Coords {
530
+ x: number;
531
+ y: number;
532
+ }
533
+ interface Rect extends Size, Coords {
534
+ }
535
+ interface Area extends Rect {
536
+ scale: number;
537
+ }
538
+
539
+ /**
540
+ * Output format accepted by {@link getImageFromVideoStream}.
541
+ *
542
+ * `'canvas'` returns the element itself; the MIME types return a data URL.
543
+ */
544
+ type ImageOutputValues = 'canvas' | 'image/png' | 'image/webp' | 'image/jpeg';
545
+
359
546
  interface SignalingData {
360
547
  instanceType: string;
361
548
  branchInfo: string;
@@ -1261,10 +1448,10 @@ declare class FreezeFrameService extends SubService {
1261
1448
 
1262
1449
  declare class InputService extends SubService {
1263
1450
  private readonly commandsSender;
1264
- private readonly isDevMode;
1451
+ private readonly devModeService;
1265
1452
  private readonly videoService;
1266
1453
  private readonly unrealConfig;
1267
- readonly defaultKeys: (17 | 37 | 38 | 39 | 40 | 46 | 65 | 68 | 83 | 87 | 107 | 109 | 187 | 189)[];
1454
+ readonly defaultKeys: readonly IKeyCodeName[];
1268
1455
  private availableKeys;
1269
1456
  private options;
1270
1457
  private reInit$;
@@ -1275,6 +1462,7 @@ declare class InputService extends SubService {
1275
1462
  private fingerIds;
1276
1463
  private maxByteValue;
1277
1464
  private fingers;
1465
+ private readonly heldKeys;
1278
1466
  private readonly mouseMoveView;
1279
1467
  private readonly mouseButtonView;
1280
1468
  private readonly mouseWheelView;
@@ -1286,7 +1474,16 @@ declare class InputService extends SubService {
1286
1474
  private offsetParams;
1287
1475
  constructor();
1288
1476
  protected init(): void;
1289
- useKeyboardKeys(keys: IKeyCode[]): void;
1477
+ /**
1478
+ * Replaces the forwarded-key allow-list. Takes physical key names
1479
+ * (`KeyboardEvent.code`) such as `'KeyW'` or `'BracketLeft'`.
1480
+ *
1481
+ * Legacy numeric `keyCode` values are still accepted and converted, but they
1482
+ * cannot express every key: `InvertedKeyMap` is many-to-one, so 37 resolves
1483
+ * to `'ArrowLeft'` and 17 to `'ControlLeft'`, and `'Numpad4'`/`'ControlRight'`
1484
+ * are unreachable through the numeric form. Pass names.
1485
+ */
1486
+ useKeyboardKeys(keys: readonly (IKeyCode | IKeyCodeName)[]): void;
1290
1487
  get video(): HTMLVideoElement | null;
1291
1488
  private setup;
1292
1489
  /**
@@ -1315,22 +1512,46 @@ declare class InputService extends SubService {
1315
1512
  private pressMouseButtons;
1316
1513
  private registerHoveringMouseEvents;
1317
1514
  private registerMouseEnterAndLeaveEvents;
1318
- private isKeyCodeBrowserKey;
1319
1515
  /**
1320
- * The key code Unreal expects, which is the Windows virtual-key code.
1516
+ * Keys whose default browser action fights the stream: the function keys
1517
+ * (F5 reloads mid-session), Tab (moves focus out of the player) and the
1518
+ * arrows (scroll the page while they steer the camera).
1519
+ *
1520
+ * Takes the *resolved* virtual-key code rather than `event.code`, so a numpad
1521
+ * key acting as an arrow with NumLock off is caught as well.
1522
+ */
1523
+ private isBrowserKey;
1524
+ /**
1525
+ * The key code Unreal expects — the Windows virtual-key code — or `null` when
1526
+ * the event carries no key worth forwarding.
1321
1527
  *
1322
- * `e.keyCode` is not that on Gecko: Firefox reports the punctuation keys by
1323
- * their ASCII value — `=` as 61 and `-` as 173, where Chrome sends 187 and 189
1324
- * — so zoom-on-plus/minus reached the engine as codes it does not know.
1325
- * `KeyboardNumericCode` is keyed by `e.code` and holds the virtual-key value,
1326
- * so it resolves the same number on every browser; a no-op on Chrome.
1528
+ * `KeyboardNumericCode` covers only the keys whose `event.keyCode` cannot be
1529
+ * trusted; everything else, the numpad included, falls through to the value
1530
+ * the browser resolved.
1327
1531
  *
1328
- * Checked after the right-hand modifiers, which need their own distinct codes
1329
- * that the map does not carry.
1532
+ * Returns `null` for an IME composition — `event.code` still names the
1533
+ * physical key there, so the keystrokes would otherwise reach the engine as
1534
+ * camera movement — and for a `keyCode` of 0, which no engine key uses.
1330
1535
  */
1331
1536
  private getKeyCode;
1537
+ /**
1538
+ * True while a focused element owns the keyboard. Read off `composedPath()`
1539
+ * rather than `event.target`: retargeting reports the host element for an
1540
+ * `<input>` inside a shadow root, so a `target`-based check never fires in an
1541
+ * app built from web components.
1542
+ */
1543
+ private isTextEntryTarget;
1544
+ /** True when `event.code` is on the allow-list, or dev mode waives it. */
1545
+ private isKeyAllowed;
1546
+ private sendKeyUp;
1547
+ /**
1548
+ * Releases everything the engine still believes is held. Needed because a
1549
+ * `keyup` is not guaranteed to arrive: the window can lose focus to a browser
1550
+ * dialog or another tab first, and then the engine keeps moving the camera
1551
+ * forever.
1552
+ */
1553
+ private releaseKeys;
1332
1554
  private registerKeyboardEvents;
1333
- private startListenKeys;
1334
1555
  static ɵfac: i0.ɵɵFactoryDeclaration<InputService, never>;
1335
1556
  static ɵprov: i0.ɵɵInjectableDeclaration<InputService>;
1336
1557
  }
@@ -1783,11 +2004,10 @@ declare class StreamStatusTelemetryService2 {
1783
2004
  /**
1784
2005
  * Emit the region / signaling / webrtc phases for an ADOPTED connection.
1785
2006
  *
1786
- * The prefetch script established the connection in PARALLEL with page load,
1787
- * so the phases are positioned at their real (anchor-relative) spans — shifted
1788
- * LEFT to overlap the HTML-loading / route-idle rows — rather than sequentially
1789
- * after them. That's the whole point of the prefetch: the handshake already
1790
- * happened while the page was loading. Each phase ships:
2007
+ * The prefetch script established the connection in parallel with page load,
2008
+ * so phases sit at their real anchor-relative spans — shifted left to overlap
2009
+ * the HTML-loading / route-idle rows instead of following them. Each phase
2010
+ * ships:
1791
2011
  * - `duration` — the real sub-step time the prefetch spent.
1792
2012
  * - `spanStartMs` — anchor-relative start, so the viewer left-shifts the bar.
1793
2013
  * - `adoptionAtMs` — where Angular took over (the next phase, Unreal
@@ -1834,22 +2054,15 @@ declare class StreamStatusTelemetryService2 {
1834
2054
  */
1835
2055
  private collectBootstrapInfo;
1836
2056
  /**
1837
- * Aggregate all resources the browser has loaded so far
1838
- * (`performance.getEntriesByType('resource')`), grouped by `initiatorType`,
1839
- * **split by `window.load`**:
1840
- *
1841
- * - `resources` — fetched before `loadEventEnd` (= part of the actual
1842
- * page weight). What you'd traditionally call "page load."
1843
- * - `lazyResources` — fetched after `loadEventEnd` (= typically the
1844
- * route's lazy-loaded chunks, JSON manifests etc. that download
1845
- * when the user navigates to a sub-route the streamer lives on).
2057
+ * Aggregates `performance.getEntriesByType('resource')` by `initiatorType`,
2058
+ * split at `window.load`: `resources` for entries before `loadEventEnd` (the
2059
+ * actual page weight) and `lazyResources` for those after it (typically the
2060
+ * route's lazy chunks and manifests, downloaded when the user navigates to the
2061
+ * sub-route the streamer lives on). If `loadEventEnd` is 0 or unknown,
2062
+ * everything lands in `resources` and `lazyResources` is null.
1846
2063
  *
1847
- * When `loadEventEnd` is 0/unknown, everything goes into `resources`
1848
- * and `lazyResources` is null — old behavior.
1849
- *
1850
- * `transferSize === 0 && decodedBodySize > 0` means the resource came from
1851
- * the HTTP cache — counted separately so cached hits don't inflate
1852
- * the "bytes on the wire" metric.
2064
+ * `transferSize === 0 && decodedBodySize > 0` means an HTTP-cache hit, counted
2065
+ * separately so it does not inflate the bytes-on-the-wire metric.
1853
2066
  */
1854
2067
  private collectResourceTotals;
1855
2068
  private aggregate;
@@ -2176,77 +2389,22 @@ declare class AnalyticsService extends SubService {
2176
2389
  }
2177
2390
 
2178
2391
  /**
2179
- * Adaptive FPS controller that monitors the incoming WebRTC video stream
2180
- * quality and dynamically adjusts Unreal Engine's `t.MaxFPS` console variable.
2181
- *
2182
- * ## How it works
2183
- *
2184
- * The service subscribes to `VideoService.videoStats$` (emitted every 250 ms)
2185
- * and collects samples of FPS, bitrate, and QP (Quantization Parameter).
2186
- * Every {@link SAMPLE_WINDOW} ticks (~2 s) it averages the collected samples
2187
- * and feeds them into a set of **fuzzy-logic membership functions** that
2188
- * classify each metric into overlapping quality bands.
2189
- *
2190
- * ### Input signals
2191
- *
2192
- * **1. FPS ratio** = `actualFPS / currentTargetFPS` (0..1+)
2193
- *
2194
- * | Band | Range | Meaning |
2195
- * |--------|---------------------|-----------------------------------------------|
2196
- * | low | ratio < 0.7 → 1.0, linear ramp 0.7..0.9 → 0 | Stream cannot keep up with the target |
2197
- * | okays | triangle 0.8..1.0, peak at 0.95 | Stream roughly matches the target |
2198
- * | high | ratio 0.9..1.0 → ramp, >1.0 → 1.0 | Stream meets or exceeds the target |
2199
- *
2200
- * **2. Bitrate load** = `currentBitrate / MAX_BITRATE` (0..1+)
2201
- *
2202
- * `MAX_BITRATE` = 20 Mbit/s — the reference ceiling for the encoder.
2203
- *
2204
- * | Band | Range | Meaning |
2205
- * |--------|-------------------------------------------------|-----------------------------------------------|
2206
- * | high | load 0.5..0.8 → ramp, >0.8 → 1.0 | Encoder is using most of the available bandwidth |
2207
- * | not high | load < 0.5 → 0.0 | Bandwidth headroom is available |
2208
- *
2209
- * **3. Quality** — mapped from QP via `mapQpToQuality()` (see {@link Quality}):
2210
- *
2211
- * | Quality | QP range | Meaning |
2212
- * |----------|-----------|-------------------------------------------------|
2213
- * | `lime` | QP <= 26 | Good — encoder has enough headroom |
2214
- * | `orange` | QP 27..35 | Fair — encoder is under moderate pressure |
2215
- * | `red` | QP > 35 | Poor — heavy compression, visible artifacts |
2216
- *
2217
- * ### Decision rules (evaluated in priority order, first match wins)
2218
- *
2219
- * | # | Condition | Decision |
2220
- * |---|------------------------------------------------------------|--------------|
2221
- * | 1 | FPS ratio is **low** (membership > 0.5) | **decrease** |
2222
- * | 2 | FPS ratio is **ok** AND quality is `red` | **decrease** |
2223
- * | 3 | FPS ratio is **ok** AND bitrate **high** AND quality is `orange` | **hold** |
2224
- * | 4 | FPS ratio is **high** AND quality is `lime` AND bitrate **not high** | **increase** |
2225
- * | — | Everything else | **hold** |
2392
+ * Adaptive FPS controller: watches the incoming WebRTC stream and steers Unreal's
2393
+ * `t.MaxFPS` between the discrete {@link FPS_STEPS}.
2226
2394
  *
2227
- * ## Cooldown-based throttling
2395
+ * Every {@link SAMPLE_WINDOW} ticks of `VideoService.videoStats$` (250 ms each)
2396
+ * it averages FPS, bitrate and QP, runs them through the fuzzy membership
2397
+ * functions below, and picks a decision by first-match rules. Fuzzy bands rather
2398
+ * than hard thresholds so a metric sitting on a boundary does not oscillate.
2228
2399
  *
2229
- * The service continuously evaluates fuzzy rules in every sample window.
2230
- * After each FPS step change it enforces a cooldown before the next
2231
- * change can be applied. The cooldown duration depends on the
2232
- * (previous decision → next decision) pair:
2400
+ * Cooldowns between steps are deliberately asymmetric: stepping *down* again is
2401
+ * allowed after {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS}, but stepping back
2402
+ * *up* after a decrease waits {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} — a
2403
+ * stream that just failed its target is likely to fail it again.
2233
2404
  *
2234
- * | Previous | Next | Cooldown |
2235
- * |-----------|-----------|-----------------------------------------------------|
2236
- * | decrease | decrease | {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS} (2 s) |
2237
- * | decrease | increase | {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} (10 s) |
2238
- * | increase | decrease | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
2239
- * | increase | increase | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
2240
- *
2241
- * This means the system reacts quickly after an increase (2 s in any
2242
- * direction) but is cautious about stepping back up after a decrease
2243
- * (10 s), while still allowing consecutive decreases at 2 s intervals.
2244
- *
2245
- * ## FPS dispatch
2246
- *
2247
- * FPS changes are dispatched via the NgRx `setMaxFps` action, which triggers
2248
- * the existing `UnrealEffects.setMaxFps$` effect that sends the
2249
- * `t.MaxFPS <value>` console command to Unreal Engine.
2405
+ * Changes go out as the `setMaxFps` action, not a direct command: the existing
2406
+ * `UnrealEffects.setMaxFps$` effect turns it into the `t.MaxFPS <value>` console
2407
+ * command.
2250
2408
  */
2251
2409
  declare class FpsMonitorService extends SubService {
2252
2410
  private readonly videoService;
@@ -2299,52 +2457,15 @@ declare class FpsMonitorService extends SubService {
2299
2457
  */
2300
2458
  private processTick;
2301
2459
  private handleMonitoring;
2302
- /**
2303
- * Combines fuzzy membership values into a single discrete decision.
2304
- * Rules are evaluated in priority order — first match wins.
2305
- *
2306
- * Inputs:
2307
- * - `fpsRatio` = avgFPS / currentTargetFPS (e.g., 45/60 = 0.75)
2308
- * - `bitrateLoad` = avgBitrate / 20 Mbit/s (e.g., 12M/20M = 0.6)
2309
- * - `quality` = mapQpToQuality(avgQP) → 'lime' | 'orange' | 'red'
2310
- */
2460
+ /** Rules are evaluated in priority order — first match wins. */
2311
2461
  private evaluateDecision;
2312
- /**
2313
- * FPS ratio membership "low".
2314
- * Input: a ratio = actualFPS / targetFPS (e.g., 42/60 = 0.70).
2315
- *
2316
- * ratio < 0.7 → 1.0 (clearly failing, e.g., 40/60)
2317
- * ratio 0.7..0.9 → linear ramp down (e.g., 0.8 → 0.5)
2318
- * a ratio > 0.9 → 0.0 (keeping up)
2319
- */
2462
+ /** "low" — the stream cannot keep up with its target. */
2320
2463
  private membershipFpsLow;
2321
- /**
2322
- * FPS ratio membership "ok" — triangle shape.
2323
- * Input: ratio = actualFPS / targetFPS.
2324
- *
2325
- * ratio < 0.8 → 0.0
2326
- * ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g., 57/60)
2327
- * ratio 0.95..1.0 → ramp down
2328
- * a ratio > 1.0 → 0.0
2329
- */
2464
+ /** "ok" — triangle peaking just under the target. */
2330
2465
  private membershipFpsOk;
2331
- /**
2332
- * FPS ratio membership "high".
2333
- * Input: ratio = actualFPS / targetFPS.
2334
- *
2335
- * ratio < 0.9 → 0.0
2336
- * ratio 0.9..1.0 → linear ramp up (e.g., 0.95 → 0.5)
2337
- * a ratio >= 1.0 → 1.0 (meeting or exceeding target, e.g., 60/60)
2338
- */
2466
+ /** "high" — the stream meets or exceeds its target. */
2339
2467
  private membershipFpsHigh;
2340
- /**
2341
- * Bitrate load membership "high".
2342
- * Input: a load = currentBitrate / MAX_BITRATE (20 Mbit/s).
2343
- *
2344
- * load < 0.5 → 0.0 (below 10 Mbit/s — plenty of headroom)
2345
- * load 0.5..0.8 → linear ramp (e.g., 13M/20M = 0.65 → 0.5)
2346
- * load > 0.8 → 1.0 (above 16 Mbit/s — near capacity)
2347
- */
2468
+ /** "high" — the encoder is using most of the available bandwidth. */
2348
2469
  private membershipBitrateHigh;
2349
2470
  private upgradeFps;
2350
2471
  /**
@@ -3481,5 +3602,5 @@ declare const selectUserInactivityDetected: _ngrx_store.MemoizedSelector<Record<
3481
3602
  /** Used by external consumers (e.g., metabox). Do not remove. */
3482
3603
  declare const selectIsVideoPlayingAndDataChannelConnected: _ngrx_store.MemoizedSelector<Record<string, any>, boolean, (s1: boolean, s2: boolean) => boolean>;
3483
3604
 
3484
- export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UnrealAfkSlotDirective, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealResumeSlotDirective, UnrealSceneComponent, UnrealStatusMessage, UnrealStatusSlotDirective, 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, isAppOwned, isMetaboxMode, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, resolveSkin, 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, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
3485
- export type { AggregatedStat, AnySignalingMessage, AppOwnedStream, AwsInstance, BootstrapInfo, CandidateMessage, CirrusRequestKind, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, DisconnectReasonType, EmittedMilestone, EventId, FpsLowData, FreezeFrameMessage, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, PlayerCountMessage, PollingOrchestrationMessage, Quality, QualityAnomalyData, ReceivedFile, RequestStream, ResourceBucket, ResourceTotals, SSInfo, SampleData, SelectedIceCandidatePair, SessionEndReason, SessionEndedData, SessionStartedData, SignalingData, SignalingMessageHandler, SignalingMessageMap, SignalingRetryData, SignalingRetryKind, SlowResource, StreamConfig, StreamRequestContext, StreamResolutionProps, StreamTelemetryEvent, StreamTelemetryEventMap, StreamTelemetryEventName, StreamTelemetryLifecycleData, StreamTelemetryLifecycleEventName, StreamTelemetryTrackData, StreamerListMessage, TelemetryEventEnvelope, TelemetryLifecycleEnvelope, TelemetryType, UnknownFields, UnrealAfkSlotContext, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped, UnrealResumeSlotContext, UnrealSlotTemplateContext, UnrealState, UnrealStatusSlotContext, UnrealUiClasses, UnrealUiPart, WSCloseCodesValues, WindowAggregate };
3605
+ export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IME_PROCESS_KEY, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, InvertedKeyMap, KeyboardNumericCode, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MODIFIER_KEY_CODES, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UnrealAfkSlotDirective, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealResumeSlotDirective, UnrealSceneComponent, UnrealStatusMessage, UnrealStatusSlotDirective, 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, isAppOwned, isMetaboxMode, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, resolveSkin, 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, startStream, streamAdopted, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
3606
+ export type { AggregatedStat, AnySignalingMessage, AppOwnedStream, Area, AwsInstance, BootstrapInfo, CandidateMessage, CirrusRequestKind, CloseReason, CommandInProgress, ConfigMessage, ConnectionError, ControlSchemeValues, Coords, DisconnectReasonType, EmittedMilestone, EventId, FpsLowData, FreezeFrameMessage, IKeyCode, IKeyCodeName, IKeyName, ImageOutputValues, InboundVideoStats, InputProps, InstanceReady, InstanceReserved, InterruptClientStream, ListStreamersMessage, MessageBase, MessageTypeValues, OrchestrationErrorMessage, OrchestrationMessageType, P2PEstablishedMessage, ParkedStream, PhaseEndedData, PhaseOverlay, PhaseParam, PlayerCountMessage, PollingOrchestrationMessage, Quality, QualityAnomalyData, ReceivedFile, Rect, RequestStream, ResourceBucket, ResourceTotals, SSInfo, SampleData, SelectedIceCandidatePair, SessionEndReason, SessionEndedData, SessionStartedData, SignalingData, SignalingMessageHandler, SignalingMessageMap, SignalingRetryData, SignalingRetryKind, Size, SlowResource, StreamConfig, StreamRequestContext, StreamResolutionProps, StreamTelemetryEvent, StreamTelemetryEventMap, StreamTelemetryEventName, StreamTelemetryLifecycleData, StreamTelemetryLifecycleEventName, StreamTelemetryTrackData, StreamerListMessage, TelemetryEventEnvelope, TelemetryLifecycleEnvelope, TelemetryType, UnknownFields, UnrealAfkSlotContext, UnrealCallbackDescriptor, UnrealCallbackEventMap, UnrealCallbackJson, UnrealCallbackJsonWrapped, UnrealResumeSlotContext, UnrealSlotTemplateContext, UnrealState, UnrealStatusSlotContext, UnrealUiClasses, UnrealUiPart, WSCloseCodesValues, WindowAggregate };