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

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.
@@ -4,10 +4,10 @@ import { NgTemplateOutlet, NgOptimizedImage, JsonPipe } from '@angular/common';
4
4
  import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
5
5
  import { Actions, ofType, createEffect, provideEffects } from '@ngrx/effects';
6
6
  import { Subject, skip, share, timer, of, take, defer, switchMap, from, throwError, Observable, EMPTY, retry, merge, timeout as timeout$1, repeat, BehaviorSubject, interval, map as map$1, takeUntil as takeUntil$1, fromEvent, tap as tap$1, throttleTime, delay, concat, debounceTime, mergeMap, NEVER, ignoreElements, startWith, scan, timestamp, pairwise, concatMap, animationFrameScheduler, shareReplay, firstValueFrom, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith } from 'rxjs';
7
- import { whereNot, lerp, clampf, Falsy, generateUuid, Truthy, Logger, tapLog, Semaphore, COLOR_CODES, getCanvasCached, getSnapshot, fitIntoRectangle, where, KeyboardNumericCode, InvertedKeyMap, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
8
7
  import { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
9
8
  export { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
10
- import { filter, takeUntil, catchError, tap, map, timeout, finalize, first, switchMap as switchMap$1, distinctUntilChanged, withLatestFrom, exhaustMap, takeWhile, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
9
+ import { tap, filter, takeUntil, catchError, map, timeout, finalize, first, switchMap as switchMap$1, distinctUntilChanged, withLatestFrom, exhaustMap, takeWhile, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
10
+ import { v4 } from 'uuid';
11
11
  import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
12
12
  import { HttpClient } from '@angular/common/http';
13
13
  import { ScrollStrategyOptions } from '@angular/cdk/overlay';
@@ -36,10 +36,10 @@ class UnrealButtonComponent {
36
36
  event.preventDefault();
37
37
  this.clicked.emit(event);
38
38
  }
39
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
40
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: UnrealButtonComponent, isStandalone: true, selector: "app-unreal-button", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, testID: { classPropertyName: "testID", publicName: "data-testid", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\n (click)=\"handleClick($event)\"\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n [class.unreal-button--primary]=\"weight() === 'primary'\"\n [class.unreal-button--lg]=\"size() === 'lg'\"\n [class.unreal-button--full-width]=\"fullWidth()\"\n [disabled]=\"isDisabled()\"\n [type]=\"type()\"\n class=\"unreal-button\"\n>\n <span><ng-content></ng-content></span>\n</button>\n", styles: [".unreal-button{--unrealButtonHeight: 36px;--unrealButtonPaddings: 0 12px;--unrealButtonBorderRadius: 6px;--unrealButtonFontSize: var(--src-font-size-sm, 14px);--unrealButtonLineHeight: var(--src-font-line-sm, 20px);--unrealButtonWidth: auto;--unrealButtonBgColor: transparent;--unrealButtonBorderColor: var(--src-border-button-basic, #e5e7eb);--unrealButtonBorder: 1px solid var(--unrealButtonBorderColor);--unrealButtonFontColor: var(--src-text-ui-secondary-main, #111827);--unrealButtonFontWeight: var(--src-font-weight-medium, 500);--unrealButtonFocusRingSize: 4px;--unrealButtonFocusRingColor: var( --src-shadow-light, rgba(148, 163, 184, .16) );--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 1px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16));position:relative;display:flex;justify-content:center;align-items:center;width:var(--unrealButtonWidth);height:var(--unrealButtonHeight);padding:var(--unrealButtonPaddings);font-weight:var(--unrealButtonFontWeight);font-size:var(--unrealButtonFontSize);font-style:normal;line-height:var(--unrealButtonLineHeight);text-align:center;color:var(--unrealButtonFontColor);letter-spacing:0;white-space:nowrap;border:none;background-color:var(--unrealButtonBgColor);border-radius:var(--unrealButtonBorderRadius);box-shadow:var(--unrealButtonBoxShadow);cursor:pointer}.unreal-button:after{content:\"\";position:absolute;inset:0;border-radius:var(--unrealButtonBorderRadius);border:var(--unrealButtonBorder)}.unreal-button>span{display:flex;justify-content:center;align-items:center;gap:var(--src-gap-md, 6px);width:auto;font-size:inherit;line-height:inherit;color:inherit;font-weight:inherit}.unreal-button:disabled{--unrealButtonBgColor: var( --src-ui-accent-disabled, rgba(148, 163, 184, .24) );--unrealButtonFontColor: var(--src-text-ui-secondary-disabled, #9ca3af);--unrealButtonBoxShadow: none;--unrealButtonBorder: none;--unrealButtonBorderColor: transparent;cursor:default;pointer-events:none}.unreal-button:focus-visible{--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af);outline:none;box-shadow:0 0 0 var(--unrealButtonFocusRingSize) var(--unrealButtonFocusRingColor)}@media(pointer:fine){.unreal-button:hover{--unrealButtonBgColor: var( --src-ui-secondary-default-hover, rgba(148, 163, 184, .08) );--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af)}}.unreal-button--primary{--unrealButtonBgColor: var(--unreal-accent, var(--src-ui-accent-default, #017bff));--unrealButtonBorder: none;--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff));--unrealButtonFocusRingColor: var( --src-shadow-accent-light, rgba(1, 123, 255, .16) )}@media(pointer:fine){.unreal-button--primary:hover{--unrealButtonBgColor: var(--unreal-accent-hover, var(--src-ui-accent-default-hover, #016fe6));--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff))}}.unreal-button--lg{--unrealButtonHeight: 44px;--unrealButtonPaddings: 0 16px;--unrealButtonBorderRadius: 8px;--unrealButtonFontSize: var(--src-font-size-base, 16px);--unrealButtonLineHeight: var(--src-font-line-base, 24px);--unrealButtonFocusRingSize: 6px;--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 2px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16))}.unreal-button--lg>span{gap:var(--src-gap-md, 8px)}.unreal-button--full-width{--unrealButtonWidth: 100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
39
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
40
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.3", type: UnrealButtonComponent, isStandalone: true, selector: "app-unreal-button", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null }, testID: { classPropertyName: "testID", publicName: "data-testid", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clicked: "clicked" }, ngImport: i0, template: "<button\n (click)=\"handleClick($event)\"\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n [class.unreal-button--primary]=\"weight() === 'primary'\"\n [class.unreal-button--lg]=\"size() === 'lg'\"\n [class.unreal-button--full-width]=\"fullWidth()\"\n [disabled]=\"isDisabled()\"\n [type]=\"type()\"\n class=\"unreal-button\"\n>\n <span><ng-content></ng-content></span>\n</button>\n", styles: [".unreal-button{--unrealButtonHeight: 36px;--unrealButtonPaddings: 0 12px;--unrealButtonBorderRadius: 6px;--unrealButtonFontSize: var(--src-font-size-sm, 14px);--unrealButtonLineHeight: var(--src-font-line-sm, 20px);--unrealButtonWidth: auto;--unrealButtonBgColor: transparent;--unrealButtonBorderColor: var(--src-border-button-basic, #e5e7eb);--unrealButtonBorder: 1px solid var(--unrealButtonBorderColor);--unrealButtonFontColor: var(--src-text-ui-secondary-main, #111827);--unrealButtonFontWeight: var(--src-font-weight-medium, 500);--unrealButtonFocusRingSize: 4px;--unrealButtonFocusRingColor: var( --src-shadow-light, rgba(148, 163, 184, .16) );--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 1px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16));position:relative;display:flex;justify-content:center;align-items:center;width:var(--unrealButtonWidth);height:var(--unrealButtonHeight);padding:var(--unrealButtonPaddings);font-weight:var(--unrealButtonFontWeight);font-size:var(--unrealButtonFontSize);font-style:normal;line-height:var(--unrealButtonLineHeight);text-align:center;color:var(--unrealButtonFontColor);letter-spacing:0;white-space:nowrap;border:none;background-color:var(--unrealButtonBgColor);border-radius:var(--unrealButtonBorderRadius);box-shadow:var(--unrealButtonBoxShadow);cursor:pointer}.unreal-button:after{content:\"\";position:absolute;inset:0;border-radius:var(--unrealButtonBorderRadius);border:var(--unrealButtonBorder)}.unreal-button>span{display:flex;justify-content:center;align-items:center;gap:var(--src-gap-md, 6px);width:auto;font-size:inherit;line-height:inherit;color:inherit;font-weight:inherit}.unreal-button:disabled{--unrealButtonBgColor: var( --src-ui-accent-disabled, rgba(148, 163, 184, .24) );--unrealButtonFontColor: var(--src-text-ui-secondary-disabled, #9ca3af);--unrealButtonBoxShadow: none;--unrealButtonBorder: none;--unrealButtonBorderColor: transparent;cursor:default;pointer-events:none}.unreal-button:focus-visible{--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af);outline:none;box-shadow:0 0 0 var(--unrealButtonFocusRingSize) var(--unrealButtonFocusRingColor)}@media(pointer:fine){.unreal-button:hover{--unrealButtonBgColor: var( --src-ui-secondary-default-hover, rgba(148, 163, 184, .08) );--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af)}}.unreal-button--primary{--unrealButtonBgColor: var(--unreal-accent, var(--src-ui-accent-default, #017bff));--unrealButtonBorder: none;--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff));--unrealButtonFocusRingColor: var( --src-shadow-accent-light, rgba(1, 123, 255, .16) )}@media(pointer:fine){.unreal-button--primary:hover{--unrealButtonBgColor: var(--unreal-accent-hover, var(--src-ui-accent-default-hover, #016fe6));--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff))}}.unreal-button--lg{--unrealButtonHeight: 44px;--unrealButtonPaddings: 0 16px;--unrealButtonBorderRadius: 8px;--unrealButtonFontSize: var(--src-font-size-base, 16px);--unrealButtonLineHeight: var(--src-font-line-base, 24px);--unrealButtonFocusRingSize: 6px;--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 2px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16))}.unreal-button--lg>span{gap:var(--src-gap-md, 8px)}.unreal-button--full-width{--unrealButtonWidth: 100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
41
41
  }
42
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealButtonComponent, decorators: [{
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealButtonComponent, decorators: [{
43
43
  type: Component,
44
44
  args: [{ selector: 'app-unreal-button', changeDetection: ChangeDetectionStrategy.OnPush, template: "<button\n (click)=\"handleClick($event)\"\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n [class.unreal-button--primary]=\"weight() === 'primary'\"\n [class.unreal-button--lg]=\"size() === 'lg'\"\n [class.unreal-button--full-width]=\"fullWidth()\"\n [disabled]=\"isDisabled()\"\n [type]=\"type()\"\n class=\"unreal-button\"\n>\n <span><ng-content></ng-content></span>\n</button>\n", styles: [".unreal-button{--unrealButtonHeight: 36px;--unrealButtonPaddings: 0 12px;--unrealButtonBorderRadius: 6px;--unrealButtonFontSize: var(--src-font-size-sm, 14px);--unrealButtonLineHeight: var(--src-font-line-sm, 20px);--unrealButtonWidth: auto;--unrealButtonBgColor: transparent;--unrealButtonBorderColor: var(--src-border-button-basic, #e5e7eb);--unrealButtonBorder: 1px solid var(--unrealButtonBorderColor);--unrealButtonFontColor: var(--src-text-ui-secondary-main, #111827);--unrealButtonFontWeight: var(--src-font-weight-medium, 500);--unrealButtonFocusRingSize: 4px;--unrealButtonFocusRingColor: var( --src-shadow-light, rgba(148, 163, 184, .16) );--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 1px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16));position:relative;display:flex;justify-content:center;align-items:center;width:var(--unrealButtonWidth);height:var(--unrealButtonHeight);padding:var(--unrealButtonPaddings);font-weight:var(--unrealButtonFontWeight);font-size:var(--unrealButtonFontSize);font-style:normal;line-height:var(--unrealButtonLineHeight);text-align:center;color:var(--unrealButtonFontColor);letter-spacing:0;white-space:nowrap;border:none;background-color:var(--unrealButtonBgColor);border-radius:var(--unrealButtonBorderRadius);box-shadow:var(--unrealButtonBoxShadow);cursor:pointer}.unreal-button:after{content:\"\";position:absolute;inset:0;border-radius:var(--unrealButtonBorderRadius);border:var(--unrealButtonBorder)}.unreal-button>span{display:flex;justify-content:center;align-items:center;gap:var(--src-gap-md, 6px);width:auto;font-size:inherit;line-height:inherit;color:inherit;font-weight:inherit}.unreal-button:disabled{--unrealButtonBgColor: var( --src-ui-accent-disabled, rgba(148, 163, 184, .24) );--unrealButtonFontColor: var(--src-text-ui-secondary-disabled, #9ca3af);--unrealButtonBoxShadow: none;--unrealButtonBorder: none;--unrealButtonBorderColor: transparent;cursor:default;pointer-events:none}.unreal-button:focus-visible{--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af);outline:none;box-shadow:0 0 0 var(--unrealButtonFocusRingSize) var(--unrealButtonFocusRingColor)}@media(pointer:fine){.unreal-button:hover{--unrealButtonBgColor: var( --src-ui-secondary-default-hover, rgba(148, 163, 184, .08) );--unrealButtonBorderColor: var(--src-border-button-basic-hover, #9ca3af)}}.unreal-button--primary{--unrealButtonBgColor: var(--unreal-accent, var(--src-ui-accent-default, #017bff));--unrealButtonBorder: none;--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff));--unrealButtonFocusRingColor: var( --src-shadow-accent-light, rgba(1, 123, 255, .16) )}@media(pointer:fine){.unreal-button--primary:hover{--unrealButtonBgColor: var(--unreal-accent-hover, var(--src-ui-accent-default-hover, #016fe6));--unrealButtonFontColor: var(--unreal-accent-text, var(--src-text-ui-primary-main, #fff))}}.unreal-button--lg{--unrealButtonHeight: 44px;--unrealButtonPaddings: 0 16px;--unrealButtonBorderRadius: 8px;--unrealButtonFontSize: var(--src-font-size-base, 16px);--unrealButtonLineHeight: var(--src-font-line-base, 24px);--unrealButtonFocusRingSize: 6px;--unrealButtonBoxShadow: 0 -1px 0 0 var(--src-shadow-hard, rgba(71, 85, 105, .24)) inset, 0 2px 0 0 var(--src-shadow-light, rgba(148, 163, 184, .16))}.unreal-button--lg>span{gap:var(--src-gap-md, 8px)}.unreal-button--full-width{--unrealButtonWidth: 100%}\n"] }]
45
45
  }], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], isDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "isDisabled", required: false }] }], testID: [{ type: i0.Input, args: [{ isSignal: true, alias: "data-testid", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], clicked: [{ type: i0.Output, args: ["clicked"] }] } });
@@ -307,6 +307,157 @@ const MouseButtonsMask = {
307
307
  FifthButton: 16, // Browser Forward button.
308
308
  };
309
309
 
310
+ /**
311
+ * `KeyboardEvent.code` → the Windows virtual-key code Unreal expects.
312
+ *
313
+ * Only keys whose `event.keyCode` cannot be trusted belong here. The browser
314
+ * already resolves `keyCode` correctly for everything else, so the table exists
315
+ * for two cases: browsers that disagree on a value (Gecko reports the
316
+ * punctuation keys by ASCII — `;` as 59, `=` as 61, `-` as 173) and keys Unreal
317
+ * numbers differently from the DOM (the right-hand modifiers, the Cmd/Win keys).
318
+ *
319
+ * The numpad is deliberately absent. A numpad key reports the same `code` in
320
+ * both NumLock states but a different virtual key — `Numpad4` is 37/`VK_LEFT`
321
+ * with NumLock off and 100/`VK_NUMPAD4` with it on — so any entry here would
322
+ * have to commit to one state and be wrong in the other. Falling through to
323
+ * `event.keyCode` is right in both. `NumpadAdd`/`Subtract`/`Multiply`/`Divide`
324
+ * stay because they do not change with NumLock.
325
+ *
326
+ * `SpecialKeyCodes` in `./buttons-codes` repeats seven of these entries with
327
+ * the same values. The two are kept apart because both are public API; merging
328
+ * them would be a breaking change.
329
+ *
330
+ * Values must stay unique so `InvertedKeyMap` is lossless. The `OSLeft`/
331
+ * `MetaLeft` and `OSRight`/`MetaRight` pairs share a value on purpose: they are
332
+ * legacy and current names for the same physical key.
333
+ */
334
+ const KeyboardNumericCode = {
335
+ Backspace: 8,
336
+ Tab: 9,
337
+ Enter: 13,
338
+ ShiftLeft: 16,
339
+ ControlLeft: 17,
340
+ AltLeft: 18,
341
+ Escape: 27,
342
+ Space: 32,
343
+ ArrowLeft: 37,
344
+ ArrowUp: 38,
345
+ ArrowRight: 39,
346
+ ArrowDown: 40,
347
+ Digit0: 48,
348
+ Digit1: 49,
349
+ Digit2: 50,
350
+ Digit3: 51,
351
+ Digit4: 52,
352
+ Digit5: 53,
353
+ Digit6: 54,
354
+ Digit7: 55,
355
+ Digit8: 56,
356
+ Digit9: 57,
357
+ KeyA: 65,
358
+ KeyB: 66,
359
+ KeyC: 67,
360
+ KeyD: 68,
361
+ KeyE: 69,
362
+ KeyF: 70,
363
+ KeyG: 71,
364
+ KeyH: 72,
365
+ KeyI: 73,
366
+ KeyJ: 74,
367
+ KeyK: 75,
368
+ KeyL: 76,
369
+ KeyM: 77,
370
+ KeyN: 78,
371
+ KeyO: 79,
372
+ KeyP: 80,
373
+ KeyQ: 81,
374
+ KeyR: 82,
375
+ KeyS: 83,
376
+ KeyT: 84,
377
+ KeyU: 85,
378
+ KeyV: 86,
379
+ KeyW: 87,
380
+ KeyX: 88,
381
+ KeyY: 89,
382
+ KeyZ: 90,
383
+ // Chrome/Safari report 91 for the left Cmd/Win key, Firefox on macOS 224.
384
+ // The right one shares keyCode 93 with ContextMenu on Chrome/Safari, making
385
+ // the two indistinguishable. Unreal numbers them 91 and 92.
386
+ OSLeft: 91,
387
+ MetaLeft: 91,
388
+ OSRight: 92,
389
+ MetaRight: 92,
390
+ ContextMenu: 93,
391
+ NumpadMultiply: 106,
392
+ NumpadAdd: 107,
393
+ NumpadSubtract: 109,
394
+ NumpadDivide: 111,
395
+ F1: 112,
396
+ F2: 113,
397
+ F3: 114,
398
+ F4: 115,
399
+ F5: 116,
400
+ F6: 117,
401
+ F7: 118,
402
+ F8: 119,
403
+ F9: 120,
404
+ F10: 121,
405
+ F11: 122,
406
+ F12: 123,
407
+ NumLock: 144,
408
+ ScrollLock: 145,
409
+ Semicolon: 186,
410
+ Equal: 187,
411
+ Comma: 188,
412
+ Minus: 189,
413
+ Period: 190,
414
+ Slash: 191,
415
+ Backquote: 192,
416
+ BracketLeft: 219,
417
+ Backslash: 220,
418
+ BracketRight: 221,
419
+ Quote: 222,
420
+ IntlBackslash: 226,
421
+ // Unreal keeps distinct codes for the right-hand modifiers; the DOM reports
422
+ // both sides of each pair under the same `keyCode`.
423
+ ShiftRight: 253,
424
+ ControlRight: 254,
425
+ AltRight: 255,
426
+ };
427
+ /**
428
+ * The `keyCode` a browser reports while an IME is composing (`VK_PROCESSKEY`).
429
+ *
430
+ * `event.code` keeps naming the *physical* key during composition — `KeyW`,
431
+ * `KeyA` and friends — so a consumer that forwards keys has to drop these
432
+ * events explicitly or typing CJK text turns into keystrokes.
433
+ */
434
+ const IME_PROCESS_KEY = 229;
435
+ /**
436
+ * Codes of the keys that are themselves modifiers. A modifier's own `keydown`
437
+ * already reports `ctrlKey`/`metaKey` as pressed, so a "browser shortcut in
438
+ * flight" check has to exempt them or `ControlRight` would block itself.
439
+ */
440
+ const MODIFIER_KEY_CODES = new Set([
441
+ 'ShiftLeft',
442
+ 'ShiftRight',
443
+ 'ControlLeft',
444
+ 'ControlRight',
445
+ 'AltLeft',
446
+ 'AltRight',
447
+ 'MetaLeft',
448
+ 'MetaRight',
449
+ 'OSLeft',
450
+ 'OSRight',
451
+ ]);
452
+ const InvertedKeyMapValues = {};
453
+ //For use for InvertedKeyMap[32] will return 'Space' and so on
454
+ const InvertedKeyMap = InvertedKeyMapValues;
455
+ Object.entries(KeyboardNumericCode).forEach(
456
+ // @ts-expect-error @typescript-eslint/ban-ts-comment
457
+ ([key, value]) => {
458
+ InvertedKeyMapValues[value] = key;
459
+ });
460
+
310
461
  const ControlScheme = {
311
462
  // A mouse can lock inside the WebRTC player, so the user can simply move the
312
463
  // mouse to control the orientation of the camera. The user presses the
@@ -491,8 +642,12 @@ const UnrealStatusMessage = {
491
642
  };
492
643
 
493
644
  const COMMAND_EXILE_TIMEOUT_MS = 10_000;
645
+ const normalizeId = (value) => String(value).trim().replace(/ +/gi, '_').toLowerCase();
494
646
  function exileExpiredCommands(commandsInProgress, idToRemove, now) {
495
- let remaining = commandsInProgress.filter(whereNot({ id: idToRemove }));
647
+ // Case-insensitive, like the `whereNot()` predicate this replaces: a caller
648
+ // supplies `correlationId`, and it round-trips through the engine.
649
+ const idKey = normalizeId(idToRemove);
650
+ let remaining = commandsInProgress.filter(({ id }) => normalizeId(id) !== idKey);
496
651
  const expired = remaining.filter(({ timeStamp }) => now - timeStamp > COMMAND_EXILE_TIMEOUT_MS);
497
652
  if (expired.length) {
498
653
  const expiredIds = new Set(expired.map(({ id }) => id));
@@ -825,6 +980,488 @@ const unrealFeature = createFeature({
825
980
  reducer: unrealReducer,
826
981
  });
827
982
 
983
+ function fitIntoRectangle(objectRect, areaRect, round = true, fixedRatio = null) {
984
+ const result = { x: 0, y: 0, w: 0, h: 0, scale: 1 };
985
+ const ratio = objectRect.h / objectRect.w;
986
+ if (objectRect.h > objectRect.w) {
987
+ result.h = areaRect.h;
988
+ result.w = areaRect.h / ratio;
989
+ if (result.w > areaRect.w) {
990
+ result.w = areaRect.w;
991
+ result.h = areaRect.w * ratio;
992
+ }
993
+ }
994
+ else {
995
+ result.w = areaRect.w;
996
+ result.h = areaRect.w * ratio;
997
+ if (result.h > areaRect.h) {
998
+ result.h = areaRect.h;
999
+ result.w = areaRect.h / ratio;
1000
+ }
1001
+ }
1002
+ if (fixedRatio) {
1003
+ result.w = objectRect.w * fixedRatio;
1004
+ result.h = objectRect.h * fixedRatio;
1005
+ }
1006
+ const wOut = round ? Math.floor(result.w) : result.w;
1007
+ const hOut = round ? Math.floor(result.h) : result.h;
1008
+ return {
1009
+ x: areaRect.x + (areaRect.w - wOut) / 2,
1010
+ y: areaRect.y + (areaRect.h - hOut) / 2,
1011
+ w: wOut,
1012
+ h: hOut,
1013
+ scale: wOut / objectRect.w,
1014
+ };
1015
+ }
1016
+
1017
+ const canvasCacheObj = {};
1018
+ function getCanvasCached(id) {
1019
+ if (!canvasCacheObj[id]) {
1020
+ const canvas = document.createElement('canvas');
1021
+ const ctx = canvas.getContext('2d');
1022
+ if (!ctx) {
1023
+ throw new Error('Canvas 2D context is unavailable.');
1024
+ }
1025
+ canvasCacheObj[id] = {
1026
+ canvas,
1027
+ ctx,
1028
+ };
1029
+ }
1030
+ return canvasCacheObj[id];
1031
+ }
1032
+ function sourceDimensions(imageElement) {
1033
+ const naturalWidth = 'naturalWidth' in imageElement ? imageElement.naturalWidth : 0;
1034
+ const naturalHeight = 'naturalHeight' in imageElement ? imageElement.naturalHeight : 0;
1035
+ return {
1036
+ width: naturalWidth || imageElement.width,
1037
+ height: naturalHeight || imageElement.height,
1038
+ };
1039
+ }
1040
+ function canvasContext(canvas) {
1041
+ const context = canvas.getContext('2d');
1042
+ if (!context) {
1043
+ throw new Error('Canvas 2D context is unavailable.');
1044
+ }
1045
+ return context;
1046
+ }
1047
+ function getSnapshot2(imageElement, maxWidth = 1024, output = 'image/png', quality = 0.9) {
1048
+ const canvasHelper = document.createElement('canvas');
1049
+ const drawCTX = canvasContext(canvasHelper);
1050
+ const dimensions = sourceDimensions(imageElement);
1051
+ const prop = fitIntoRectangle({
1052
+ x: 0,
1053
+ y: 0,
1054
+ w: dimensions.width,
1055
+ h: dimensions.height,
1056
+ }, {
1057
+ x: 0,
1058
+ y: 0,
1059
+ w: maxWidth,
1060
+ h: dimensions.height,
1061
+ });
1062
+ const width = prop.w;
1063
+ const height = prop.h;
1064
+ canvasHelper.width = width;
1065
+ canvasHelper.height = height;
1066
+ drawCTX.save();
1067
+ drawCTX.clearRect(0, 0, width, height);
1068
+ drawCTX.drawImage(imageElement, 0, 0, width, height);
1069
+ drawCTX.restore();
1070
+ if (output === 'canvas') {
1071
+ return canvasHelper;
1072
+ }
1073
+ return canvasHelper.toDataURL(output, quality);
1074
+ }
1075
+ function getSnapshot(imageElement, maxWidth = 1024, output = 'image/png', quality = 0.9) {
1076
+ const canvasHelper = document.createElement('canvas');
1077
+ const drawCTX = canvasContext(canvasHelper);
1078
+ const dimensions = sourceDimensions(imageElement);
1079
+ const prop = fitIntoRectangle({
1080
+ x: 0,
1081
+ y: 0,
1082
+ w: dimensions.width,
1083
+ h: dimensions.height,
1084
+ }, {
1085
+ x: 0,
1086
+ y: 0,
1087
+ w: dimensions.width / 2,
1088
+ h: dimensions.height,
1089
+ });
1090
+ const width = prop.w;
1091
+ const height = prop.h;
1092
+ canvasHelper.width = width;
1093
+ canvasHelper.height = height;
1094
+ drawCTX.save();
1095
+ drawCTX.clearRect(0, 0, width, height);
1096
+ drawCTX.drawImage(imageElement, 0, 0, width, height);
1097
+ drawCTX.restore();
1098
+ if (width > maxWidth * 2) {
1099
+ return getSnapshot(canvasHelper, maxWidth, output, quality);
1100
+ }
1101
+ return getSnapshot2(imageElement, maxWidth, output, quality);
1102
+ }
1103
+
1104
+ /* eslint-disable prefer-const */
1105
+ /**
1106
+ * Colour-space conversions used by the dev stat graph overlay.
1107
+ *
1108
+ * Carried over verbatim; `HSVtoRGB` reassigns its locals, hence the
1109
+ * file-level `prefer-const` exemption.
1110
+ */
1111
+ function HEXtoRGB(color) {
1112
+ if (typeof color === 'string') {
1113
+ color = parseInt(color.replace('#', ''), 16);
1114
+ }
1115
+ const r = (color >> 16) & 255;
1116
+ const g = (color >> 8) & 255;
1117
+ const b = color & 255;
1118
+ return { r: r, g: g, b: b };
1119
+ }
1120
+ function RGBtoHEX(r, g, b) {
1121
+ let redColor = r.toString(16);
1122
+ let greenColor = g.toString(16);
1123
+ let blackColor = b.toString(16);
1124
+ if (redColor.length === 1) {
1125
+ redColor = '0' + redColor;
1126
+ }
1127
+ if (greenColor.length === 1) {
1128
+ greenColor = '0' + greenColor;
1129
+ }
1130
+ if (blackColor.length === 1) {
1131
+ blackColor = '0' + blackColor;
1132
+ }
1133
+ return (redColor + greenColor + blackColor).toUpperCase();
1134
+ }
1135
+ /**
1136
+ * RGB from the respective figures, HSV sequences in terms of returns.
1137
+ * RGB values are as follows.
1138
+ * R - a number from 0 to 255
1139
+ * G - a number from 0 to 255
1140
+ * B - a number from 0 to 255
1141
+ *
1142
+ * HSV values are as follows.
1143
+ * H - a number between 360-0
1144
+ * S - number between 0 and 1.0
1145
+ * V - number between 0 and 1.0
1146
+ *
1147
+ * Cannot compute, including alpha.
1148
+ * @ Param r the red (R) indicating the number (0x00 to 0xFF to)
1149
+ * @ Param g green (G) indicates the number (0x00 to 0xFF to)
1150
+ * @ Param b blue (B) shows the number (0x00 to 0xFF to)
1151
+ * @ Return HSV values as [H, S, V]
1152
+ **/
1153
+ function RGBtoHSV(r, g, b) {
1154
+ r /= 255;
1155
+ g /= 255;
1156
+ b /= 255;
1157
+ let h = 0;
1158
+ let s;
1159
+ let x, y;
1160
+ if (r >= g)
1161
+ x = r;
1162
+ else
1163
+ x = g;
1164
+ if (b > x)
1165
+ x = b;
1166
+ if (r <= g)
1167
+ y = r;
1168
+ else
1169
+ y = g;
1170
+ if (b < y)
1171
+ y = b;
1172
+ const v = x;
1173
+ const c = x - y;
1174
+ if (x === 0)
1175
+ s = 0;
1176
+ else
1177
+ s = c / x;
1178
+ if (s !== 0) {
1179
+ if (r === x) {
1180
+ h = (g - b) / c;
1181
+ }
1182
+ else {
1183
+ if (g === x) {
1184
+ h = 2 + (b - r) / c;
1185
+ }
1186
+ else {
1187
+ if (b === x) {
1188
+ h = 4 + (r - g) / c;
1189
+ }
1190
+ }
1191
+ }
1192
+ h = h * 60;
1193
+ if (h < 0)
1194
+ h = h + 360;
1195
+ }
1196
+ return [h, s, v];
1197
+ }
1198
+ /**
1199
+ * HSV from each of the RGB values to determine a return as a number[].
1200
+ * RGB values are as follows.
1201
+ * R - a number from 0 to 255
1202
+ * G - a number from 0 to 255
1203
+ * B - a number from 0 to 255
1204
+ *
1205
+ * HSV values are as follows.
1206
+ * H - a number between 360-0
1207
+ * S - number between 0 and 1.0
1208
+ * V - number between 0 and 1.0
1209
+ *
1210
+ * H is replaced with equivalent numbers in the range of the 360-0 that is out of range.
1211
+ * Cannot compute, including alpha.
1212
+ *
1213
+ * @ Param h hue (Hue) number that indicates (to 360-0)
1214
+ * @ Param s the saturation (Saturation) shows the number (0.0 to 1.0)
1215
+ * @ Param v lightness (Value) indicates the number (0.0 to 1.0)
1216
+ * @ Return RGB values into a number[] of [R, G, B]
1217
+ **/
1218
+ function HSVtoRGB(h, s, v) {
1219
+ let r = 0, g = 0, b = 0;
1220
+ let i, x, y, z;
1221
+ if (s < 0) {
1222
+ s = 0;
1223
+ }
1224
+ if (s > 1) {
1225
+ s = 1;
1226
+ }
1227
+ if (v < 0) {
1228
+ v = 0;
1229
+ }
1230
+ if (v > 1) {
1231
+ v = 1;
1232
+ }
1233
+ h = h % 360;
1234
+ if (h < 0) {
1235
+ h += 360;
1236
+ }
1237
+ h /= 60;
1238
+ i = h >> 0;
1239
+ x = v * (1 - s);
1240
+ y = v * (1 - s * (h - i));
1241
+ z = v * (1 - s * (1 - h + i));
1242
+ switch (i) {
1243
+ case 0:
1244
+ r = v;
1245
+ g = z;
1246
+ b = x;
1247
+ break;
1248
+ case 1:
1249
+ r = y;
1250
+ g = v;
1251
+ b = x;
1252
+ break;
1253
+ case 2:
1254
+ r = x;
1255
+ g = v;
1256
+ b = z;
1257
+ break;
1258
+ case 3:
1259
+ r = x;
1260
+ g = y;
1261
+ b = v;
1262
+ break;
1263
+ case 4:
1264
+ r = z;
1265
+ g = x;
1266
+ b = v;
1267
+ break;
1268
+ case 5:
1269
+ r = v;
1270
+ g = x;
1271
+ b = y;
1272
+ break;
1273
+ }
1274
+ return [(r * 255) >> 0, (g * 255) >> 0, (b * 255) >> 0];
1275
+ }
1276
+
1277
+ /** Console palette for the Unreal/Cirrus message log. */
1278
+ const COLOR_CODES = {
1279
+ TO_UNREAL: ['#ffffff', '#005912', `UNREAL <-`],
1280
+ TAP_LOG: ['#ffff00', '#1976d2', ' [TAPLOG]'],
1281
+ FROM_UNREAL: ['#ffff00', '#898989', 'UNREAL ->'],
1282
+ FROM_UNREAL_ERROR: ['#ffffff', '#ff0000', 'UNREAL ERROR ->'],
1283
+ FROM_CIRRUS: ['#ffffff', '#ef702b', 'CIRRUS ->'],
1284
+ FROM_CIRRUS_ERROR: ['#ffffff', '#ff0000', 'CIRRUS ->'],
1285
+ TO_CIRRUS: ['#ffffff', '#3c9738', 'CIRRUS <-'],
1286
+ };
1287
+
1288
+ function generateUuid() {
1289
+ return v4();
1290
+ }
1291
+
1292
+ /**
1293
+ * Truthiness with the string forms folded in: `'false'`, `'undefined'` and
1294
+ * `'null'` are falsy here, which `Boolean` would call true.
1295
+ */
1296
+ const Truthy = (value) => !!value && value !== 'false' && value !== 'undefined' && value !== 'null';
1297
+ /** The inverse of {@link Truthy}. */
1298
+ const Falsy = (value) => !Truthy(value);
1299
+
1300
+ /* eslint-disable no-console -- this file *is* the console wrapper; every call
1301
+ is gated behind `Logger.isDevMode`. */
1302
+ /**
1303
+ * Left-pads `input` to `size`, then trims to the last `size` characters — a
1304
+ * value longer than `size` is truncated, not left as-is.
1305
+ */
1306
+ function pad(input, size, symbol = '0') {
1307
+ let s = input + '';
1308
+ while (s.length < size) {
1309
+ s = symbol + s;
1310
+ }
1311
+ return s.slice(-size);
1312
+ }
1313
+ /** `UTC:HH:MM:SS.mmm` for the current instant. */
1314
+ function timeUTCToString() {
1315
+ const date = new Date();
1316
+ return `UTC:${pad(date.getUTCHours(), 2)}:${pad(date.getUTCMinutes(), 2)}:${pad(date.getUTCSeconds(), 2)}.${pad(date.getUTCMilliseconds(), 3)}`;
1317
+ }
1318
+ class Logger {
1319
+ // Guard the top-level `localStorage` access so the package stays importable
1320
+ // in non-browser contexts (Node CLI / SSR); off the browser dev mode is off.
1321
+ static { this.isDevMode = typeof localStorage !== 'undefined' &&
1322
+ Truthy(localStorage.getItem('devMode')); }
1323
+ static colored(color, background, ...args) {
1324
+ if (!Logger.isDevMode) {
1325
+ return;
1326
+ }
1327
+ const [first, ...rest] = args;
1328
+ console.log.apply(console, [
1329
+ `%c ${timeUTCToString()} ${first} `,
1330
+ `color: ${color}; background: ${background};`,
1331
+ ...rest,
1332
+ ]);
1333
+ }
1334
+ static log(...args) {
1335
+ if (!Logger.isDevMode) {
1336
+ return;
1337
+ }
1338
+ console.log.apply(console, [
1339
+ `%c ${timeUTCToString()} [ LOG ] `,
1340
+ 'color: black; background: #80ff80;',
1341
+ ...args,
1342
+ ]);
1343
+ }
1344
+ static warn(...args) {
1345
+ if (!Logger.isDevMode) {
1346
+ return;
1347
+ }
1348
+ console.warn.apply(console, [
1349
+ `%c ${timeUTCToString()} WARN `,
1350
+ 'color: black; background: #ffd500;',
1351
+ ...args,
1352
+ ]);
1353
+ }
1354
+ static info(...args) {
1355
+ if (!Logger.isDevMode) {
1356
+ return;
1357
+ }
1358
+ console.log.apply(console, [
1359
+ `%c ${timeUTCToString()} [ INFO ] `,
1360
+ 'color: white; background: blue;',
1361
+ ...args,
1362
+ ]);
1363
+ }
1364
+ static error(...args) {
1365
+ if (!Logger.isDevMode) {
1366
+ return;
1367
+ }
1368
+ console.error.apply(console, [
1369
+ `%c ${timeUTCToString()} ERROR `,
1370
+ 'color: white; background: red;',
1371
+ ...args,
1372
+ ]);
1373
+ }
1374
+ }
1375
+
1376
+ /** Clamps `value` between `min` and `max`; an inverted range yields `min`. */
1377
+ function clampf(min, max, value) {
1378
+ if (min > max) {
1379
+ return min;
1380
+ }
1381
+ return Math.min(max, Math.max(min, value));
1382
+ }
1383
+ /** Linear interpolation; `value` is clamped to the 0..1 range first. */
1384
+ function lerp(min, max, value) {
1385
+ value = clampf(0, 1, value);
1386
+ return value * (max - min) + min;
1387
+ }
1388
+ /**
1389
+ * The inverse of {@link lerp}: where `value` sits between `min` and `max`.
1390
+ *
1391
+ * Returns 1 when the range is empty, to avoid dividing by zero.
1392
+ */
1393
+ function inverseLerp(min, max, value, clampToNormal = false) {
1394
+ if (clampToNormal) {
1395
+ value = Math.min(max, value);
1396
+ value = Math.max(min, value);
1397
+ }
1398
+ const difference = max - min;
1399
+ if (difference === 0) {
1400
+ return 1;
1401
+ }
1402
+ const result = (value - min) / difference;
1403
+ if (clampToNormal) {
1404
+ return Math.min(1, Math.max(0, result));
1405
+ }
1406
+ return result;
1407
+ }
1408
+ const EPSILON = 0.01;
1409
+ /**
1410
+ * Floating-point equality within `Epsilon` (default `0.01`).
1411
+ *
1412
+ * Note the `||` fallback: passing `0` uses the default, it does not compare
1413
+ * exactly.
1414
+ */
1415
+ function fpIsASameAsB(A, B, Epsilon) {
1416
+ Epsilon = Epsilon || EPSILON;
1417
+ return Math.abs(A - B) < Epsilon;
1418
+ }
1419
+
1420
+ /**
1421
+ * A counting semaphore: `acquire()` resolves once a slot is free, `release()`
1422
+ * hands the slot to the longest-waiting caller.
1423
+ */
1424
+ class Semaphore {
1425
+ constructor(maxConcurrency) {
1426
+ this.maxConcurrency = maxConcurrency;
1427
+ this.tasks = [];
1428
+ this.currentCount = 0;
1429
+ }
1430
+ acquire() {
1431
+ return new Promise((resolve) => {
1432
+ if (this.currentCount < this.maxConcurrency) {
1433
+ this.currentCount++;
1434
+ resolve();
1435
+ }
1436
+ else {
1437
+ this.tasks.push(resolve);
1438
+ }
1439
+ });
1440
+ }
1441
+ release() {
1442
+ if (this.tasks.length > 0) {
1443
+ const nextTask = this.tasks.shift();
1444
+ if (nextTask) {
1445
+ nextTask();
1446
+ }
1447
+ }
1448
+ else if (this.currentCount > 0) {
1449
+ this.currentCount--;
1450
+ }
1451
+ }
1452
+ }
1453
+
1454
+ const tapLog = (text, ...args) => (source) => source.pipe(tap((data) => {
1455
+ Logger.colored(...COLOR_CODES.TAP_LOG, text, data, ...args);
1456
+ }));
1457
+
1458
+ // Internal only. Deliberately absent from src/public-api.ts — these are the
1459
+ // symbols this package used to pull from @3dsource/utils. Exporting them would
1460
+ // re-create the coupling this folder exists to remove.
1461
+ //
1462
+ // Import this barrel from production code ('../utils' / '../../utils'), never
1463
+ // the individual files: the specs mock the barrel as a unit.
1464
+
828
1465
  /**
829
1466
  * `'metabox'` — the default when `UNREAL_CONFIG` omits `mode` — speaks the full
830
1467
  * Metabox command protocol. `'default'` targets a stock Pixel Streaming
@@ -953,10 +1590,10 @@ class WebrtcErrorModalComponent {
953
1590
  close() {
954
1591
  this.#dialogRef.close();
955
1592
  }
956
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
957
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.2", type: WebrtcErrorModalComponent, isStandalone: true, selector: "app-webrtc-error-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"unreal-modal\">\n <header class=\"unreal-modal__header\"></header>\n <section class=\"unreal-modal__body\">\n <div [attr.data-testid]=\"'title'\" class=\"unreal-modal__title\">\n WebRTC error\n </div>\n <div style=\"text-align: left\">\n An internet connection type (WebRTC) appears to be blocked either by your\n browser settings or your current network. If WebRTC is blocked on your\n browser you may be able to adjust these settings yourself based on\n instructions here:\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\n >https://myownconference.com/blog/en/webrtc/</a\n >\n <br /><br />\n Trying a different web browser may help confirm this as well. If WebRTC is\n blocked by your network, try switching to a different network if possible\n or contact your network administrator.<br /><br />\n WebRTC is common, safe and increasingly utilised method for streaming real\n time 3D experiences via a web browser. It typically consumes no more\n bandwidth than streaming an HD video.\n </div>\n </section>\n <footer class=\"unreal-modal__footer\">\n <app-unreal-button\n (clicked)=\"closeModalWithCirrusDisconnect()\"\n [data-testid]=\"'close-webrtc-error-modal'\"\n weight=\"primary\"\n >\n Ok\n </app-unreal-button>\n </footer>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__body{width:100%}.unreal-modal .unreal-modal__body p{color:var(--src-text-body-main, #1f2937);font-size:14px;font-style:normal;font-weight:400;line-height:24px;margin-top:0}.unreal-modal .unreal-modal__body a{word-wrap:break-word;white-space:normal}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1593
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1594
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.3", type: WebrtcErrorModalComponent, isStandalone: true, selector: "app-webrtc-error-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"unreal-modal\">\n <header class=\"unreal-modal__header\"></header>\n <section class=\"unreal-modal__body\">\n <div [attr.data-testid]=\"'title'\" class=\"unreal-modal__title\">\n WebRTC error\n </div>\n <div style=\"text-align: left\">\n An internet connection type (WebRTC) appears to be blocked either by your\n browser settings or your current network. If WebRTC is blocked on your\n browser you may be able to adjust these settings yourself based on\n instructions here:\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\n >https://myownconference.com/blog/en/webrtc/</a\n >\n <br /><br />\n Trying a different web browser may help confirm this as well. If WebRTC is\n blocked by your network, try switching to a different network if possible\n or contact your network administrator.<br /><br />\n WebRTC is common, safe and increasingly utilised method for streaming real\n time 3D experiences via a web browser. It typically consumes no more\n bandwidth than streaming an HD video.\n </div>\n </section>\n <footer class=\"unreal-modal__footer\">\n <app-unreal-button\n (clicked)=\"closeModalWithCirrusDisconnect()\"\n [data-testid]=\"'close-webrtc-error-modal'\"\n weight=\"primary\"\n >\n Ok\n </app-unreal-button>\n </footer>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__body{width:100%}.unreal-modal .unreal-modal__body p{color:var(--src-text-body-main, #1f2937);font-size:14px;font-style:normal;font-weight:400;line-height:24px;margin-top:0}.unreal-modal .unreal-modal__body a{word-wrap:break-word;white-space:normal}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
958
1595
  }
959
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebrtcErrorModalComponent, decorators: [{
1596
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: WebrtcErrorModalComponent, decorators: [{
960
1597
  type: Component,
961
1598
  args: [{ selector: 'app-webrtc-error-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [UnrealButtonComponent], template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"unreal-modal\">\n <header class=\"unreal-modal__header\"></header>\n <section class=\"unreal-modal__body\">\n <div [attr.data-testid]=\"'title'\" class=\"unreal-modal__title\">\n WebRTC error\n </div>\n <div style=\"text-align: left\">\n An internet connection type (WebRTC) appears to be blocked either by your\n browser settings or your current network. If WebRTC is blocked on your\n browser you may be able to adjust these settings yourself based on\n instructions here:\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\n >https://myownconference.com/blog/en/webrtc/</a\n >\n <br /><br />\n Trying a different web browser may help confirm this as well. If WebRTC is\n blocked by your network, try switching to a different network if possible\n or contact your network administrator.<br /><br />\n WebRTC is common, safe and increasingly utilised method for streaming real\n time 3D experiences via a web browser. It typically consumes no more\n bandwidth than streaming an HD video.\n </div>\n </section>\n <footer class=\"unreal-modal__footer\">\n <app-unreal-button\n (clicked)=\"closeModalWithCirrusDisconnect()\"\n [data-testid]=\"'close-webrtc-error-modal'\"\n weight=\"primary\"\n >\n Ok\n </app-unreal-button>\n </footer>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__body{width:100%}.unreal-modal .unreal-modal__body p{color:var(--src-text-body-main, #1f2937);font-size:14px;font-style:normal;font-weight:400;line-height:24px;margin-top:0}.unreal-modal .unreal-modal__body a{word-wrap:break-word;white-space:normal}\n"] }]
962
1599
  }] });
@@ -1179,10 +1816,10 @@ class CommandTelemetryService extends SubService {
1179
1816
  this.trackTime(out);
1180
1817
  funcToDecorate(out);
1181
1818
  }
1182
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1183
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandTelemetryService, providedIn: 'root' }); }
1819
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1820
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CommandTelemetryService, providedIn: 'root' }); }
1184
1821
  }
1185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandTelemetryService, decorators: [{
1822
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: CommandTelemetryService, decorators: [{
1186
1823
  type: Injectable,
1187
1824
  args: [{ providedIn: 'root' }]
1188
1825
  }], ctorParameters: () => [] });
@@ -1203,10 +1840,10 @@ class DevModeService {
1203
1840
  localStorage.removeItem('devMode');
1204
1841
  }
1205
1842
  }
1206
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1207
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DevModeService, providedIn: 'root' }); }
1843
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1844
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DevModeService, providedIn: 'root' }); }
1208
1845
  }
1209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DevModeService, decorators: [{
1846
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: DevModeService, decorators: [{
1210
1847
  type: Injectable,
1211
1848
  args: [{
1212
1849
  providedIn: 'root',
@@ -1525,10 +2162,10 @@ class RegionsPingService {
1525
2162
  }
1526
2163
  }
1527
2164
  }
1528
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1529
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RegionsPingService, providedIn: 'root' }); }
2165
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2166
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RegionsPingService, providedIn: 'root' }); }
1530
2167
  }
1531
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RegionsPingService, decorators: [{
2168
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: RegionsPingService, decorators: [{
1532
2169
  type: Injectable,
1533
2170
  args: [{ providedIn: 'root' }]
1534
2171
  }], ctorParameters: () => [] });
@@ -1571,101 +2208,25 @@ function withStreamRequestId(wsUrl, streamRequestId) {
1571
2208
  }
1572
2209
 
1573
2210
  /**
1574
- * Factory that creates a WebSocket connection and wraps it in an RxJS Observable.
1575
- *
1576
- * Returns a **single-value Observable**: it emits the open `WebSocket` instance
1577
- * once the connection is established, then completes. This design allows
1578
- * downstream operators (e.g. `tap` in `connectToCirrus`) to attach their own
1579
- * event handlers before any messages arrive.
1580
- *
1581
- * ## Ownership model
1582
- *
1583
- * The factory **transfers ownership** of the WebSocket to the consumer upon
1584
- * emission. Once `observer.next(ws)` fires, the consumer is responsible for
1585
- * the socket's lifecycle (attaching handlers, closing when done). The teardown
1586
- * logic will **never** close a socket that has been successfully delivered.
1587
- *
1588
- * ## Timeout
1589
- *
1590
- * The observable is piped through `timeout({ first: WS_TIMEOUT })`. If the
1591
- * WebSocket `open` event does not fire within `WS_TIMEOUT` milliseconds, the
1592
- * stream errors with a timeout message and the teardown disposes the socket.
1593
- *
1594
- * ## Teardown & leak prevention
1595
- *
1596
- * When the observable is unsubscribed (timeout, manual unsubscribe, or error),
1597
- * the teardown function runs and handles two cases:
1598
- *
1599
- * 1. **Socket was emitted (`emitted === true`):** The consumer owns the socket.
1600
- * Teardown only removes the internal event listeners — it does NOT close
1601
- * the socket. Closing it here would break downstream consumers like
1602
- * `connectToCirrus` that rely on the connection staying alive.
1603
- *
1604
- * 2. **Socket was NOT emitted (`emitted === false`):** The socket is orphaned
1605
- * (nobody has a reference to it). Teardown must close it to prevent a leak.
1606
- * However, calling `ws.close()` on a `CONNECTING` socket does not reliably
1607
- * abort the underlying HTTP upgrade request in all browsers — the TCP
1608
- * handshake may still complete asynchronously. To handle this edge case,
1609
- * teardown replaces the `open` listener with a one-shot handler that calls
1610
- * `ws.close()` if the connection succeeds after teardown has already run.
1611
- * This ensures the orphaned socket is closed regardless of timing:
1612
- *
1613
- * - If `close()` aborts the connection immediately — done.
1614
- * - If the browser lets the handshake complete anyway — the `open` handler
1615
- * fires and closes the now-connected socket, so it doesn't linger in
1616
- * the browser's network tab as a "Pending" request.
1617
- *
1618
- * ## Why `defer`?
1619
- *
1620
- * The outer `defer()` ensures a **new WebSocket** is created on each
1621
- * subscription. Without it, retry operators would reuse the same (possibly
1622
- * closed) socket instance. With `defer`, each retry gets a fresh connection.
1623
- *
1624
- * ## SSR guard
1625
- *
1626
- * If `window` or `WebSocket` is not available (server-side rendering),
1627
- * the function returns an error observable immediately without attempting
1628
- * a connection.
1629
- *
1630
- * ## Lifecycle flags
1631
- *
1632
- * - `opened` — set to `true` when the `open` event fires. Guards the `close`
1633
- * handler so it only errors the observable if the socket was closed *before*
1634
- * the connection was established (e.g. server rejected the upgrade).
1635
- *
1636
- * - `emitted` — set to `true` right before `observer.next(ws)`. Tells the
1637
- * teardown function whether the socket was delivered to the consumer.
1638
- * This is the flag that controls whether teardown closes the socket or
1639
- * leaves it for the consumer.
2211
+ * Creates a WebSocket and wraps it in a single-value Observable: emits the open
2212
+ * socket, then completes, so a downstream `tap` can attach handlers before any
2213
+ * message arrives.
1640
2214
  *
1641
- * ## Abort
2215
+ * Ownership transfers to the consumer on emission — teardown never closes a
2216
+ * socket it already delivered, because `connectToCirrus` needs that connection
2217
+ * to stay alive.
1642
2218
  *
1643
- * The factory does **not** accept an abort signal — cancellation is handled
1644
- * by the caller via standard RxJS unsubscription. When the caller's
1645
- * `takeUntil` (or manual `unsubscribe`) fires, the observable tears down
1646
- * and the orphaned socket is closed. For retry loops, place `takeUntil`
1647
- * at two levels:
2219
+ * An *undelivered* socket is orphaned and teardown must close it. `ws.close()`
2220
+ * on a `CONNECTING` socket does not reliably abort the HTTP upgrade in every
2221
+ * browser, so teardown also swaps in a one-shot `open` handler that closes the
2222
+ * socket if the handshake completes anyway.
1648
2223
  *
1649
- * 1. **Inside the per-attempt chain** (e.g. in `connectToCirrus`) — cancels
1650
- * the current in-flight WebSocket attempt and triggers teardown.
1651
- * 2. **Outside `retry`** (e.g. in `connectToSignaling`) — kills the entire
1652
- * retry loop so no further re-subscriptions occur.
2224
+ * The outer `defer` gives every subscription a fresh socket, so `retry` does not
2225
+ * reuse a closed one.
1653
2226
  *
1654
- * ## Usage
1655
- *
1656
- * ```typescript
1657
- * // In connectToCirrus — takeUntil cancels the current attempt:
1658
- * createWebSocket(wsUrl).pipe(
1659
- * takeUntil(abort$),
1660
- * tap(ws => addWsHandlers(ws)),
1661
- * );
1662
- *
1663
- * // In connectToSignaling — takeUntil outside retry kills the loop:
1664
- * defer(() => innerChain$).pipe(
1665
- * retry({ delay: ... }),
1666
- * takeUntil(abort$),
1667
- * );
1668
- * ```
2227
+ * There is no abort signal: cancellation is the caller's `takeUntil`, and it has
2228
+ * to sit at two levels — inside the per-attempt chain to cancel the in-flight
2229
+ * socket, and outside `retry` to stop the loop re-subscribing.
1669
2230
  *
1670
2231
  * @param wsUrl - The `ws://` or `wss://` URL to connect to.
1671
2232
  * @returns Observable that emits a single open WebSocket, then completes.
@@ -1673,17 +2234,18 @@ function withStreamRequestId(wsUrl, streamRequestId) {
1673
2234
  * does not support WebSocket.
1674
2235
  */
1675
2236
  function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1676
- // --- SSR guard: bail immediately if WebSocket API is unavailable ---
2237
+ // SSR guard.
1677
2238
  if (typeof window === 'undefined' || !('WebSocket' in window)) {
1678
2239
  return throwError(() => new Error("Your browser doesn't support WebSocket"));
1679
2240
  }
1680
2241
  Logger.info('Creating socket', wsUrl);
1681
2242
  return defer(() => new Observable((observer) => {
1682
2243
  const ws = new WebSocket(wsUrl);
1683
- // Lifecycle flags — see JSDoc above for detailed explanation.
2244
+ // `opened` guards `onClose` so it only errors on a close *before* the
2245
+ // connection was established; `emitted` tells teardown whether the
2246
+ // consumer already owns the socket.
1684
2247
  let opened = false;
1685
2248
  let emitted = false;
1686
- // --- Event handlers (attached during connection, removed in teardown) ---
1687
2249
  const onOpen = () => {
1688
2250
  opened = true;
1689
2251
  emitted = true;
@@ -1704,23 +2266,20 @@ function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1704
2266
  ws.addEventListener('open', onOpen);
1705
2267
  ws.addEventListener('error', onError);
1706
2268
  ws.addEventListener('close', onClose);
1707
- // --- Teardown: runs on unsubscribe, timeout, error, or complete ---
2269
+ // Teardown: unsubscribe, timeout, error, or complete.
1708
2270
  return () => {
1709
2271
  ws.removeEventListener('error', onError);
1710
2272
  ws.removeEventListener('close', onClose);
1711
2273
  if (!emitted &&
1712
2274
  (ws.readyState === WebSocket.CONNECTING ||
1713
2275
  ws.readyState === WebSocket.OPEN)) {
1714
- // Socket was never delivered — it's orphaned. Must close it.
1715
- // Replace the open listener with a cleanup handler: if the
1716
- // browser completes the handshake after this teardown, the
1717
- // handler will close the orphaned socket immediately.
2276
+ // Orphaned: close it, and cover the case where the browser
2277
+ // completes the handshake after this teardown anyway.
1718
2278
  ws.removeEventListener('open', onOpen);
1719
2279
  ws.addEventListener('open', () => ws.close());
1720
- // Some strict implementations (including `ws`) emit an `error`
1721
- // when a CONNECTING socket is closed. The observable is already
1722
- // torn down, so consume that expected lifecycle error and remove
1723
- // the temporary handlers once the socket closes.
2280
+ // Some implementations (including `ws`) emit `error` when a
2281
+ // CONNECTING socket is closed. The observable is already torn
2282
+ // down, so swallow it and drop the handlers on close.
1724
2283
  const onOrphanError = () => undefined;
1725
2284
  const onOrphanClose = () => {
1726
2285
  ws.removeEventListener('error', onOrphanError);
@@ -1731,8 +2290,7 @@ function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1731
2290
  ws.close();
1732
2291
  }
1733
2292
  else {
1734
- // Socket was delivered to consumer — they own it now.
1735
- // Just remove our internal listener; don't touch the socket.
2293
+ // Delivered — the consumer owns it; only drop our listener.
1736
2294
  ws.removeEventListener('open', onOpen);
1737
2295
  }
1738
2296
  };
@@ -2366,10 +2924,10 @@ class SignallingService extends SubService {
2366
2924
  Logger.log('Start Getting Region');
2367
2925
  return this.regionsPingService.getFastest().pipe(take(1));
2368
2926
  }
2369
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2370
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SignallingService, providedIn: 'root' }); }
2927
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2928
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SignallingService, providedIn: 'root' }); }
2371
2929
  }
2372
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SignallingService, decorators: [{
2930
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SignallingService, decorators: [{
2373
2931
  type: Injectable,
2374
2932
  args: [{ providedIn: 'root' }]
2375
2933
  }], ctorParameters: () => [] });
@@ -2784,10 +3342,10 @@ class VideoService extends SubService {
2784
3342
  this.armGestureRetry(audio);
2785
3343
  }
2786
3344
  }
2787
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2788
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoService, providedIn: 'root' }); }
3345
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3346
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: VideoService, providedIn: 'root' }); }
2789
3347
  }
2790
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoService, decorators: [{
3348
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: VideoService, decorators: [{
2791
3349
  type: Injectable,
2792
3350
  args: [{ providedIn: 'root' }]
2793
3351
  }], ctorParameters: () => [] });
@@ -3453,10 +4011,10 @@ class WebRtcPlayerService extends SubService {
3453
4011
  Logger.error("Couldn't create answer", e);
3454
4012
  }
3455
4013
  }
3456
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3457
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebRtcPlayerService, providedIn: 'root' }); }
4014
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4015
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: WebRtcPlayerService, providedIn: 'root' }); }
3458
4016
  }
3459
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebRtcPlayerService, decorators: [{
4017
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: WebRtcPlayerService, decorators: [{
3460
4018
  type: Injectable,
3461
4019
  args: [{ providedIn: 'root' }]
3462
4020
  }], ctorParameters: () => [] });
@@ -3626,10 +4184,10 @@ class StreamAdoptionService {
3626
4184
  this.webRtc.adopt(parked.pc, parked.dc, parked.stream, parked.config ?? {}, parked.dcBuffer);
3627
4185
  return true;
3628
4186
  }
3629
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamAdoptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3630
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamAdoptionService, providedIn: 'root' }); }
4187
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamAdoptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4188
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamAdoptionService, providedIn: 'root' }); }
3631
4189
  }
3632
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamAdoptionService, decorators: [{
4190
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamAdoptionService, decorators: [{
3633
4191
  type: Injectable,
3634
4192
  args: [{ providedIn: 'root' }]
3635
4193
  }] });
@@ -3697,10 +4255,10 @@ class UnrealCallbackService {
3697
4255
  return observable;
3698
4256
  });
3699
4257
  }
3700
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3701
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCallbackService, providedIn: 'root' }); }
4258
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4259
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCallbackService, providedIn: 'root' }); }
3702
4260
  }
3703
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCallbackService, decorators: [{
4261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCallbackService, decorators: [{
3704
4262
  type: Injectable,
3705
4263
  args: [{ providedIn: 'root' }]
3706
4264
  }] });
@@ -3817,10 +4375,10 @@ class UnrealCommunicatorService extends SubService {
3817
4375
  requestQualityControl() {
3818
4376
  this.sendInputData(new Uint8Array([MessageType.RequestQualityControl]).buffer);
3819
4377
  }
3820
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3821
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCommunicatorService, providedIn: 'root' }); }
4378
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4379
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCommunicatorService, providedIn: 'root' }); }
3822
4380
  }
3823
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCommunicatorService, decorators: [{
4381
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealCommunicatorService, decorators: [{
3824
4382
  type: Injectable,
3825
4383
  args: [{ providedIn: 'root' }]
3826
4384
  }], ctorParameters: () => [] });
@@ -3834,10 +4392,10 @@ class UnrealErrorModalComponent {
3834
4392
  close() {
3835
4393
  this.#dialogRef.close();
3836
4394
  }
3837
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3838
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.2", type: UnrealErrorModalComponent, isStandalone: true, selector: "app-unreal-error-modal", ngImport: i0, template: "<div class=\"unreal-modal\">\n <header class=\"unreal-modal__header\">\n <div\n [attr.data-testid]=\"'unreal-error-header-title'\"\n class=\"unreal-modal__title\"\n >\n Warning\n </div>\n </header>\n <div class=\"unreal-modal__body\">\n <div>{{ dialogData.content }}</div>\n </div>\n <div class=\"unreal-modal__footer\">\n <app-unreal-button (clicked)=\"close()\">Ok</app-unreal-button>\n </div>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__header{padding:16px 24px 0}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4395
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4396
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.3", type: UnrealErrorModalComponent, isStandalone: true, selector: "app-unreal-error-modal", ngImport: i0, template: "<div class=\"unreal-modal\">\n <header class=\"unreal-modal__header\">\n <div\n [attr.data-testid]=\"'unreal-error-header-title'\"\n class=\"unreal-modal__title\"\n >\n Warning\n </div>\n </header>\n <div class=\"unreal-modal__body\">\n <div>{{ dialogData.content }}</div>\n </div>\n <div class=\"unreal-modal__footer\">\n <app-unreal-button (clicked)=\"close()\">Ok</app-unreal-button>\n </div>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__header{padding:16px 24px 0}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3839
4397
  }
3840
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealErrorModalComponent, decorators: [{
4398
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealErrorModalComponent, decorators: [{
3841
4399
  type: Component,
3842
4400
  args: [{ selector: 'app-unreal-error-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [UnrealButtonComponent], template: "<div class=\"unreal-modal\">\n <header class=\"unreal-modal__header\">\n <div\n [attr.data-testid]=\"'unreal-error-header-title'\"\n class=\"unreal-modal__title\"\n >\n Warning\n </div>\n </header>\n <div class=\"unreal-modal__body\">\n <div>{{ dialogData.content }}</div>\n </div>\n <div class=\"unreal-modal__footer\">\n <app-unreal-button (clicked)=\"close()\">Ok</app-unreal-button>\n </div>\n</div>\n", styles: [".unreal-modal{--unrealModalWidth: 422px;--unrealModalMaxWidth: calc(100% - 16px) ;--unrealModalMaxHeight: 80vh;--unrealModalBg: var(--src-surface-container-main, #fff);--unrealModalBoxShadow: 0 16px 36px 4px var(--src-shadow-hard, rgba(71, 85, 105, .24)), 0 1px 4px 0 var(--src-shadow-light, rgba(148, 163, 184, .16));--unrealModalBorderRadius: var(--src-layout-radius-const-rounded-md, 12px);--unrealModalTitleSize: var(--src-font-size-base, 16px);--unrealModalTitleLineHeight: var(--src-font-line-base, 24px);--unrealModalTitleColor: var(--src-text-body-main, #111827);--unrealModalBodyPadding: var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-2xl, 32px) var(--src-layout-padding-const-xl, 24px);--unrealModalTitleBorder: none;--unrealModalFooterBorder: 1px solid var(--src-border-container-light, rgba(148, 163, 184, .16));display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;width:var(--unrealModalWidth);max-width:var(--unrealModalMaxWidth);max-height:var(--unrealModalMaxHeight);margin:auto;background-color:var(--unrealModalBg);border-radius:var(--unrealModalBorderRadius);box-shadow:var(--unrealModalBoxShadow);color:var(--src-text-body-secondary, #4b5563);font-family:var(--src-font-family-header, Inter);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:var(--src-font-line-sm, 20px)}.unreal-modal .unreal-modal__header{position:relative;display:flex;padding:0;width:100%;border-bottom:var(--unrealModalTitleBorder)}.unreal-modal .unreal-modal__body{display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);gap:var(--src-layout-gap-const-md, 12px);padding:var(--unrealModalBodyPadding);overflow:auto;scrollbar-width:thin;scrollbar-color:var(--src-border-container-basic, rgba(148, 163, 184, .24)) transparent}.unreal-modal .unreal-modal__body .unreal-modal__title{grid-column:1/-1}.unreal-modal .unreal-modal__body::-webkit-scrollbar{width:4px}.unreal-modal .unreal-modal__body::-webkit-scrollbar-track{background:transparent}.unreal-modal .unreal-modal__body::-webkit-scrollbar-thumb{background-color:var(--src-border-container-basic, rgba(148, 163, 184, .24));border-radius:10px}.unreal-modal .unreal-modal__footer{display:flex;justify-content:flex-end;align-items:center;padding:var(--src-layout-padding-const-md, 12px) var(--src-layout-padding-const-xl, 24px) var(--src-layout-padding-const-lg, 16px) var(--src-layout-padding-const-xl, 24px);width:100%;border-top:var(--unrealModalFooterBorder);gap:var(--src-layout-gap-const-sm, 8px)}.unreal-modal .unreal-modal__title{font-size:var(--unrealModalTitleSize);font-weight:600;line-height:var(--unrealModalTitleLineHeight);color:var(--unrealModalTitleColor);word-break:break-word}.unreal-modal .unreal-modal__header{padding:16px 24px 0}\n"] }]
3843
4401
  }] });
@@ -4233,10 +4791,10 @@ class UnrealEffects {
4233
4791
  // Fails silently if cross-origin
4234
4792
  }
4235
4793
  }
4236
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4237
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealEffects }); }
4794
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4795
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealEffects }); }
4238
4796
  }
4239
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealEffects, decorators: [{
4797
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealEffects, decorators: [{
4240
4798
  type: Injectable
4241
4799
  }] });
4242
4800
 
@@ -4388,10 +4946,9 @@ const floatToSmoothPercents = () => (source) => source.pipe(timestamp(), startWi
4388
4946
  * Smoothly interpolates from `start` to `end` over `duration`ms using the
4389
4947
  * animation frame scheduler. Completes when the transition finishes.
4390
4948
  *
4391
- * NOTE: duplicates `smoothTransition` from `@3dsource/utils`. The utils version
4392
- * is missing `takeWhile(... < 1, true)` and never completes — which would
4393
- * deadlock the `concatMap` above. Keep this local copy until utils is fixed.
4394
- * Not exported: internal to `floatToSmoothPercents`.
4949
+ * Deliberately local. The version this was forked from never completed — it
4950
+ * lacked `takeWhile(... < 1, true)` — which would deadlock the `concatMap`
4951
+ * above. Not exported: internal to `floatToSmoothPercents`.
4395
4952
  */
4396
4953
  const smoothTransition = (start, end, duration) => {
4397
4954
  const startTime = animationFrameScheduler.now();
@@ -4753,10 +5310,10 @@ class AFKService extends SubService {
4753
5310
  }
4754
5311
  }, 1000);
4755
5312
  }
4756
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4757
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AFKService, providedIn: 'root' }); }
5313
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5314
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AFKService, providedIn: 'root' }); }
4758
5315
  }
4759
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AFKService, decorators: [{
5316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AFKService, decorators: [{
4760
5317
  type: Injectable,
4761
5318
  args: [{ providedIn: 'root' }]
4762
5319
  }], ctorParameters: () => [] });
@@ -4806,10 +5363,10 @@ class LoadingProgressService {
4806
5363
  // `switchMap` cancels the inner subscription on every visibility change
4807
5364
  // so stale emissions can't leak through.
4808
5365
  #boundedProgress$;
4809
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4810
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LoadingProgressService, providedIn: 'root' }); }
5366
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5367
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: LoadingProgressService, providedIn: 'root' }); }
4811
5368
  }
4812
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LoadingProgressService, decorators: [{
5369
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: LoadingProgressService, decorators: [{
4813
5370
  type: Injectable,
4814
5371
  args: [{ providedIn: 'root' }]
4815
5372
  }] });
@@ -4895,10 +5452,10 @@ class FreezeFrameService extends SubService {
4895
5452
  progress: 1,
4896
5453
  }));
4897
5454
  }
4898
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4899
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameService, providedIn: 'root' }); }
5455
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5456
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FreezeFrameService, providedIn: 'root' }); }
4900
5457
  }
4901
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameService, decorators: [{
5458
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FreezeFrameService, decorators: [{
4902
5459
  type: Injectable,
4903
5460
  args: [{ providedIn: 'root' }]
4904
5461
  }], ctorParameters: () => [] });
@@ -5050,19 +5607,21 @@ class FileReceiverService extends SubService {
5050
5607
  this.reset();
5051
5608
  }
5052
5609
  }
5053
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileReceiverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5054
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileReceiverService, providedIn: 'root' }); }
5610
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileReceiverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5611
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileReceiverService, providedIn: 'root' }); }
5055
5612
  }
5056
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileReceiverService, decorators: [{
5613
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileReceiverService, decorators: [{
5057
5614
  type: Injectable,
5058
5615
  args: [{ providedIn: 'root' }]
5059
5616
  }], ctorParameters: () => [] });
5060
5617
 
5061
- const filteredLogs = [
5062
- {
5063
- command: 'onChangeSequence',
5064
- },
5065
- ];
5618
+ /**
5619
+ * Matches the normalisation the old `where()` predicate applied to both sides:
5620
+ * trim, collapse space runs to `_`, lowercase.
5621
+ */
5622
+ const normalizeCommand = (value) => String(value).trim().replace(/ +/gi, '_').toLowerCase();
5623
+ /** Commands whose arrival is not worth a console line. */
5624
+ const FILTERED_LOG_COMMANDS = new Set(['onChangeSequence'].map(normalizeCommand));
5066
5625
  class AggregatorService extends SubService {
5067
5626
  constructor() {
5068
5627
  super();
@@ -5149,7 +5708,11 @@ class AggregatorService extends SubService {
5149
5708
  });
5150
5709
  }
5151
5710
  showMessageFromUnreal(json) {
5152
- if (filteredLogs.some(where({ command: json?.command }))) {
5711
+ // The `undefined` check is redundant while the set holds only real command
5712
+ // names, but `where()` ignored undefined criteria outright — keep it so an
5713
+ // entry literally named 'undefined' could never start matching.
5714
+ if (json?.command !== undefined &&
5715
+ FILTERED_LOG_COMMANDS.has(normalizeCommand(json.command))) {
5153
5716
  return;
5154
5717
  }
5155
5718
  Logger.colored(...COLOR_CODES.FROM_UNREAL, json);
@@ -5232,10 +5795,10 @@ class AggregatorService extends SubService {
5232
5795
  Logger.warn(`unrecognized data received, packet ID ${view[0]}`);
5233
5796
  }
5234
5797
  }
5235
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5236
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AggregatorService, providedIn: 'root' }); }
5798
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5799
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AggregatorService, providedIn: 'root' }); }
5237
5800
  }
5238
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AggregatorService, decorators: [{
5801
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AggregatorService, decorators: [{
5239
5802
  type: Injectable,
5240
5803
  args: [{ providedIn: 'root' }]
5241
5804
  }], ctorParameters: () => [] });
@@ -5396,10 +5959,10 @@ class ConsoleExtensionsService extends SubService {
5396
5959
  Logger.info('emitCommand(command) => send command to Unreal Engine.');
5397
5960
  Logger.info('stressTest({iterations}) => hammer reconnect: repeatedly drop DC/PC and assert recovery.');
5398
5961
  }
5399
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5400
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ConsoleExtensionsService, providedIn: 'root' }); }
5962
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5963
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ConsoleExtensionsService, providedIn: 'root' }); }
5401
5964
  }
5402
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ConsoleExtensionsService, decorators: [{
5965
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ConsoleExtensionsService, decorators: [{
5403
5966
  type: Injectable,
5404
5967
  args: [{ providedIn: 'root' }]
5405
5968
  }], ctorParameters: () => [] });
@@ -5413,30 +5976,48 @@ const RESIZE_DEBOUNCE_MS = 300;
5413
5976
  const FUNCTION_KEY_MIN = 112;
5414
5977
  const FUNCTION_KEY_MAX = 123;
5415
5978
  const TAB_KEY_CODE = 9;
5979
+ // VK_LEFT..VK_DOWN. Held down, these scroll the page while they steer the
5980
+ // camera, so they belong with the keys `suppressBrowserKeys` neutralises.
5981
+ const ARROW_KEY_MIN = 37;
5982
+ const ARROW_KEY_MAX = 40;
5983
+ /** Elements that own the keyboard while focused — never forward to the stream. */
5984
+ const TEXT_ENTRY_SELECTOR = 'input, textarea, select, [contenteditable]:not([contenteditable="false"])';
5416
5985
  class InputService extends SubService {
5417
5986
  constructor() {
5418
5987
  super();
5419
5988
  this.commandsSender = inject(UnrealCommunicatorService);
5420
- this.isDevMode = inject(DevModeService).isDevMode;
5989
+ // The service, not its value: `isDevMode` is a getter over localStorage, and
5990
+ // `setDevMode()` is documented as callable from the console at runtime.
5991
+ this.devModeService = inject(DevModeService);
5421
5992
  this.videoService = inject(VideoService);
5422
5993
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
5994
+ // Physical key names (`KeyboardEvent.code`), which is what the allow-list is
5995
+ // matched against. The numpad digits are deliberately absent: with NumLock
5996
+ // off they *are* the arrows, but the browser reports them under their own
5997
+ // `code`, so enabling them here would forward a second set of arrow keys.
5998
+ // A consumer that wants them passes them to `useKeyboardKeys`.
5423
5999
  this.defaultKeys = [
5424
- KeyboardNumericCode.KeyW,
5425
- KeyboardNumericCode.KeyS,
5426
- KeyboardNumericCode.KeyA,
5427
- KeyboardNumericCode.KeyD,
5428
- KeyboardNumericCode.ArrowUp,
5429
- KeyboardNumericCode.ArrowDown,
5430
- KeyboardNumericCode.ArrowLeft,
5431
- KeyboardNumericCode.ArrowRight,
5432
- KeyboardNumericCode.NumpadAdd,
5433
- KeyboardNumericCode.NumpadSubtract,
5434
- KeyboardNumericCode.ControlRight,
5435
- KeyboardNumericCode.Equal,
5436
- KeyboardNumericCode.Minus,
5437
- KeyboardNumericCode.NumpadDecimal,
6000
+ 'KeyW',
6001
+ 'KeyS',
6002
+ 'KeyA',
6003
+ 'KeyD',
6004
+ 'ArrowUp',
6005
+ 'ArrowDown',
6006
+ 'ArrowLeft',
6007
+ 'ArrowRight',
6008
+ 'NumpadAdd',
6009
+ 'NumpadSubtract',
6010
+ // Both Control keys. Only `ControlRight` was ever declared here, but the
6011
+ // numeric allow-list resolved 17 to `'ControlLeft'`, so the left one is what
6012
+ // actually reached the engine — dropping it now would be a regression for
6013
+ // the more commonly pressed of the two.
6014
+ 'ControlLeft',
6015
+ 'ControlRight',
6016
+ 'Equal',
6017
+ 'Minus',
6018
+ 'NumpadDecimal',
5438
6019
  ];
5439
- this.availableKeys = this.defaultKeys;
6020
+ this.availableKeys = new Set(this.defaultKeys);
5440
6021
  this.options = InputOptions;
5441
6022
  this.reInit$ = new Subject();
5442
6023
  this.deactivatedVideoTrigger$ = NEVER;
@@ -5453,6 +6034,12 @@ class InputService extends SubService {
5453
6034
  this.fingerIds = new Map();
5454
6035
  this.maxByteValue = 255;
5455
6036
  this.fingers = [...INITIAL_FINGER_IDS];
6037
+ // Virtual-key codes for which a KeyDown has been sent and no KeyUp yet. This
6038
+ // is the sole gate on KeyUp, which makes the pairing an invariant instead of
6039
+ // something the filters have to agree on twice: a key the engine was never
6040
+ // told about cannot be released, and a key it was told about is always
6041
+ // released — including when the browser never delivers the `keyup` at all.
6042
+ this.heldKeys = new Set();
5456
6043
  // Reusable wire buffers. Input events fire at up to ~60 Hz; the data channel
5457
6044
  // copies the bytes synchronously on sending (RTCDataChannel.send), so a single
5458
6045
  // buffer per message shape is safe and avoids per-event allocation/GC churn.
@@ -5485,8 +6072,17 @@ class InputService extends SubService {
5485
6072
  .pipe(filter(() => !!this.video), takeUntil$1(this.destroy$))
5486
6073
  .subscribe(() => this.setup());
5487
6074
  }
6075
+ /**
6076
+ * Replaces the forwarded-key allow-list. Takes physical key names
6077
+ * (`KeyboardEvent.code`) such as `'KeyW'` or `'BracketLeft'`.
6078
+ *
6079
+ * Legacy numeric `keyCode` values are still accepted and converted, but they
6080
+ * cannot express every key: `InvertedKeyMap` is many-to-one, so 37 resolves
6081
+ * to `'ArrowLeft'` and 17 to `'ControlLeft'`, and `'Numpad4'`/`'ControlRight'`
6082
+ * are unreachable through the numeric form. Pass names.
6083
+ */
5488
6084
  useKeyboardKeys(keys) {
5489
- this.availableKeys = keys;
6085
+ this.availableKeys = new Set(keys.map((key) => (typeof key === 'number' ? InvertedKeyMap[key] : key)));
5490
6086
  }
5491
6087
  get video() {
5492
6088
  return this.videoService.video;
@@ -5501,6 +6097,10 @@ class InputService extends SubService {
5501
6097
  // Reset touch state to prevent stale identifiers across sessions
5502
6098
  this.fingerIds.clear();
5503
6099
  this.fingers = [...INITIAL_FINGER_IDS];
6100
+ // Same for the key registry: a key held across a reconnect belongs to the
6101
+ // previous engine session, and releasing it into the new one is worse than
6102
+ // dropping it.
6103
+ this.heldKeys.clear();
5504
6104
  merge(fromResizeObserver(video), this.actions$.pipe(ofType(videoAdaptedToContainer)))
5505
6105
  .pipe(debounceTime$1(RESIZE_DEBOUNCE_MS), takeUntil$1(this.deactivatedVideoTrigger$))
5506
6106
  .subscribe(() => this.setupNormalizeAndQuantize(video));
@@ -5588,9 +6188,17 @@ class InputService extends SubService {
5588
6188
  fromEvent(window, 'blur')
5589
6189
  .pipe(filter(() => !!video), takeUntil$1(this.deactivatedVideoTrigger$))
5590
6190
  .subscribe(() => this.releaseMouseButtons(255, 0, 0));
6191
+ // Deliberately *not* gated on `deactivatedVideoTrigger$`: that trigger
6192
+ // includes the visibility-hidden event, so a subscription behind it is torn
6193
+ // down by the very event this one has to react to. `reInit$` alone is
6194
+ // enough to avoid stacking duplicates across `setup()` calls, since
6195
+ // `setup()` fires it before it gets here.
6196
+ merge(fromEvent(window, 'blur'), fromEvent(document, 'visibilitychange').pipe(filter(() => document.visibilityState === 'hidden')))
6197
+ .pipe(takeUntil$1(merge(this.reInit$, this.destroy$)))
6198
+ .subscribe(() => this.releaseKeys());
5591
6199
  this.registerMouseEnterAndLeaveEvents(video);
5592
6200
  this.registerHoveringMouseEvents(video);
5593
- this.registerKeyboardEvents(this.availableKeys);
6201
+ this.registerKeyboardEvents();
5594
6202
  this.registerTouchEvents(video);
5595
6203
  }
5596
6204
  getTouchPositionWithOffset({ clientX, clientY }) {
@@ -5812,62 +6420,108 @@ class InputService extends SubService {
5812
6420
  this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
5813
6421
  });
5814
6422
  }
5815
- // Browser keys do not have a charCode so we only need to test keyCode.
5816
- isKeyCodeBrowserKey(keyCode) {
6423
+ /**
6424
+ * Keys whose default browser action fights the stream: the function keys
6425
+ * (F5 reloads mid-session), Tab (moves focus out of the player) and the
6426
+ * arrows (scroll the page while they steer the camera).
6427
+ *
6428
+ * Takes the *resolved* virtual-key code rather than `event.code`, so a numpad
6429
+ * key acting as an arrow with NumLock off is caught as well.
6430
+ */
6431
+ isBrowserKey(keyCode) {
5817
6432
  return ((keyCode >= FUNCTION_KEY_MIN && keyCode <= FUNCTION_KEY_MAX) ||
6433
+ (keyCode >= ARROW_KEY_MIN && keyCode <= ARROW_KEY_MAX) ||
5818
6434
  keyCode === TAB_KEY_CODE);
5819
6435
  }
5820
6436
  /**
5821
- * The key code Unreal expects, which is the Windows virtual-key code.
6437
+ * The key code Unreal expects — the Windows virtual-key code — or `null` when
6438
+ * the event carries no key worth forwarding.
5822
6439
  *
5823
- * `e.keyCode` is not that on Gecko: Firefox reports the punctuation keys by
5824
- * their ASCII value — `=` as 61 and `-` as 173, where Chrome sends 187 and 189
5825
- * — so zoom-on-plus/minus reached the engine as codes it does not know.
5826
- * `KeyboardNumericCode` is keyed by `e.code` and holds the virtual-key value,
5827
- * so it resolves the same number on every browser; a no-op on Chrome.
6440
+ * `KeyboardNumericCode` covers only the keys whose `event.keyCode` cannot be
6441
+ * trusted; everything else, the numpad included, falls through to the value
6442
+ * the browser resolved.
5828
6443
  *
5829
- * Checked after the right-hand modifiers, which need their own distinct codes
5830
- * that the map does not carry.
6444
+ * Returns `null` for an IME composition — `event.code` still names the
6445
+ * physical key there, so the keystrokes would otherwise reach the engine as
6446
+ * camera movement — and for a `keyCode` of 0, which no engine key uses.
5831
6447
  */
5832
6448
  getKeyCode(e) {
5833
- if (e.keyCode === SpecialKeyCodes.Shift && e.code === 'ShiftRight')
5834
- return SpecialKeyCodes.RightShift;
5835
- else if (e.keyCode === SpecialKeyCodes.Control && e.code === 'ControlRight')
5836
- return SpecialKeyCodes.RightControl;
5837
- else if (e.keyCode === SpecialKeyCodes.Alt && e.code === 'AltRight')
5838
- return SpecialKeyCodes.RightAlt;
5839
- return (KeyboardNumericCode[e.code] ??
5840
- e.keyCode);
5841
- }
5842
- registerKeyboardEvents(availableKeys) {
5843
- const availableKeyNames = availableKeys.map((value) => InvertedKeyMap[value]);
5844
- const keyBlock = (event) => {
5845
- const code = event.code;
5846
- const isInputFieldFocused = event.target?.localName === 'input';
5847
- const keyMatch = availableKeyNames.includes(code);
5848
- if (this.isDevMode && !keyMatch) {
5849
- Logger.warn('Code=>', code, 'Keyboard event is not filtered in DEV Mode. App may behave inappropriately!');
5850
- return isInputFieldFocused;
5851
- }
5852
- return isInputFieldFocused || !keyMatch;
5853
- };
5854
- this.startListenKeys(keyBlock);
6449
+ if (e.keyCode === IME_PROCESS_KEY) {
6450
+ return null;
6451
+ }
6452
+ const keyCode = KeyboardNumericCode[e.code] ??
6453
+ e.keyCode;
6454
+ return keyCode || null;
6455
+ }
6456
+ /**
6457
+ * True while a focused element owns the keyboard. Read off `composedPath()`
6458
+ * rather than `event.target`: retargeting reports the host element for an
6459
+ * `<input>` inside a shadow root, so a `target`-based check never fires in an
6460
+ * app built from web components.
6461
+ */
6462
+ isTextEntryTarget(e) {
6463
+ const target = e.composedPath()[0] ?? e.target;
6464
+ return target instanceof Element && !!target.closest(TEXT_ENTRY_SELECTOR);
6465
+ }
6466
+ /** True when `event.code` is on the allow-list, or dev mode waives it. */
6467
+ isKeyAllowed(e) {
6468
+ if (this.availableKeys.has(e.code)) {
6469
+ return true;
6470
+ }
6471
+ if (!this.devModeService.isDevMode) {
6472
+ return false;
6473
+ }
6474
+ // Once per press, not once per keydown/keyup/keypress and not on autorepeat.
6475
+ if (e.type === 'keydown' && !e.repeat) {
6476
+ Logger.warn('Code=>', e.code, 'Keyboard event is not filtered in DEV Mode. App may behave inappropriately!');
6477
+ }
6478
+ return true;
6479
+ }
6480
+ sendKeyUp(keyCode) {
6481
+ const keyUp = this.keyUpBytes;
6482
+ keyUp[0] = MessageType.KeyUp;
6483
+ keyUp[1] = keyCode;
6484
+ this.sendInputData(keyUp.buffer);
5855
6485
  }
5856
- startListenKeys(_filter) {
5857
- const filter = _filter ?? (() => false);
6486
+ /**
6487
+ * Releases everything the engine still believes is held. Needed because a
6488
+ * `keyup` is not guaranteed to arrive: the window can lose focus to a browser
6489
+ * dialog or another tab first, and then the engine keeps moving the camera
6490
+ * forever.
6491
+ */
6492
+ releaseKeys() {
6493
+ for (const keyCode of this.heldKeys) {
6494
+ this.sendKeyUp(keyCode);
6495
+ }
6496
+ this.heldKeys.clear();
6497
+ }
6498
+ registerKeyboardEvents() {
5858
6499
  fromEvent(document, 'keydown')
5859
6500
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5860
6501
  .subscribe((e) => {
5861
- if (filter(e)) {
6502
+ // A focused field owns the key outright — no forwarding and no
6503
+ // `preventDefault`, or Tab could not move between inputs.
6504
+ if (this.isTextEntryTarget(e)) {
5862
6505
  return;
5863
6506
  }
5864
- const isCtrlOrCommand = e.ctrlKey || e.metaKey;
5865
- // Normalised first: on Firefox the raw `e.keyCode` for these keys is 61
5866
- // and 173, so comparing it left browser zoom to reach the engine too.
5867
6507
  const keyCode = this.getKeyCode(e);
5868
- const isPlusOrMinus = keyCode === KeyboardNumericCode.Equal ||
5869
- keyCode === KeyboardNumericCode.Minus;
5870
- if (isCtrlOrCommand && isPlusOrMinus) {
6508
+ if (keyCode === null) {
6509
+ return;
6510
+ }
6511
+ // Ahead of the allow-list on purpose. None of the browser keys are
6512
+ // forwarded, so a check that sits behind the allow-list can never run,
6513
+ // which is what made `suppressBrowserKeys` a no-op.
6514
+ if (this.options.suppressBrowserKeys && this.isBrowserKey(keyCode)) {
6515
+ e.preventDefault();
6516
+ }
6517
+ if (!this.isKeyAllowed(e)) {
6518
+ return;
6519
+ }
6520
+ // A browser shortcut is in flight (Ctrl+W closes the tab, Ctrl+S opens
6521
+ // a dialog, Ctrl+= zooms). The browser acts on it regardless, so the
6522
+ // engine must not also treat it as gameplay input. Modifier keys are
6523
+ // exempt: their own keydown already reports themselves as pressed.
6524
+ if ((e.ctrlKey || e.metaKey) && !MODIFIER_KEY_CODES.has(e.code)) {
5871
6525
  return;
5872
6526
  }
5873
6527
  const keyDown = this.keyDownBytes;
@@ -5875,38 +6529,46 @@ class InputService extends SubService {
5875
6529
  keyDown[1] = keyCode;
5876
6530
  keyDown[2] = Number(e.repeat);
5877
6531
  this.sendInputData(keyDown.buffer);
6532
+ this.heldKeys.add(keyCode);
5878
6533
  // Backspace isn't a keypress in JS, but UE4 text-entry fields need it
5879
6534
  // to delete characters.
5880
- if (e.keyCode === SpecialKeyCodes.BackSpace) {
6535
+ if (keyCode === SpecialKeyCodes.BackSpace) {
5881
6536
  const data = this.keyPressView;
5882
6537
  data.setUint8(0, MessageType.KeyPress);
5883
6538
  data.setUint16(1, SpecialKeyCodes.BackSpace, true);
5884
6539
  this.sendInputData(data.buffer);
5885
6540
  }
5886
- if (this.options.suppressBrowserKeys &&
5887
- this.isKeyCodeBrowserKey(e.keyCode)) {
5888
- e.preventDefault();
5889
- }
5890
6541
  });
5891
6542
  fromEvent(document, 'keyup')
5892
6543
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5893
6544
  .subscribe((e) => {
5894
- if (filter(e)) {
6545
+ const keyCode = this.getKeyCode(e);
6546
+ if (keyCode === null) {
5895
6547
  return;
5896
6548
  }
5897
- const keyUp = this.keyUpBytes;
5898
- keyUp[0] = MessageType.KeyUp;
5899
- keyUp[1] = this.getKeyCode(e);
5900
- this.sendInputData(keyUp.buffer);
5901
6549
  if (this.options.suppressBrowserKeys &&
5902
- this.isKeyCodeBrowserKey(e.keyCode)) {
6550
+ this.isBrowserKey(keyCode) &&
6551
+ !this.isTextEntryTarget(e)) {
5903
6552
  e.preventDefault();
5904
6553
  }
6554
+ // The registry is the only gate here. Re-running the allow-list and the
6555
+ // focus check would let their state drift between press and release —
6556
+ // press over the video, release with a dialog focused, and the engine
6557
+ // never hears the key came up.
6558
+ if (!this.heldKeys.delete(keyCode)) {
6559
+ return;
6560
+ }
6561
+ this.sendKeyUp(keyCode);
5905
6562
  });
5906
6563
  fromEvent(document, 'keypress')
5907
6564
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5908
6565
  .subscribe((e) => {
5909
- if (filter(e)) {
6566
+ if (this.isTextEntryTarget(e) || !this.isKeyAllowed(e)) {
6567
+ return;
6568
+ }
6569
+ // Deprecated event: the payload is a character, so an IME composition
6570
+ // or a key with no character produces nothing to send.
6571
+ if (e.keyCode === IME_PROCESS_KEY || !e.charCode) {
5910
6572
  return;
5911
6573
  }
5912
6574
  const data = this.keyPressView;
@@ -5915,10 +6577,10 @@ class InputService extends SubService {
5915
6577
  this.sendInputData(data.buffer);
5916
6578
  });
5917
6579
  }
5918
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: InputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5919
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: InputService, providedIn: 'root' }); }
6580
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: InputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6581
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: InputService, providedIn: 'root' }); }
5920
6582
  }
5921
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: InputService, decorators: [{
6583
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: InputService, decorators: [{
5922
6584
  type: Injectable,
5923
6585
  args: [{ providedIn: 'root' }]
5924
6586
  }], ctorParameters: () => [] });
@@ -5987,46 +6649,36 @@ class StreamStatusTelemetryService extends SubService {
5987
6649
  VideoEncoderQP,
5988
6650
  };
5989
6651
  }
5990
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5991
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService, providedIn: 'root' }); }
6652
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6653
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService, providedIn: 'root' }); }
5992
6654
  }
5993
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService, decorators: [{
6655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService, decorators: [{
5994
6656
  type: Injectable,
5995
6657
  args: [{ providedIn: 'root' }]
5996
6658
  }], ctorParameters: () => [] });
5997
6659
 
5998
6660
  /**
5999
- * Pro-grade telemetry for the WebRTC stream lifecycle.
6661
+ * Telemetry for the WebRTC stream lifecycle.
6000
6662
  *
6001
- * Wire-level event taxonomy (intentionally small — all phase-specific
6002
- * details ride inside `stream.phase_ended`'s payload):
6003
- * - `stream.session_started` — once per session, when a real connection
6004
- * attempt begins (`setEstablishingConnection(true)`)
6005
- * - `stream.phase_ended` — once per closer in {@link phaseCatalog};
6006
- * carries phaseNo / id / color /
6007
- * milestones / params / overlays
6008
- * - `stream.sample` — every 5s, window-aggregated (p. 50/p. 95/min/max)
6009
- * - `stream.quality_degraded` — on QP entering `red` (>35)
6010
- * - `stream.quality_recovered` — on QP leaving `red`
6011
- * - `stream.fps_low` — when the window FPS p50 < 70% of the target
6012
- * - `stream.session_ended` — once per session, on disconnect/pagehide/restart
6663
+ * The event taxonomy is deliberately small — phase-specific detail rides inside
6664
+ * `stream.phase_ended`'s payload rather than becoming its own event type:
6665
+ * `session_started`, `phase_ended` (once per closer in {@link phaseCatalog}),
6666
+ * `sample` (every 5s, window-aggregated), `quality_degraded` /
6667
+ * `quality_recovered` (QP entering/leaving `red`), `fps_low`, `session_ended`.
6668
+ * Every wire shape lives in `interfaces/stream-telemetry-events.interface.ts`,
6669
+ * and `track()` / `emitLifecycle()` are typed against it — a drifting payload is
6670
+ * a compile error.
6013
6671
  *
6014
- * Every event carries `sessionId` + `userId`. The `sessionId` IS the stream's
6015
- * connection id (`selectCirrusConnectionId`) — the same value the debug overlay
6016
- * shows as "Connection Id" — so a telemetry session can be matched to a Cirrus
6017
- * player session by id. Since that id only exists once the orchestration
6018
- * answered, the first events are queued and flushed with it (see
6672
+ * `sessionId` IS the stream's connection id (`selectCirrusConnectionId`), the
6673
+ * value the debug overlay labels "Connection Id", so a telemetry session can be
6674
+ * matched to a Cirrus player session. That id only exists once orchestration
6675
+ * answered, so early events are queued and flushed with it (see
6019
6676
  * {@link SessionState.wireId}); a session that never got one falls back to a
6020
- * locally generated uuid. Samples carry a monotonic `seq` for server-side
6021
- * ordering & dedup. Durations use `performance.now()` — immune to wall-clock
6022
- * jumps.
6677
+ * local uuid. Samples carry a monotonic `seq` for server-side ordering and
6678
+ * dedup, and durations use `performance.now()` to survive wall-clock jumps.
6023
6679
  *
6024
- * NOTE: For `stream.session_ended` on the tab close to survive teardown,
6025
- * the Mixpanel client must be configured with `api_transport: 'sendBeacon'`.
6026
- *
6027
- * Every wire shape lives in `interfaces/stream-telemetry-events.interface.ts`
6028
- * — `track()` / `emitLifecycle()` are typed against it, so a payload that
6029
- * drifts from the contract is a compile error.
6680
+ * NOTE: `stream.session_ended` only survives tab close if the Mixpanel client
6681
+ * is configured with `api_transport: 'sendBeacon'`.
6030
6682
  */
6031
6683
  /** Trim a resource URL to its path: drop origin and query string so the
6032
6684
  * emitted name is short, stable, and not a PII-bearing query. */
@@ -6351,45 +7003,31 @@ class StreamStatusTelemetryService2 {
6351
7003
  }
6352
7004
  }
6353
7005
  init() {
6354
- // Start a new session when a real connection attempt BEGINS, not on
6355
- // every `initSignalling` dispatch. `initSignalling` is fired multiple
6356
- // times per page load (the unreal-scene effect, `startStream`, the
6357
- // reconnect effect), but the connection layer collapses them into one
6358
- // attempt via `connectToSignaling$` (`exhaustMap` + `!cirrusConnected`
6359
- // filter). `setEstablishingConnection(true)` is dispatched exactly once
6360
- // per real attempt, by that effect's `startWith(...)` — the SINGLE
6361
- // dispatch site, downstream of the guard and ahead of both branches
6362
- // (prefetch adoption / fresh connect). So binding here yields one session
6363
- // per attempt instead of a burst of `restart` churn sessions. Genuine
6364
- // reconnects (e.g. DataChannelTimeout) re-enter the same path and
6365
- // correctly end the prior session as `restart` + start a new one.
7006
+ // Bound to `setEstablishingConnection(true)`, not `initSignalling`, which
7007
+ // fires several times per page load (unreal-scene effect, `startStream`,
7008
+ // reconnect). `connectToSignaling$` collapses those into one attempt
7009
+ // (`exhaustMap` + `!cirrusConnected`), and its `startWith(...)` is the
7010
+ // single dispatch site for `true` — downstream of that guard and ahead of
7011
+ // both branches (prefetch adoption / fresh connect). So this yields one
7012
+ // session per real attempt instead of a burst of `restart` churn, while
7013
+ // genuine reconnects still end the prior session as `restart`.
6366
7014
  //
6367
- // Do NOT add a second dispatch site: this binding turns every extra
6368
- // `true` into `endSession('restart')` + a new session, which throws away
6369
- // the first session's page-load measurement and its prefetch adoption.
7015
+ // Do NOT add a second dispatch site: every extra `true` becomes
7016
+ // `endSession('restart')` + a new session, discarding the first session's
7017
+ // page-load measurement and its prefetch adoption.
6370
7018
  this.actions$
6371
7019
  .pipe(ofType(setEstablishingConnection), filter$1(({ value }) => value === true), takeUntil$1(this.destroy$))
6372
7020
  .subscribe(() => this.startSession());
6373
- // Action-stream end triggers (NOT a state-observable subscription).
6374
- //
6375
- // We can't observe the cirrus connection flag via `pairwise`
6376
- // because the `destroyUnrealScene` reducer resets the entire
6377
- // unreal state to `initialState` in one step — `cirrusConnected`
6378
- // flips from `true` to `false` AND `disconnectReason` resets to
6379
- // `None` simultaneously. A state-observable subscriber would fire
6380
- // synchronously inside that reducer's emission, before the
6381
- // follow-up effect (`destroyUnrealScene → destroyRemoteConnections({Destroy})`)
6382
- // gets a chance to write `Destroy` back into state — so the
6383
- // subscriber would see `None` and end as `'disconnected'`.
6384
- //
6385
- // Subscribing to the actions instead lets us pick the right
6386
- // reason per dispatched action, and `endSession` is idempotent
6387
- // (`if (!this.session) return`) so any cascading second trigger
6388
- // is a no-op. Coverage:
7021
+ // End triggers read the action stream, not the state, on purpose. The
7022
+ // `destroyUnrealScene` reducer resets the whole unreal state in one step,
7023
+ // so `cirrusConnected` flips to `false` while `disconnectReason` resets to
7024
+ // `None`. A state subscriber fires synchronously inside that emission —
7025
+ // before the follow-up effect writes `Destroy` back — and would report
7026
+ // `'disconnected'`. Per-action subscriptions pick the right reason instead,
7027
+ // and `endSession` is idempotent, so a cascading second trigger is a no-op:
6389
7028
  // - `destroyUnrealScene` → `'destroy'` (route change, removed from DOM)
6390
- // - `setCirrusDisconnected` → `'disconnected'` (WS close, AFK,
6391
- // manual drop, WebRTCError, etc. — granular cause + message
6392
- // read from current state in `endSession`)
7029
+ // - `setCirrusDisconnected` → `'disconnected'` (WS close, AFK, manual
7030
+ // drop, WebRTCError — granular cause read from state in `endSession`)
6393
7031
  this.actions$
6394
7032
  .pipe(ofType(destroyUnrealScene), takeUntil$1(this.destroy$))
6395
7033
  .subscribe(() => this.endSession('destroy'));
@@ -6685,11 +7323,10 @@ class StreamStatusTelemetryService2 {
6685
7323
  /**
6686
7324
  * Emit the region / signaling / webrtc phases for an ADOPTED connection.
6687
7325
  *
6688
- * The prefetch script established the connection in PARALLEL with page load,
6689
- * so the phases are positioned at their real (anchor-relative) spans — shifted
6690
- * LEFT to overlap the HTML-loading / route-idle rows — rather than sequentially
6691
- * after them. That's the whole point of the prefetch: the handshake already
6692
- * happened while the page was loading. Each phase ships:
7326
+ * The prefetch script established the connection in parallel with page load,
7327
+ * so phases sit at their real anchor-relative spans — shifted left to overlap
7328
+ * the HTML-loading / route-idle rows instead of following them. Each phase
7329
+ * ships:
6693
7330
  * - `duration` — the real sub-step time the prefetch spent.
6694
7331
  * - `spanStartMs` — anchor-relative start, so the viewer left-shifts the bar.
6695
7332
  * - `adoptionAtMs` — where Angular took over (the next phase, Unreal
@@ -6955,22 +7592,15 @@ class StreamStatusTelemetryService2 {
6955
7592
  }
6956
7593
  }
6957
7594
  /**
6958
- * Aggregate all resources the browser has loaded so far
6959
- * (`performance.getEntriesByType('resource')`), grouped by `initiatorType`,
6960
- * **split by `window.load`**:
7595
+ * Aggregates `performance.getEntriesByType('resource')` by `initiatorType`,
7596
+ * split at `window.load`: `resources` for entries before `loadEventEnd` (the
7597
+ * actual page weight) and `lazyResources` for those after it (typically the
7598
+ * route's lazy chunks and manifests, downloaded when the user navigates to the
7599
+ * sub-route the streamer lives on). If `loadEventEnd` is 0 or unknown,
7600
+ * everything lands in `resources` and `lazyResources` is null.
6961
7601
  *
6962
- * - `resources` — fetched before `loadEventEnd` (= part of the actual
6963
- * page weight). What you'd traditionally call "page load."
6964
- * - `lazyResources` — fetched after `loadEventEnd` (= typically the
6965
- * route's lazy-loaded chunks, JSON manifests etc. that download
6966
- * when the user navigates to a sub-route the streamer lives on).
6967
- *
6968
- * When `loadEventEnd` is 0/unknown, everything goes into `resources`
6969
- * and `lazyResources` is null — old behavior.
6970
- *
6971
- * `transferSize === 0 && decodedBodySize > 0` means the resource came from
6972
- * the HTTP cache — counted separately so cached hits don't inflate
6973
- * the "bytes on the wire" metric.
7602
+ * `transferSize === 0 && decodedBodySize > 0` means an HTTP-cache hit, counted
7603
+ * separately so it does not inflate the bytes-on-the-wire metric.
6974
7604
  */
6975
7605
  collectResourceTotals(loadEventEnd) {
6976
7606
  try {
@@ -7092,10 +7722,10 @@ class StreamStatusTelemetryService2 {
7092
7722
  emit(event, data) {
7093
7723
  this.store.dispatch(trackTelemetryData({ event, data }));
7094
7724
  }
7095
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7096
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService2, providedIn: 'root' }); }
7725
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7726
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService2, providedIn: 'root' }); }
7097
7727
  }
7098
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService2, decorators: [{
7728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StreamStatusTelemetryService2, decorators: [{
7099
7729
  type: Injectable,
7100
7730
  args: [{ providedIn: 'root' }]
7101
7731
  }], ctorParameters: () => [] });
@@ -7181,10 +7811,10 @@ class FileHandlerService {
7181
7811
  return observable;
7182
7812
  });
7183
7813
  }
7184
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7185
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileHandlerService, providedIn: 'root' }); }
7814
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7815
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileHandlerService, providedIn: 'root' }); }
7186
7816
  }
7187
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileHandlerService, decorators: [{
7817
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FileHandlerService, decorators: [{
7188
7818
  type: Injectable,
7189
7819
  args: [{ providedIn: 'root' }]
7190
7820
  }] });
@@ -7273,86 +7903,31 @@ class AnalyticsService extends SubService {
7273
7903
  Logger.log('Analytics Event:', name, params);
7274
7904
  this.store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
7275
7905
  }
7276
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7277
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
7906
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7907
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
7278
7908
  }
7279
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AnalyticsService, decorators: [{
7909
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AnalyticsService, decorators: [{
7280
7910
  type: Injectable,
7281
7911
  args: [{ providedIn: 'root' }]
7282
7912
  }], ctorParameters: () => [] });
7283
7913
 
7284
7914
  /**
7285
- * Adaptive FPS controller that monitors the incoming WebRTC video stream
7286
- * quality and dynamically adjusts Unreal Engine's `t.MaxFPS` console variable.
7287
- *
7288
- * ## How it works
7289
- *
7290
- * The service subscribes to `VideoService.videoStats$` (emitted every 250 ms)
7291
- * and collects samples of FPS, bitrate, and QP (Quantization Parameter).
7292
- * Every {@link SAMPLE_WINDOW} ticks (~2 s) it averages the collected samples
7293
- * and feeds them into a set of **fuzzy-logic membership functions** that
7294
- * classify each metric into overlapping quality bands.
7295
- *
7296
- * ### Input signals
7297
- *
7298
- * **1. FPS ratio** = `actualFPS / currentTargetFPS` (0..1+)
7299
- *
7300
- * | Band | Range | Meaning |
7301
- * |--------|---------------------|-----------------------------------------------|
7302
- * | low | ratio < 0.7 → 1.0, linear ramp 0.7..0.9 → 0 | Stream cannot keep up with the target |
7303
- * | okays | triangle 0.8..1.0, peak at 0.95 | Stream roughly matches the target |
7304
- * | high | ratio 0.9..1.0 → ramp, >1.0 → 1.0 | Stream meets or exceeds the target |
7915
+ * Adaptive FPS controller: watches the incoming WebRTC stream and steers Unreal's
7916
+ * `t.MaxFPS` between the discrete {@link FPS_STEPS}.
7305
7917
  *
7306
- * **2. Bitrate load** = `currentBitrate / MAX_BITRATE` (0..1+)
7918
+ * Every {@link SAMPLE_WINDOW} ticks of `VideoService.videoStats$` (250 ms each)
7919
+ * it averages FPS, bitrate and QP, runs them through the fuzzy membership
7920
+ * functions below, and picks a decision by first-match rules. Fuzzy bands rather
7921
+ * than hard thresholds so a metric sitting on a boundary does not oscillate.
7307
7922
  *
7308
- * `MAX_BITRATE` = 20 Mbit/s — the reference ceiling for the encoder.
7923
+ * Cooldowns between steps are deliberately asymmetric: stepping *down* again is
7924
+ * allowed after {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS}, but stepping back
7925
+ * *up* after a decrease waits {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} — a
7926
+ * stream that just failed its target is likely to fail it again.
7309
7927
  *
7310
- * | Band | Range | Meaning |
7311
- * |--------|-------------------------------------------------|-----------------------------------------------|
7312
- * | high | load 0.5..0.8 → ramp, >0.8 → 1.0 | Encoder is using most of the available bandwidth |
7313
- * | not high | load < 0.5 → 0.0 | Bandwidth headroom is available |
7314
- *
7315
- * **3. Quality** — mapped from QP via `mapQpToQuality()` (see {@link Quality}):
7316
- *
7317
- * | Quality | QP range | Meaning |
7318
- * |----------|-----------|-------------------------------------------------|
7319
- * | `lime` | QP <= 26 | Good — encoder has enough headroom |
7320
- * | `orange` | QP 27..35 | Fair — encoder is under moderate pressure |
7321
- * | `red` | QP > 35 | Poor — heavy compression, visible artifacts |
7322
- *
7323
- * ### Decision rules (evaluated in priority order, first match wins)
7324
- *
7325
- * | # | Condition | Decision |
7326
- * |---|------------------------------------------------------------|--------------|
7327
- * | 1 | FPS ratio is **low** (membership > 0.5) | **decrease** |
7328
- * | 2 | FPS ratio is **ok** AND quality is `red` | **decrease** |
7329
- * | 3 | FPS ratio is **ok** AND bitrate **high** AND quality is `orange` | **hold** |
7330
- * | 4 | FPS ratio is **high** AND quality is `lime` AND bitrate **not high** | **increase** |
7331
- * | — | Everything else | **hold** |
7332
- *
7333
- * ## Cooldown-based throttling
7334
- *
7335
- * The service continuously evaluates fuzzy rules in every sample window.
7336
- * After each FPS step change it enforces a cooldown before the next
7337
- * change can be applied. The cooldown duration depends on the
7338
- * (previous decision → next decision) pair:
7339
- *
7340
- * | Previous | Next | Cooldown |
7341
- * |-----------|-----------|-----------------------------------------------------|
7342
- * | decrease | decrease | {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS} (2 s) |
7343
- * | decrease | increase | {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} (10 s) |
7344
- * | increase | decrease | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
7345
- * | increase | increase | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
7346
- *
7347
- * This means the system reacts quickly after an increase (2 s in any
7348
- * direction) but is cautious about stepping back up after a decrease
7349
- * (10 s), while still allowing consecutive decreases at 2 s intervals.
7350
- *
7351
- * ## FPS dispatch
7352
- *
7353
- * FPS changes are dispatched via the NgRx `setMaxFps` action, which triggers
7354
- * the existing `UnrealEffects.setMaxFps$` effect that sends the
7355
- * `t.MaxFPS <value>` console command to Unreal Engine.
7928
+ * Changes go out as the `setMaxFps` action, not a direct command: the existing
7929
+ * `UnrealEffects.setMaxFps$` effect turns it into the `t.MaxFPS <value>` console
7930
+ * command.
7356
7931
  */
7357
7932
  class FpsMonitorService extends SubService {
7358
7933
  constructor() {
@@ -7453,15 +8028,7 @@ class FpsMonitorService extends SubService {
7453
8028
  const decision = this.evaluateDecision(avg, currentTarget);
7454
8029
  this.upgradeFps(decision, avg.qp);
7455
8030
  }
7456
- /**
7457
- * Combines fuzzy membership values into a single discrete decision.
7458
- * Rules are evaluated in priority order — first match wins.
7459
- *
7460
- * Inputs:
7461
- * - `fpsRatio` = avgFPS / currentTargetFPS (e.g., 45/60 = 0.75)
7462
- * - `bitrateLoad` = avgBitrate / 20 Mbit/s (e.g., 12M/20M = 0.6)
7463
- * - `quality` = mapQpToQuality(avgQP) → 'lime' | 'orange' | 'red'
7464
- */
8031
+ /** Rules are evaluated in priority order — first match wins. */
7465
8032
  evaluateDecision(avg, currentTarget) {
7466
8033
  const referenceTarget = currentTarget > 0 ? currentTarget : this.UNLIMITED_REFERENCE_FPS;
7467
8034
  const fpsRatio = avg.fps / referenceTarget;
@@ -7501,21 +8068,10 @@ class FpsMonitorService extends SubService {
7501
8068
  }
7502
8069
  return 'hold';
7503
8070
  }
7504
- // ---------------------------------------------------------------------------
7505
- // Fuzzy membership functions
7506
- //
7507
- // Each function maps a normalised input (0..1+) to a membership degree (0..1).
7508
- // Overlapping trapezoid/triangle shapes let multiple categories be partially
7509
- // active at the same time, which makes the rules less brittle than hard thresholds.
7510
- // ---------------------------------------------------------------------------
7511
- /**
7512
- * FPS ratio membership "low".
7513
- * Input: a ratio = actualFPS / targetFPS (e.g., 42/60 = 0.70).
7514
- *
7515
- * ratio < 0.7 → 1.0 (clearly failing, e.g., 40/60)
7516
- * ratio 0.7..0.9 → linear ramp down (e.g., 0.8 → 0.5)
7517
- * a ratio > 0.9 → 0.0 (keeping up)
7518
- */
8071
+ // Fuzzy membership functions: normalised input (0..1+) → membership degree
8072
+ // (0..1). The shapes overlap on purpose, so several bands can be partially
8073
+ // active at once and the rules stay less brittle than hard thresholds.
8074
+ /** "low" — the stream cannot keep up with its target. */
7519
8075
  membershipFpsLow(ratio) {
7520
8076
  if (ratio < 0.7)
7521
8077
  return 1.0;
@@ -7523,15 +8079,7 @@ class FpsMonitorService extends SubService {
7523
8079
  return 0.0;
7524
8080
  return (0.9 - ratio) / 0.2;
7525
8081
  }
7526
- /**
7527
- * FPS ratio membership "ok" — triangle shape.
7528
- * Input: ratio = actualFPS / targetFPS.
7529
- *
7530
- * ratio < 0.8 → 0.0
7531
- * ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g., 57/60)
7532
- * ratio 0.95..1.0 → ramp down
7533
- * a ratio > 1.0 → 0.0
7534
- */
8082
+ /** "ok" — triangle peaking just under the target. */
7535
8083
  membershipFpsOk(ratio) {
7536
8084
  if (ratio < 0.8)
7537
8085
  return 0.0;
@@ -7541,14 +8089,7 @@ class FpsMonitorService extends SubService {
7541
8089
  return (1.0 - ratio) / 0.05;
7542
8090
  return 0.0;
7543
8091
  }
7544
- /**
7545
- * FPS ratio membership "high".
7546
- * Input: ratio = actualFPS / targetFPS.
7547
- *
7548
- * ratio < 0.9 → 0.0
7549
- * ratio 0.9..1.0 → linear ramp up (e.g., 0.95 → 0.5)
7550
- * a ratio >= 1.0 → 1.0 (meeting or exceeding target, e.g., 60/60)
7551
- */
8092
+ /** "high" — the stream meets or exceeds its target. */
7552
8093
  membershipFpsHigh(ratio) {
7553
8094
  if (ratio < 0.9)
7554
8095
  return 0.0;
@@ -7556,14 +8097,7 @@ class FpsMonitorService extends SubService {
7556
8097
  return 1.0;
7557
8098
  return (ratio - 0.9) / 0.1;
7558
8099
  }
7559
- /**
7560
- * Bitrate load membership "high".
7561
- * Input: a load = currentBitrate / MAX_BITRATE (20 Mbit/s).
7562
- *
7563
- * load < 0.5 → 0.0 (below 10 Mbit/s — plenty of headroom)
7564
- * load 0.5..0.8 → linear ramp (e.g., 13M/20M = 0.65 → 0.5)
7565
- * load > 0.8 → 1.0 (above 16 Mbit/s — near capacity)
7566
- */
8100
+ /** "high" — the encoder is using most of the available bandwidth. */
7567
8101
  membershipBitrateHigh(load) {
7568
8102
  if (load < 0.5)
7569
8103
  return 0.0;
@@ -7643,10 +8177,10 @@ class FpsMonitorService extends SubService {
7643
8177
  }
7644
8178
  return { fps: fps / len, bitrate: bitrate / len, qp: qp / len };
7645
8179
  }
7646
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7647
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FpsMonitorService, providedIn: 'root' }); }
8180
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
8181
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FpsMonitorService, providedIn: 'root' }); }
7648
8182
  }
7649
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FpsMonitorService, decorators: [{
8183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FpsMonitorService, decorators: [{
7650
8184
  type: Injectable,
7651
8185
  args: [{ providedIn: 'root' }]
7652
8186
  }], ctorParameters: () => [] });
@@ -7681,10 +8215,10 @@ class AfkTimeoutModalComponent {
7681
8215
  resetAfk() {
7682
8216
  this.#afkService.reset();
7683
8217
  }
7684
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7685
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: AfkTimeoutModalComponent, isStandalone: true, selector: "app-afk-timeout-modal", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowAfkPopup()) {\n <div\n (click)=\"resetAfk()\"\n [class]=\"scrimSkin()\"\n class=\"afk-anchor clickableState\"\n id=\"videoPlayOverlay\"\n >\n <div [class]=\"cardSkin()\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\n </div>\n <div class=\"resume-box__text\">\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\n <p>\n No activity detected. Press 'Continue' if you wish to keep your\n session active\n </p>\n </div>\n <app-unreal-button\n [fullWidth]=\"true\"\n [data-testid]=\"'continue-session'\"\n [class]=\"buttonSkin()\"\n weight=\"primary\"\n size=\"lg\"\n >\n Continue\n </app-unreal-button>\n }\n </div>\n </div>\n}\n", styles: [".afk-anchor{position:absolute;z-index:30;top:0;width:100%;height:100%}.afk-scrim{font-size:1.8em;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));background-color:var(--unreal-surface-scrim, rgba(100, 100, 100, .7))}.clickableState{display:flex;justify-content:center;align-items:center;cursor:pointer}.textDisplayState{display:flex}.hiddenState{display:none}.resume-box{width:340px;padding:32px 20px 20px;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.resume-box__pic{width:72px;height:72px;margin:0 auto 22px;border-radius:48px;background:#ecf0f2;padding:12px;display:flex;align-items:center;justify-content:center}.text-number{color:var(--unreal-text-main, #1f2937);text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:30px;font-style:normal;font-weight:400;line-height:24px}.resume-box__text{margin-bottom:18px}.resume-box__text p{text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--unreal-text-secondary, var(--src-text-body-secondary, #6b7280))}.resume-box__text .resume-box__heading{color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8218
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8219
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: AfkTimeoutModalComponent, isStandalone: true, selector: "app-afk-timeout-modal", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowAfkPopup()) {\n <div\n (click)=\"resetAfk()\"\n [class]=\"scrimSkin()\"\n class=\"afk-anchor clickableState\"\n id=\"videoPlayOverlay\"\n >\n <div [class]=\"cardSkin()\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\n </div>\n <div class=\"resume-box__text\">\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\n <p>\n No activity detected. Press 'Continue' if you wish to keep your\n session active\n </p>\n </div>\n <app-unreal-button\n [fullWidth]=\"true\"\n [data-testid]=\"'continue-session'\"\n [class]=\"buttonSkin()\"\n weight=\"primary\"\n size=\"lg\"\n >\n Continue\n </app-unreal-button>\n }\n </div>\n </div>\n}\n", styles: [".afk-anchor{position:absolute;z-index:30;top:0;width:100%;height:100%}.afk-scrim{font-size:1.8em;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));background-color:var(--unreal-surface-scrim, rgba(100, 100, 100, .7))}.clickableState{display:flex;justify-content:center;align-items:center;cursor:pointer}.textDisplayState{display:flex}.hiddenState{display:none}.resume-box{width:340px;padding:32px 20px 20px;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.resume-box__pic{width:72px;height:72px;margin:0 auto 22px;border-radius:48px;background:#ecf0f2;padding:12px;display:flex;align-items:center;justify-content:center}.text-number{color:var(--unreal-text-main, #1f2937);text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:30px;font-style:normal;font-weight:400;line-height:24px}.resume-box__text{margin-bottom:18px}.resume-box__text p{text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--unreal-text-secondary, var(--src-text-body-secondary, #6b7280))}.resume-box__text .resume-box__heading{color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7686
8220
  }
7687
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AfkTimeoutModalComponent, decorators: [{
8221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: AfkTimeoutModalComponent, decorators: [{
7688
8222
  type: Component,
7689
8223
  args: [{ selector: 'app-afk-timeout-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [UnrealButtonComponent, NgTemplateOutlet], template: "@if (isShowAfkPopup()) {\n <div\n (click)=\"resetAfk()\"\n [class]=\"scrimSkin()\"\n class=\"afk-anchor clickableState\"\n id=\"videoPlayOverlay\"\n >\n <div [class]=\"cardSkin()\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\n </div>\n <div class=\"resume-box__text\">\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\n <p>\n No activity detected. Press 'Continue' if you wish to keep your\n session active\n </p>\n </div>\n <app-unreal-button\n [fullWidth]=\"true\"\n [data-testid]=\"'continue-session'\"\n [class]=\"buttonSkin()\"\n weight=\"primary\"\n size=\"lg\"\n >\n Continue\n </app-unreal-button>\n }\n </div>\n </div>\n}\n", styles: [".afk-anchor{position:absolute;z-index:30;top:0;width:100%;height:100%}.afk-scrim{font-size:1.8em;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));background-color:var(--unreal-surface-scrim, rgba(100, 100, 100, .7))}.clickableState{display:flex;justify-content:center;align-items:center;cursor:pointer}.textDisplayState{display:flex}.hiddenState{display:none}.resume-box{width:340px;padding:32px 20px 20px;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.resume-box__pic{width:72px;height:72px;margin:0 auto 22px;border-radius:48px;background:#ecf0f2;padding:12px;display:flex;align-items:center;justify-content:center}.text-number{color:var(--unreal-text-main, #1f2937);text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:30px;font-style:normal;font-weight:400;line-height:24px}.resume-box__text{margin-bottom:18px}.resume-box__text p{text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--unreal-text-secondary, var(--src-text-body-secondary, #6b7280))}.resume-box__text .resume-box__heading{color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"] }]
7690
8224
  }], propDecorators: { uiClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiClasses", required: false }] }], contentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentTemplate", required: false }] }] } });
@@ -7696,10 +8230,10 @@ class FreezeFrameComponent {
7696
8230
  this.combinedFreeze = this.#store.selectSignal(selectFreezeFrameCombinedDataUrl);
7697
8231
  }
7698
8232
  #store;
7699
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7700
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: FreezeFrameComponent, isStandalone: true, selector: "app-freeze-frame", ngImport: i0, template: "@let image = combinedFreeze();\n@let progress = freezeFrameProgressMessageFromVideo();\n\n<div class=\"freeze-images\">\n @if (image) {\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\n }\n\n @if (progress) {\n <div class=\"progress-status\">\n {{ progress }}\n </div>\n }\n</div>\n", styles: [".freeze-images{pointer-events:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}.freeze-images .freezeFrameOverlay{top:0;left:0;width:100%;height:100%;position:absolute;object-fit:cover;object-position:center}.freeze-images .videoStillImage{position:absolute;z-index:1;top:0;left:0;width:100%;height:100%;object-fit:cover}.freeze-images .progress-status{position:absolute;top:50%;left:50%;z-index:1;display:flex;flex-direction:column;justify-content:center;align-items:center;width:auto;height:auto;margin:0;padding:10px;background-color:#ffffffb3;border:1px solid grey;transform:translate(-50%,-50%)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8233
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8234
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: FreezeFrameComponent, isStandalone: true, selector: "app-freeze-frame", ngImport: i0, template: "@let image = combinedFreeze();\n@let progress = freezeFrameProgressMessageFromVideo();\n\n<div class=\"freeze-images\">\n @if (image) {\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\n }\n\n @if (progress) {\n <div class=\"progress-status\">\n {{ progress }}\n </div>\n }\n</div>\n", styles: [".freeze-images{pointer-events:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}.freeze-images .freezeFrameOverlay{top:0;left:0;width:100%;height:100%;position:absolute;object-fit:cover;object-position:center}.freeze-images .videoStillImage{position:absolute;z-index:1;top:0;left:0;width:100%;height:100%;object-fit:cover}.freeze-images .progress-status{position:absolute;top:50%;left:50%;z-index:1;display:flex;flex-direction:column;justify-content:center;align-items:center;width:auto;height:auto;margin:0;padding:10px;background-color:#ffffffb3;border:1px solid grey;transform:translate(-50%,-50%)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7701
8235
  }
7702
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameComponent, decorators: [{
8236
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: FreezeFrameComponent, decorators: [{
7703
8237
  type: Component,
7704
8238
  args: [{ selector: 'app-freeze-frame', changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = combinedFreeze();\n@let progress = freezeFrameProgressMessageFromVideo();\n\n<div class=\"freeze-images\">\n @if (image) {\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\n }\n\n @if (progress) {\n <div class=\"progress-status\">\n {{ progress }}\n </div>\n }\n</div>\n", styles: [".freeze-images{pointer-events:none;position:absolute;left:0;top:0;width:100%;height:100%;z-index:1}.freeze-images .freezeFrameOverlay{top:0;left:0;width:100%;height:100%;position:absolute;object-fit:cover;object-position:center}.freeze-images .videoStillImage{position:absolute;z-index:1;top:0;left:0;width:100%;height:100%;object-fit:cover}.freeze-images .progress-status{position:absolute;top:50%;left:50%;z-index:1;display:flex;flex-direction:column;justify-content:center;align-items:center;width:auto;height:auto;margin:0;padding:10px;background-color:#ffffffb3;border:1px solid grey;transform:translate(-50%,-50%)}\n"] }]
7705
8239
  }] });
@@ -7714,10 +8248,10 @@ class ImageLoadingSrcComponent {
7714
8248
  loadingImageError() {
7715
8249
  this.#store.dispatch(setLoadingImageSrc({ src: '' }));
7716
8250
  }
7717
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7718
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"loadingImageError()\"\n [ngSrc]=\"image\"\n fill\n loading=\"lazy\"\n alt=\"image loading src\"\n />\n }\n </div>\n </div>\n}\n", styles: [".preload{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.preload .back{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.preload .back img{width:100%;height:100%;object-fit:cover}.preload .back{background-color:var(--unreal-surface-screen, #fff)}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8251
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8252
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"loadingImageError()\"\n [ngSrc]=\"image\"\n fill\n loading=\"lazy\"\n alt=\"image loading src\"\n />\n }\n </div>\n </div>\n}\n", styles: [".preload{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.preload .back{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.preload .back img{width:100%;height:100%;object-fit:cover}.preload .back{background-color:var(--unreal-surface-screen, #fff)}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7719
8253
  }
7720
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
8254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
7721
8255
  type: Component,
7722
8256
  args: [{ selector: 'app-image-loading-src', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgOptimizedImage], template: "@let image = imageLoadingSrc();\n@if (isLoaderScreenVisible()) {\n <div class=\"preload\">\n <div class=\"back\">\n @if (image) {\n <img\n (error)=\"loadingImageError()\"\n [ngSrc]=\"image\"\n fill\n loading=\"lazy\"\n alt=\"image loading src\"\n />\n }\n </div>\n </div>\n}\n", styles: [".preload{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.preload .back{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.preload .back img{width:100%;height:100%;object-fit:cover}.preload .back{background-color:var(--unreal-surface-screen, #fff)}\n"] }]
7723
8257
  }] });
@@ -7800,10 +8334,10 @@ class StatGraphComponent {
7800
8334
  }
7801
8335
  this.draw();
7802
8336
  }
7803
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7804
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.2", type: StatGraphComponent, isStandalone: true, selector: "app-stat-graph", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, stat: { classPropertyName: "stat", publicName: "stat", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tickStep: { classPropertyName: "tickStep", publicName: "tickStep", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "window:resize": "resize()" } }, viewQueries: [{ propertyName: "graph", first: true, predicate: ["graph"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"content\">\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\n <canvas #graph class=\"graph\"></canvas>\n</div>\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8337
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8338
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.3", type: StatGraphComponent, isStandalone: true, selector: "app-stat-graph", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, stat: { classPropertyName: "stat", publicName: "stat", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, tickStep: { classPropertyName: "tickStep", publicName: "tickStep", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "window:resize": "resize()" } }, viewQueries: [{ propertyName: "graph", first: true, predicate: ["graph"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"content\">\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\n <canvas #graph class=\"graph\"></canvas>\n</div>\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7805
8339
  }
7806
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StatGraphComponent, decorators: [{
8340
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: StatGraphComponent, decorators: [{
7807
8341
  type: Component,
7808
8342
  args: [{ selector: 'app-stat-graph', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"content\">\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\n <canvas #graph class=\"graph\"></canvas>\n</div>\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"] }]
7809
8343
  }], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: true }] }], stat: [{ type: i0.Input, args: [{ isSignal: true, alias: "stat", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], tickStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "tickStep", required: false }] }], graph: [{ type: i0.ViewChild, args: ['graph', { isSignal: true }] }], resize: [{
@@ -7818,10 +8352,10 @@ class UnrealResumeSlotDirective {
7818
8352
  static ngTemplateContextGuard(_dir, _ctx) {
7819
8353
  return true;
7820
8354
  }
7821
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealResumeSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7822
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.2", type: UnrealResumeSlotDirective, isStandalone: true, selector: "ng-template[unrealResumeSlot]", ngImport: i0 }); }
8355
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealResumeSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
8356
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: UnrealResumeSlotDirective, isStandalone: true, selector: "ng-template[unrealResumeSlot]", ngImport: i0 }); }
7823
8357
  }
7824
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealResumeSlotDirective, decorators: [{
8358
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealResumeSlotDirective, decorators: [{
7825
8359
  type: Directive,
7826
8360
  args: [{ selector: 'ng-template[unrealResumeSlot]' }]
7827
8361
  }] });
@@ -7832,10 +8366,10 @@ class UnrealAfkSlotDirective {
7832
8366
  static ngTemplateContextGuard(_dir, _ctx) {
7833
8367
  return true;
7834
8368
  }
7835
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealAfkSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7836
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.2", type: UnrealAfkSlotDirective, isStandalone: true, selector: "ng-template[unrealAfkSlot]", ngImport: i0 }); }
8369
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealAfkSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
8370
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: UnrealAfkSlotDirective, isStandalone: true, selector: "ng-template[unrealAfkSlot]", ngImport: i0 }); }
7837
8371
  }
7838
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealAfkSlotDirective, decorators: [{
8372
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealAfkSlotDirective, decorators: [{
7839
8373
  type: Directive,
7840
8374
  args: [{ selector: 'ng-template[unrealAfkSlot]' }]
7841
8375
  }] });
@@ -7846,10 +8380,10 @@ class UnrealStatusSlotDirective {
7846
8380
  static ngTemplateContextGuard(_dir, _ctx) {
7847
8381
  return true;
7848
8382
  }
7849
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7850
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.2", type: UnrealStatusSlotDirective, isStandalone: true, selector: "ng-template[unrealStatusSlot]", ngImport: i0 }); }
8383
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealStatusSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
8384
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.1.3", type: UnrealStatusSlotDirective, isStandalone: true, selector: "ng-template[unrealStatusSlot]", ngImport: i0 }); }
7851
8385
  }
7852
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusSlotDirective, decorators: [{
8386
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealStatusSlotDirective, decorators: [{
7853
8387
  type: Directive,
7854
8388
  args: [{ selector: 'ng-template[unrealStatusSlot]' }]
7855
8389
  }] });
@@ -7872,10 +8406,10 @@ class UnrealSpinnerComponent {
7872
8406
  this.viewBox = computed(() => `0 0 ${this.size()} ${this.size()}`, /* @ts-ignore */
7873
8407
  ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
7874
8408
  }
7875
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7876
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.2", type: UnrealSpinnerComponent, isStandalone: true, selector: "app-unreal-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, strokeColor: { classPropertyName: "strokeColor", publicName: "strokeColor", isSignal: true, isRequired: false, transformFunction: null }, testID: { classPropertyName: "testID", publicName: "data-testid", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n class=\"unreal-spinner-container\"\n role=\"status\"\n>\n <svg\n [attr.width]=\"size()\"\n [attr.height]=\"size()\"\n [attr.viewBox]=\"viewBox()\"\n [style.--unrealSpinnerSize]=\"size()\"\n class=\"unreal-spinner unreal-spinner--animation\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style=\"transform: rotate(-90deg)\"\n aria-hidden=\"true\"\n >\n <circle\n [attr.r]=\"radius()\"\n [attr.cx]=\"center()\"\n [attr.cy]=\"center()\"\n [attr.stroke]=\"strokeColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"transparent\"\n stroke-linecap=\"round\"\n ></circle>\n </svg>\n</div>\n", styles: [":host{display:block}.unreal-spinner-container{position:relative;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.unreal-spinner{position:relative;display:inline-block}.unreal-spinner--animation{animation:1.4s linear 0s infinite normal none running unrealSpinnerRotate}.unreal-spinner--animation circle{transform-origin:50% 50%;stroke-dasharray:calc(var(--unrealSpinnerSize) * 2 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0;animation:1.6s ease-in-out 0s infinite normal none running unrealSpinnerDash}@keyframes unrealSpinnerRotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes unrealSpinnerDash{0%{stroke-dasharray:1px,calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0}50%{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * .4 * -1px)}to{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * 3 * -1px)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8409
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8410
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.3", type: UnrealSpinnerComponent, isStandalone: true, selector: "app-unreal-spinner", inputs: { size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, strokeWidth: { classPropertyName: "strokeWidth", publicName: "strokeWidth", isSignal: true, isRequired: false, transformFunction: null }, strokeColor: { classPropertyName: "strokeColor", publicName: "strokeColor", isSignal: true, isRequired: false, transformFunction: null }, testID: { classPropertyName: "testID", publicName: "data-testid", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n class=\"unreal-spinner-container\"\n role=\"status\"\n>\n <svg\n [attr.width]=\"size()\"\n [attr.height]=\"size()\"\n [attr.viewBox]=\"viewBox()\"\n [style.--unrealSpinnerSize]=\"size()\"\n class=\"unreal-spinner unreal-spinner--animation\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style=\"transform: rotate(-90deg)\"\n aria-hidden=\"true\"\n >\n <circle\n [attr.r]=\"radius()\"\n [attr.cx]=\"center()\"\n [attr.cy]=\"center()\"\n [attr.stroke]=\"strokeColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"transparent\"\n stroke-linecap=\"round\"\n ></circle>\n </svg>\n</div>\n", styles: [":host{display:block}.unreal-spinner-container{position:relative;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.unreal-spinner{position:relative;display:inline-block}.unreal-spinner--animation{animation:1.4s linear 0s infinite normal none running unrealSpinnerRotate}.unreal-spinner--animation circle{transform-origin:50% 50%;stroke-dasharray:calc(var(--unrealSpinnerSize) * 2 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0;animation:1.6s ease-in-out 0s infinite normal none running unrealSpinnerDash}@keyframes unrealSpinnerRotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes unrealSpinnerDash{0%{stroke-dasharray:1px,calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0}50%{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * .4 * -1px)}to{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * 3 * -1px)}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7877
8411
  }
7878
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSpinnerComponent, decorators: [{
8412
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealSpinnerComponent, decorators: [{
7879
8413
  type: Component,
7880
8414
  args: [{ selector: 'app-unreal-spinner', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n [attr.data-testid]=\"testID() || null\"\n [attr.aria-label]=\"ariaLabel()\"\n class=\"unreal-spinner-container\"\n role=\"status\"\n>\n <svg\n [attr.width]=\"size()\"\n [attr.height]=\"size()\"\n [attr.viewBox]=\"viewBox()\"\n [style.--unrealSpinnerSize]=\"size()\"\n class=\"unreal-spinner unreal-spinner--animation\"\n version=\"1.1\"\n xmlns=\"http://www.w3.org/2000/svg\"\n style=\"transform: rotate(-90deg)\"\n aria-hidden=\"true\"\n >\n <circle\n [attr.r]=\"radius()\"\n [attr.cx]=\"center()\"\n [attr.cy]=\"center()\"\n [attr.stroke]=\"strokeColor()\"\n [attr.stroke-width]=\"strokeWidth()\"\n fill=\"transparent\"\n stroke-linecap=\"round\"\n ></circle>\n </svg>\n</div>\n", styles: [":host{display:block}.unreal-spinner-container{position:relative;display:flex;justify-content:center;align-items:center;width:100%;height:100%}.unreal-spinner{position:relative;display:inline-block}.unreal-spinner--animation{animation:1.4s linear 0s infinite normal none running unrealSpinnerRotate}.unreal-spinner--animation circle{transform-origin:50% 50%;stroke-dasharray:calc(var(--unrealSpinnerSize) * 2 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0;animation:1.6s ease-in-out 0s infinite normal none running unrealSpinnerDash}@keyframes unrealSpinnerRotate{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes unrealSpinnerDash{0%{stroke-dasharray:1px,calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:0}50%{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * .4 * -1px)}to{stroke-dasharray:calc(var(--unrealSpinnerSize) * 2.5 * 1px),calc(var(--unrealSpinnerSize) * 5 * 1px);stroke-dashoffset:calc(var(--unrealSpinnerSize) * 3 * -1px)}}\n"] }]
7881
8415
  }], propDecorators: { size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], strokeWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeWidth", required: false }] }], strokeColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "strokeColor", required: false }] }], testID: [{ type: i0.Input, args: [{ isSignal: true, alias: "data-testid", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
@@ -7906,10 +8440,10 @@ class UnrealStatusComponent {
7906
8440
  }
7907
8441
  #store;
7908
8442
  #loadingProgress;
7909
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7910
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowLoadingPercentsPopup()) {\n @let _awsInstance = awsInstance();\n <div [class]=\"cardSkin()\" class=\"status-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else if (_awsInstance?.message) {\n <p>{{ _awsInstance?.message }}</p>\n } @else {\n <div [class]=\"messageSkin()\">\n <app-unreal-spinner [size]=\"30\" [strokeColor]=\"'#1F2937'\" />\n {{ smoothPercents() }}%\n </div>\n }\n </div>\n}\n", styles: ["app-stat-graph{height:30px;width:100%}.status-anchor{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;pointer-events:none}.status-box{display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));border:none;border-radius:var(--unreal-radius-pill, var(--src-border-rounded-full, 9999px));box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814}.status-anchor p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));font-size:16px;font-weight:400;line-height:24px}.status-box__message .status-box__message_text{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.status-box__message app-unreal-spinner{margin-right:12px}\n"], dependencies: [{ kind: "component", type: UnrealSpinnerComponent, selector: "app-unreal-spinner", inputs: ["size", "strokeWidth", "strokeColor", "data-testid", "ariaLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8443
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8444
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowLoadingPercentsPopup()) {\n @let _awsInstance = awsInstance();\n <div [class]=\"cardSkin()\" class=\"status-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else if (_awsInstance?.message) {\n <p>{{ _awsInstance?.message }}</p>\n } @else {\n <div [class]=\"messageSkin()\">\n <app-unreal-spinner [size]=\"30\" [strokeColor]=\"'#1F2937'\" />\n {{ smoothPercents() }}%\n </div>\n }\n </div>\n}\n", styles: ["app-stat-graph{height:30px;width:100%}.status-anchor{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;pointer-events:none}.status-box{display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));border:none;border-radius:var(--unreal-radius-pill, var(--src-border-rounded-full, 9999px));box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814}.status-anchor p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));font-size:16px;font-weight:400;line-height:24px}.status-box__message .status-box__message_text{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.status-box__message app-unreal-spinner{margin-right:12px}\n"], dependencies: [{ kind: "component", type: UnrealSpinnerComponent, selector: "app-unreal-spinner", inputs: ["size", "strokeWidth", "strokeColor", "data-testid", "ariaLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7911
8445
  }
7912
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusComponent, decorators: [{
8446
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealStatusComponent, decorators: [{
7913
8447
  type: Component,
7914
8448
  args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [UnrealSpinnerComponent, NgTemplateOutlet], template: "@if (isShowLoadingPercentsPopup()) {\n @let _awsInstance = awsInstance();\n <div [class]=\"cardSkin()\" class=\"status-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else if (_awsInstance?.message) {\n <p>{{ _awsInstance?.message }}</p>\n } @else {\n <div [class]=\"messageSkin()\">\n <app-unreal-spinner [size]=\"30\" [strokeColor]=\"'#1F2937'\" />\n {{ smoothPercents() }}%\n </div>\n }\n </div>\n}\n", styles: ["app-stat-graph{height:30px;width:100%}.status-anchor{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;pointer-events:none}.status-box{display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));border:none;border-radius:var(--unreal-radius-pill, var(--src-border-rounded-full, 9999px));box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814}.status-anchor p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color:var(--unreal-text-main, var(--src-text-body-main, #1f2937));font-size:16px;font-weight:400;line-height:24px}.status-box__message .status-box__message_text{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}.status-box__message app-unreal-spinner{margin-right:12px}\n"] }]
7915
8449
  }], propDecorators: { uiClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiClasses", required: false }] }], contentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentTemplate", required: false }] }] } });
@@ -7942,10 +8476,10 @@ class SafePipe {
7942
8476
  // content instead of disabling framework security checks.
7943
8477
  return (this.sanitizer.sanitize(context, value) ?? '');
7944
8478
  }
7945
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7946
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.2", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" }); }
8479
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
8480
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.3", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" }); }
7947
8481
  }
7948
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SafePipe, decorators: [{
8482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: SafePipe, decorators: [{
7949
8483
  type: Pipe,
7950
8484
  args: [{
7951
8485
  name: 'safe',
@@ -8058,10 +8592,10 @@ class VideoStatsComponent {
8058
8592
  elements: this.elementsToShow(),
8059
8593
  }));
8060
8594
  }
8061
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8062
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList(); track graph.key) {\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\n <app-stat-graph\n [label]=\"graph.key\"\n [stat]=\"graph.stat()\"\n [color]=\"graph.color\"\n />\n </li>\n }\n </div>\n </div>\n\n @for (el of videoStatus(); track el.key + $index) {\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\n <span>{{ el.key }}: </span>{{ el.value | json }}\n </div>\n }\n </div>\n </div>\n @if (!collapsed()) {\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\n }\n}\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;width:50px;position:absolute;background:none;cursor:pointer;font-size:10px;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#fff;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}#aggregatedStats{max-height:calc(100% - 25px);overflow-y:auto;margin-top:25px;left:20px;z-index:31;max-width:400px;padding:10px;background-color:#fff3;border:1px solid grey}#aggregatedStats>*{color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}#aggregatedStats:empty{display:none}#aggregatedStats button{position:absolute;right:5px;top:5px}#aggregatedStats.min{display:none}#aggregatedStats .forNerds{width:100%}#aggregatedStats .forNerds li{list-style-type:none;height:60px}#aggregatedStats .forNerds li app-stat-graph{display:block;width:100%;height:100%}#aggregatedStats .stat span{font-weight:700}#aggregatedStats .stat,#aggregatedStats .graph{cursor:pointer}#aggregatedStats .static{pointer-events:all}.ssInfo{position:absolute;top:0;left:50%;transform:translate(-50%);color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;text-align:center;width:80%;pointer-events:none}\n"], dependencies: [{ kind: "component", type: StatGraphComponent, selector: "app-stat-graph", inputs: ["label", "stat", "color", "tickStep"] }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8595
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8596
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList(); track graph.key) {\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\n <app-stat-graph\n [label]=\"graph.key\"\n [stat]=\"graph.stat()\"\n [color]=\"graph.color\"\n />\n </li>\n }\n </div>\n </div>\n\n @for (el of videoStatus(); track el.key + $index) {\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\n <span>{{ el.key }}: </span>{{ el.value | json }}\n </div>\n }\n </div>\n </div>\n @if (!collapsed()) {\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\n }\n}\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;width:50px;position:absolute;background:none;cursor:pointer;font-size:10px;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#fff;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}#aggregatedStats{max-height:calc(100% - 25px);overflow-y:auto;margin-top:25px;left:20px;z-index:31;max-width:400px;padding:10px;background-color:#fff3;border:1px solid grey}#aggregatedStats>*{color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}#aggregatedStats:empty{display:none}#aggregatedStats button{position:absolute;right:5px;top:5px}#aggregatedStats.min{display:none}#aggregatedStats .forNerds{width:100%}#aggregatedStats .forNerds li{list-style-type:none;height:60px}#aggregatedStats .forNerds li app-stat-graph{display:block;width:100%;height:100%}#aggregatedStats .stat span{font-weight:700}#aggregatedStats .stat,#aggregatedStats .graph{cursor:pointer}#aggregatedStats .static{pointer-events:all}.ssInfo{position:absolute;top:0;left:50%;transform:translate(-50%);color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;text-align:center;width:80%;pointer-events:none}\n"], dependencies: [{ kind: "component", type: StatGraphComponent, selector: "app-stat-graph", inputs: ["label", "stat", "color", "tickStep"] }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8063
8597
  }
8064
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoStatsComponent, decorators: [{
8598
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: VideoStatsComponent, decorators: [{
8065
8599
  type: Component,
8066
8600
  args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, JsonPipe, SafePipe], template: "@if (videoStatus() && viewportReady()) {\n <div class=\"settings-container\">\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\n <div class=\"forNerds\">\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\n <li>\n <app-stat-graph\n [stat]=\"fpsTick()!\"\n label=\"FPS (higher is better)\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"videoQP()!\"\n label=\"QP (lower is better)\"\n color=\"#D5ff07\"\n />\n </li>\n <li>\n <app-stat-graph\n [stat]=\"bitrateTick()!\"\n label=\"Bitrate\"\n color=\"#D57F07\"\n />\n </li>\n </div>\n <div>\n @for (graph of graphList(); track graph.key) {\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\n <app-stat-graph\n [label]=\"graph.key\"\n [stat]=\"graph.stat()\"\n [color]=\"graph.color\"\n />\n </li>\n }\n </div>\n </div>\n\n @for (el of videoStatus(); track el.key + $index) {\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\n <span>{{ el.key }}: </span>{{ el.value | json }}\n </div>\n }\n </div>\n </div>\n @if (!collapsed()) {\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\n }\n}\n", styles: [".settings-container{position:absolute;top:65px;left:10px;z-index:1000;bottom:10px}.gear-button{left:0;width:50px;position:absolute;background:none;cursor:pointer;font-size:10px;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#fff;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}#aggregatedStats{max-height:calc(100% - 25px);overflow-y:auto;margin-top:25px;left:20px;z-index:31;max-width:400px;padding:10px;background-color:#fff3;border:1px solid grey}#aggregatedStats>*{color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000}#aggregatedStats:empty{display:none}#aggregatedStats button{position:absolute;right:5px;top:5px}#aggregatedStats.min{display:none}#aggregatedStats .forNerds{width:100%}#aggregatedStats .forNerds li{list-style-type:none;height:60px}#aggregatedStats .forNerds li app-stat-graph{display:block;width:100%;height:100%}#aggregatedStats .stat span{font-weight:700}#aggregatedStats .stat,#aggregatedStats .graph{cursor:pointer}#aggregatedStats .static{pointer-events:all}.ssInfo{position:absolute;top:0;left:50%;transform:translate(-50%);color:#fff;text-shadow:-1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;text-align:center;width:80%;pointer-events:none}\n"] }]
8067
8601
  }], ctorParameters: () => [] });
@@ -8123,10 +8657,10 @@ class ResumeStreamButtonComponent {
8123
8657
  disconnect() {
8124
8658
  this.isSecondStart.set(true);
8125
8659
  }
8126
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8127
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: ResumeStreamButtonComponent, isStandalone: true, selector: "app-resume-stream-button", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowResumeButton()) {\n <div [class]=\"cardSkin()\" class=\"resume-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n @if (isSecondStart()) {\n <div [class]=\"textSkin()\">\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\n inactivity\n </div>\n }\n\n <app-unreal-button\n (clicked)=\"connect()\"\n [fullWidth]=\"true\"\n [data-testid]=\"'connect-button'\"\n [class]=\"buttonSkin()\"\n size=\"lg\"\n weight=\"primary\"\n >\n {{ isSecondStart() ? 'Resume' : 'Start' }}\n </app-unreal-button>\n }\n </div>\n}\n", styles: [".resume-anchor{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%);z-index:6}.resume-box{padding:16px;gap:12px;display:flex;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.connect-button{width:100%}.resume-box__text{color:var(--unreal-text-secondary, var(--src-color-grey-500, #6b7280));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8660
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8661
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: ResumeStreamButtonComponent, isStandalone: true, selector: "app-resume-stream-button", inputs: { uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null }, contentTemplate: { classPropertyName: "contentTemplate", publicName: "contentTemplate", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isShowResumeButton()) {\n <div [class]=\"cardSkin()\" class=\"resume-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n @if (isSecondStart()) {\n <div [class]=\"textSkin()\">\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\n inactivity\n </div>\n }\n\n <app-unreal-button\n (clicked)=\"connect()\"\n [fullWidth]=\"true\"\n [data-testid]=\"'connect-button'\"\n [class]=\"buttonSkin()\"\n size=\"lg\"\n weight=\"primary\"\n >\n {{ isSecondStart() ? 'Resume' : 'Start' }}\n </app-unreal-button>\n }\n </div>\n}\n", styles: [".resume-anchor{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%);z-index:6}.resume-box{padding:16px;gap:12px;display:flex;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.connect-button{width:100%}.resume-box__text{color:var(--unreal-text-secondary, var(--src-color-grey-500, #6b7280));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"], dependencies: [{ kind: "component", type: UnrealButtonComponent, selector: "app-unreal-button", inputs: ["type", "weight", "size", "fullWidth", "isDisabled", "data-testid", "ariaLabel"], outputs: ["clicked"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8128
8662
  }
8129
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
8663
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
8130
8664
  type: Component,
8131
8665
  args: [{ selector: 'app-resume-stream-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [UnrealButtonComponent, NgTemplateOutlet], template: "@if (isShowResumeButton()) {\n <div [class]=\"cardSkin()\" class=\"resume-anchor\">\n @if (contentTemplate(); as tpl) {\n <ng-container\n [ngTemplateOutlet]=\"tpl\"\n [ngTemplateOutletContext]=\"{ $implicit: slotContext }\"\n />\n } @else {\n @if (isSecondStart()) {\n <div [class]=\"textSkin()\">\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\n inactivity\n </div>\n }\n\n <app-unreal-button\n (clicked)=\"connect()\"\n [fullWidth]=\"true\"\n [data-testid]=\"'connect-button'\"\n [class]=\"buttonSkin()\"\n size=\"lg\"\n weight=\"primary\"\n >\n {{ isSecondStart() ? 'Resume' : 'Start' }}\n </app-unreal-button>\n }\n </div>\n}\n", styles: [".resume-anchor{position:absolute;bottom:0;left:50%;transform:translate(-50%,-50%);z-index:6}.resume-box{padding:16px;gap:12px;display:flex;flex-direction:column;align-items:center;border-radius:var(--unreal-radius-card, var(--src-border-rounded-parent, 8px));background:var(--unreal-surface-card, var(--src-surface-container-main, #fff));box-shadow:var(--unreal-shadow-card, 0 26px 80px 0 rgba(0, 0, 0, .2), 0 0 1px 0 rgba(0, 0, 0, .2))}.connect-button{width:100%}.resume-box__text{color:var(--unreal-text-secondary, var(--src-color-grey-500, #6b7280));text-align:center;font-family:var(--unreal-font-family, var(--src-font-family-body, system-ui, sans-serif));font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"] }]
8132
8666
  }], ctorParameters: () => [], propDecorators: { uiClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiClasses", required: false }] }], contentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentTemplate", required: false }] }] } });
@@ -8193,10 +8727,10 @@ class IntroSrcComponent {
8193
8727
  sourceElement.addEventListener('error', () => this.#store.dispatch(resetIntroSrc()));
8194
8728
  videoContainer.appendChild(videoElement);
8195
8729
  }
8196
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8197
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: IntroSrcComponent, isStandalone: true, selector: "app-intro-src", viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoPlayerCover"], descendants: true, isSignal: true }], ngImport: i0, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\n </div>\n} @else if (videoIntroSrc()) {\n <div #videoPlayerCover class=\"backVideo\"></div>\n}\n", styles: [".backVideo{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backVideo video{position:absolute;width:100%;height:100%;object-fit:cover}.backImage{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backImage img{position:absolute;width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8730
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8731
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: IntroSrcComponent, isStandalone: true, selector: "app-intro-src", viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoPlayerCover"], descendants: true, isSignal: true }], ngImport: i0, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\n </div>\n} @else if (videoIntroSrc()) {\n <div #videoPlayerCover class=\"backVideo\"></div>\n}\n", styles: [".backVideo{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backVideo video{position:absolute;width:100%;height:100%;object-fit:cover}.backImage{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backImage img{position:absolute;width:100%;height:100%;object-fit:cover}\n"], dependencies: [{ kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8198
8732
  }
8199
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: IntroSrcComponent, decorators: [{
8733
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: IntroSrcComponent, decorators: [{
8200
8734
  type: Component,
8201
8735
  args: [{ selector: 'app-intro-src', imports: [NgOptimizedImage], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = imageIntroSrc();\n@if (image) {\n <div class=\"backImage\">\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\n </div>\n} @else if (videoIntroSrc()) {\n <div #videoPlayerCover class=\"backVideo\"></div>\n}\n", styles: [".backVideo{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backVideo video{position:absolute;width:100%;height:100%;object-fit:cover}.backImage{position:absolute;top:0;left:0;display:flex;justify-content:center;align-items:center;width:100%;height:100%;z-index:1}.backImage img{position:absolute;width:100%;height:100%;object-fit:cover}\n"] }]
8202
8736
  }], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
@@ -8427,10 +8961,10 @@ class UnrealSceneComponent {
8427
8961
  },
8428
8962
  });
8429
8963
  }
8430
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8431
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.2", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, resolutionSize: { classPropertyName: "resolutionSize", publicName: "resolutionSize", isSignal: true, isRequired: false, transformFunction: null }, uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()", "contextmenu": "onContextMenu($event)" } }, queries: [{ propertyName: "resumeSlot", first: true, predicate: UnrealResumeSlotDirective, descendants: true, isSignal: true }, { propertyName: "afkSlot", first: true, predicate: UnrealAfkSlotDirective, descendants: true, isSignal: true }, { propertyName: "statusSlot", first: true, predicate: UnrealStatusSlotDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\n <div #videoContainer id=\"player\"></div>\n\n <app-intro-src />\n <app-image-loading-src />\n <app-freeze-frame />\n <app-resume-stream-button\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"resumeSlot()?.template ?? null\"\n />\n <app-unreal-status\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"statusSlot()?.template ?? null\"\n />\n <app-afk-timeout-modal\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"afkSlot()?.template ?? null\"\n />\n\n <ng-content select=\"app-pdf\"></ng-content>\n <ng-content select=\"app-show-case\"></ng-content>\n\n @if (isDevMode) {\n <app-video-stats />\n }\n</div>\n", styles: [".unreal-container{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff}.unreal-container #player{position:absolute;width:100%;height:100%;display:flex;justify-content:center;align-items:center;overflow:hidden}.unreal-container.select{cursor:url('data:image/svg+xml,<svg width=\"24\" height=\"24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.15 21.375a1.423 1.423 0 0 1-1.15.063 1.487 1.487 0 0 1-.85-.788l-3-6.45-2.325 3.25c-.283.4-.658.525-1.125.375-.467-.15-.7-.466-.7-.95V4.05c0-.417.188-.717.563-.9.375-.183.729-.142 1.062.125l10.1 7.95c.383.284.496.65.337 1.1-.158.45-.479.675-.962.675h-4.2l2.975 6.375c.183.384.204.767.063 1.15a1.487 1.487 0 0 1-.788.85Z\" fill=\"%23000\"/><path d=\"m12.697 20.861.002.005c.237.495.617.852 1.128 1.04.515.191 1.039.16 1.539-.08a1.987 1.987 0 0 0 1.04-1.128 1.922 1.922 0 0 0-.079-1.536v-.003L13.684 13.5H17.1c.32 0 .628-.075.89-.26.263-.184.438-.447.544-.749.106-.3.135-.617.04-.925a1.458 1.458 0 0 0-.545-.738L7.936 2.883a1.51 1.51 0 0 0-.768-.336 1.471 1.471 0 0 0-.825.154 1.495 1.495 0 0 0-.626.547 1.492 1.492 0 0 0-.217.802v12.825c0 .324.08.634.272.897.193.261.467.43.775.53.31.099.632.12.942.016.31-.103.555-.313.743-.578h.001l1.825-2.552 2.639 5.673Z\" stroke=\"%23fff\" stroke-opacity=\".7\"/></svg>') 6 2,auto}.unreal-container.orbit{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.998 21.6c-1.966 0-3.737-.53-5.312-1.588-1.575-1.058-2.738-2.437-3.488-4.137a.852.852 0 0 1 .013-.713.867.867 0 0 1 .512-.487.86.86 0 0 1 .688.037c.225.109.387.28.487.513.617 1.35 1.559 2.45 2.825 3.3 1.267.85 2.692 1.275 4.275 1.275 1.384 0 2.646-.33 3.788-.988a7.985 7.985 0 0 0 2.762-2.612h-.85a.873.873 0 0 1-.64-.257.863.863 0 0 1-.26-.638.88.88 0 0 1 .26-.643.864.864 0 0 1 .64-.262h3c.255 0 .47.086.642.259a.87.87 0 0 1 .258.64v3a.874.874 0 0 1-.257.642.863.863 0 0 1-.637.259.88.88 0 0 1-.643-.259.865.865 0 0 1-.263-.641v-.725a9.95 9.95 0 0 1-3.35 2.925c-1.35.733-2.833 1.1-4.45 1.1Zm0-17.4c-1.383 0-2.645.33-3.787.987A7.985 7.985 0 0 0 5.448 7.8h.85c.255 0 .47.086.642.257a.863.863 0 0 1 .258.638.88.88 0 0 1-.258.642.864.864 0 0 1-.642.263h-3a.87.87 0 0 1-.64-.259.87.87 0 0 1-.26-.641v-3c0-.255.086-.469.258-.641a.863.863 0 0 1 .637-.26.88.88 0 0 1 .643.26.864.864 0 0 1 .262.64v.726A9.95 9.95 0 0 1 7.548 3.5c1.35-.733 2.834-1.1 4.45-1.1 2 0 3.788.542 5.363 1.625 1.575 1.083 2.736 2.49 3.482 4.22a.724.724 0 0 1-.045.646.926.926 0 0 1-.525.437.87.87 0 0 1-.687-.04.977.977 0 0 1-.488-.513c-.616-1.35-1.558-2.45-2.825-3.3-1.266-.85-2.691-1.275-4.275-1.275Zm0 10.8a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 8.998 12c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 11.998 9c.834 0 1.542.292 2.125.875.584.583.875 1.292.875 2.125s-.291 1.542-.875 2.125a2.893 2.893 0 0 1-2.125.875Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"m2.739 16.072.002.005c.789 1.788 2.014 3.24 3.666 4.35 1.66 1.116 3.53 1.673 5.591 1.673 1.696 0 3.262-.386 4.69-1.16a10.54 10.54 0 0 0 2.714-2.097c.069.167.171.32.308.454.272.268.613.403.994.403.38 0 .722-.135.992-.406s.402-.614.402-.994v-3c0-.381-.134-.724-.405-.995a1.37 1.37 0 0 0-.995-.405h-3c-.383 0-.727.137-.997.411a1.38 1.38 0 0 0-.403.994c0 .38.135.723.407.992.238.237.532.369.858.397a7.552 7.552 0 0 1-2.027 1.685c-1.062.613-2.237.92-3.538.92-1.488 0-2.815-.397-3.996-1.19-1.19-.798-2.07-1.826-2.647-3.087-.147-.34-.393-.6-.727-.76a1.36 1.36 0 0 0-1.073-.058c-.37.133-.64.397-.8.754a1.351 1.351 0 0 0-.016 1.114Zm4.553-8.37a1.36 1.36 0 0 0-.858-.396A7.552 7.552 0 0 1 8.46 5.62c1.062-.613 2.237-.921 3.537-.921 1.489 0 2.816.398 3.997 1.19 1.19.8 2.07 1.827 2.646 3.088.148.34.394.6.728.76a1.37 1.37 0 0 0 1.068.063c.347-.12.62-.346.799-.668.187-.338.215-.71.072-1.072l-.003-.007-.003-.007c-.784-1.818-2.006-3.298-3.658-4.434C15.982 2.47 14.094 1.9 11.998 1.9c-1.695 0-3.261.385-4.688 1.16a10.536 10.536 0 0 0-2.715 2.096 1.364 1.364 0 0 0-.308-.453 1.38 1.38 0 0 0-.994-.403c-.38 0-.722.135-.992.406s-.403.614-.403.994v3c0 .38.135.724.406.995.27.27.614.405.994.405h3c.384 0 .727-.137.998-.412a1.38 1.38 0 0 0 .402-.993c0-.38-.135-.723-.406-.992ZM9.52 14.48a3.393 3.393 0 0 0 2.478 1.02c.965 0 1.8-.342 2.479-1.02a3.392 3.392 0 0 0 1.021-2.48c0-.963-.343-1.8-1.021-2.478A3.392 3.392 0 0 0 11.998 8.5c-.964 0-1.8.343-2.478 1.021A3.392 3.392 0 0 0 8.498 12c0 .964.344 1.8 1.022 2.479Z\"/></svg>') 12 12,auto}.unreal-container.pan{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.997 10.075a1.01 1.01 0 0 1-.738-.313 1.008 1.008 0 0 1-.312-.737V6.15l-.95.95a.998.998 0 0 1-.763.312 1.09 1.09 0 0 1-.763-.337 1.06 1.06 0 0 1-.324-.775c0-.3.108-.558.324-.775l2.776-2.775c.117-.116.237-.2.362-.25a1.04 1.04 0 0 1 .775 0c.125.05.246.134.363.25l2.8 2.8c.216.217.32.47.313.762a1.096 1.096 0 0 1-.338.763 1.058 1.058 0 0 1-.775.325c-.3 0-.558-.108-.775-.325l-.925-.925v2.875c0 .283-.104.53-.312.737a1.01 1.01 0 0 1-.738.313Zm0 11.5a1.04 1.04 0 0 1-.388-.075 1.103 1.103 0 0 1-.362-.25l-2.8-2.8a1.002 1.002 0 0 1-.313-.762c.009-.292.121-.546.337-.763.217-.217.475-.325.775-.325.3 0 .559.108.775.325l.926.925v-2.875c0-.283.104-.529.312-.737a1.01 1.01 0 0 1 .738-.313c.283 0 .529.104.738.313.208.208.312.454.312.737v2.875l.95-.95a.998.998 0 0 1 .762-.312c.292.008.546.12.763.337.216.217.324.475.324.775 0 .3-.108.559-.324.775l-2.775 2.775c-.117.116-.238.2-.363.25a1.04 1.04 0 0 1-.387.075Zm4.925-6.05a1.058 1.058 0 0 1-.326-.774c0-.3.109-.559.326-.776l.925-.925H14.97c-.283 0-.528-.104-.736-.312a1.01 1.01 0 0 1-.313-.738c0-.283.104-.53.313-.738.208-.208.453-.312.737-.312h2.875l-.95-.95a.998.998 0 0 1-.313-.762 1.09 1.09 0 0 1 .338-.764 1.06 1.06 0 0 1 .775-.324c.3 0 .558.108.775.324l2.774 2.776c.117.117.2.238.251.363a1.04 1.04 0 0 1 0 .775c-.05.124-.134.245-.25.362l-2.8 2.8c-.217.216-.471.32-.762.313a1.096 1.096 0 0 1-.763-.337Zm-11.4 0L2.746 12.75a1.103 1.103 0 0 1-.25-.362 1.04 1.04 0 0 1 0-.775c.05-.125.133-.246.25-.363l2.8-2.8c.216-.216.47-.32.762-.313.292.01.546.121.763.337.217.217.325.476.325.776 0 .3-.108.558-.325.775l-.925.925h2.875c.283 0 .529.104.737.312.208.209.313.455.313.738s-.105.53-.313.738a1.008 1.008 0 0 1-.737.312H6.147l.95.95c.217.217.32.471.312.762a1.09 1.09 0 0 1-.337.764 1.06 1.06 0 0 1-.775.324c-.3 0-.559-.108-.776-.324Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M9.22 7.912c.436.013.821-.15 1.13-.458l.097-.097v1.668c0 .422.16.793.458 1.09.298.299.67.46 1.092.46.422 0 .793-.161 1.091-.46.298-.297.459-.668.459-1.09V7.357l.071.072c.309.308.693.471 1.13.471.435 0 .82-.163 1.128-.471.301-.303.47-.676.483-1.102v-.001a1.501 1.501 0 0 0-.46-1.13l-2.799-2.8a1.6 1.6 0 0 0-.528-.36l-.003-.001a1.54 1.54 0 0 0-1.145 0l-.002.001a1.6 1.6 0 0 0-.529.36L8.118 5.172l-.001.001a1.56 1.56 0 0 0-.47 1.128c0 .435.163.82.47 1.128.303.303.676.472 1.103.484Zm0 0 .014-.5-.015.5Zm-1.311 6.865a1.497 1.497 0 0 0-.458-1.13l-.097-.097h1.668c.421 0 .793-.16 1.09-.458.298-.299.46-.67.46-1.092a1.51 1.51 0 0 0-.46-1.091 1.507 1.507 0 0 0-1.09-.459H7.354l.071-.072c.309-.308.472-.692.472-1.128 0-.436-.163-.82-.472-1.13a1.596 1.596 0 0 0-1.101-.482h-.002a1.501 1.501 0 0 0-1.129.459l-2.8 2.8a1.6 1.6 0 0 0-.36.528l-.001.003a1.54 1.54 0 0 0 0 1.144v.003c.081.2.208.375.362.529l2.774 2.775c.309.308.693.47 1.129.47.435 0 .82-.162 1.128-.47.303-.302.472-.676.484-1.102Zm0 0-.5-.014.5.015v-.001Zm3.038-8.627-.5.5.5.207V6.15Zm.475 15.814.002.001a1.54 1.54 0 0 0 1.145 0h.002c.2-.081.375-.208.53-.362l2.775-2.774a1.56 1.56 0 0 0 .47-1.129c0-.435-.162-.82-.47-1.128a1.589 1.589 0 0 0-1.103-.484 1.497 1.497 0 0 0-1.13.458l-.096.097v-1.668c0-.421-.161-.793-.459-1.09a1.509 1.509 0 0 0-1.091-.46c-.422 0-.794.161-1.092.46a1.507 1.507 0 0 0-.458 1.09v1.668l-.072-.072a1.558 1.558 0 0 0-1.129-.471c-.436 0-.82.163-1.128.471l-.001.001c-.301.302-.47.675-.483 1.101v.001c-.012.437.151.821.46 1.13l2.8 2.8c.153.153.328.28.528.36Zm5.146-6.085c.303.302.676.47 1.102.484a1.5 1.5 0 0 0 1.13-.46l2.8-2.8a1.6 1.6 0 0 0 .36-.528l.002-.003a1.54 1.54 0 0 0 0-1.144l-.001-.003a1.6 1.6 0 0 0-.361-.529l-2.774-2.775h-.001a1.56 1.56 0 0 0-1.128-.47c-.436 0-.82.162-1.128.47h-.001a1.59 1.59 0 0 0-.484 1.102c-.012.437.15.822.459 1.13l.097.097h-1.668c-.422 0-.793.16-1.09.459-.299.298-.46.67-.46 1.091 0 .422.161.793.46 1.092.297.297.668.458 1.09.458h1.668l-.072.072a1.558 1.558 0 0 0-.472 1.129c0 .435.164.82.472 1.128ZM6.147 13.05l.5.5.207-.5h-.707Z\"/></svg>') 12 12,auto}.unreal-container.dolly{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><mask id=\"a\" width=\"24\" height=\"24\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"%23127392\" d=\"M0 0h24v24H0z\"/></mask><g mask=\"url(%23a)\"><path fill=\"%23000\" d=\"M12.002 20.575a1.026 1.026 0 0 1-.75-.325l-2.55-2.55a.933.933 0 0 1-.287-.713c.008-.275.113-.513.313-.713.2-.2.441-.3.725-.3.283 0 .524.1.724.3l.776.776V6.95l-.8.8c-.2.2-.438.296-.712.288a1.018 1.018 0 0 1-.713-.312c-.2-.2-.3-.442-.3-.726s.1-.525.3-.725l2.524-2.525a1.026 1.026 0 0 1 1.5 0l2.55 2.55c.2.2.296.438.289.713a1.023 1.023 0 0 1-.314.713c-.2.2-.441.3-.724.3a.988.988 0 0 1-.725-.3l-.775-.776v10.1l.8-.8c.2-.2.437-.296.712-.288.275.008.512.112.712.312.2.2.3.442.3.726s-.1.525-.3.725l-2.524 2.525a1.026 1.026 0 0 1-.75.325Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M13.553 8.153c.28.245.62.373 1 .373.415 0 .784-.153 1.078-.447.287-.287.446-.644.46-1.05v-.002a1.432 1.432 0 0 0-.435-1.08l-2.55-2.55-.354.353.354-.354a1.526 1.526 0 0 0-2.207 0L8.374 5.921A1.487 1.487 0 0 0 7.928 7c0 .416.152.785.446 1.08.288.287.645.446 1.052.457l4.127-.384Zm0 0v7.693m0-7.693a1.667 1.667 0 0 1-.079-.074l-3.021.075v7.693a1.482 1.482 0 0 0-1-.373c-.416 0-.785.153-1.079.447a1.522 1.522 0 0 0-.46 1.05v.002c-.011.417.14.786.435 1.08l2.55 2.55a1.527 1.527 0 0 0 2.207 0l2.525-2.524c.294-.294.446-.663.446-1.079 0-.416-.152-.785-.446-1.08a1.518 1.518 0 0 0-1.052-.457m0 0-.014.5.014-.5Zm0 0a1.424 1.424 0 0 0-1.027.383m0 0a1.583 1.583 0 0 0-.053.05l.354.354m-.3-.404v.104l.3.3m0 0-.3-.3v.6l.3-.3Z\"/></g></svg>%0A') 12 12,auto}.unreal-container.disabled:before{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}#playerUI{position:absolute;z-index:10;width:100%}#statsContainer{display:none;text-align:left;background-color:#000c}#stats{padding:6px;font-weight:700;font-size:14px;color:#0f0}canvas{position:absolute;image-rendering:crisp-edges}#overlay{position:absolute;top:0;right:2%;z-index:100;padding:4px;border-top-width:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-bottomright:5px;border-bottom-right-radius:5px;-webkit-border-bottom-left-radius:5px;-moz-border-radius-bottomleft:5px;border-bottom-left-radius:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none}.overlay{font-weight:lighter;font-family:var(--src-font-family-body, system-ui, sans-serif)}#overlayButton:hover{cursor:pointer}#overlayButton{font-size:40px;text-align:right}#overlaySettings{display:none;width:300px;padding:4px}#videoMessageOverlay{position:absolute;z-index:20;width:100%;margin:auto;font-size:1.8em;font-family:var(--src-font-family-body, system-ui, sans-serif)}#playButton{display:inline-block;height:auto}img#playButton{width:10%;max-width:241px}#UIInteraction{position:fixed}#UIInteractionButtonBoundary{padding:2px}#UIInteractionButton{cursor:pointer}#hiddenInput{position:absolute;left:-10%;width:0;opacity:0}#editTextButton{position:absolute;width:40px;height:40px}\n"], dependencies: [{ kind: "component", type: AfkTimeoutModalComponent, selector: "app-afk-timeout-modal", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: FreezeFrameComponent, selector: "app-freeze-frame" }, { kind: "component", type: VideoStatsComponent, selector: "app-video-stats" }, { kind: "component", type: ResumeStreamButtonComponent, selector: "app-resume-stream-button", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8964
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8965
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.3", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, resolutionSize: { classPropertyName: "resolutionSize", publicName: "resolutionSize", isSignal: true, isRequired: false, transformFunction: null }, uiClasses: { classPropertyName: "uiClasses", publicName: "uiClasses", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()", "contextmenu": "onContextMenu($event)" } }, queries: [{ propertyName: "resumeSlot", first: true, predicate: UnrealResumeSlotDirective, descendants: true, isSignal: true }, { propertyName: "afkSlot", first: true, predicate: UnrealAfkSlotDirective, descendants: true, isSignal: true }, { propertyName: "statusSlot", first: true, predicate: UnrealStatusSlotDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\n <div #videoContainer id=\"player\"></div>\n\n <app-intro-src />\n <app-image-loading-src />\n <app-freeze-frame />\n <app-resume-stream-button\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"resumeSlot()?.template ?? null\"\n />\n <app-unreal-status\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"statusSlot()?.template ?? null\"\n />\n <app-afk-timeout-modal\n [uiClasses]=\"uiClasses()\"\n [contentTemplate]=\"afkSlot()?.template ?? null\"\n />\n\n <ng-content select=\"app-pdf\"></ng-content>\n <ng-content select=\"app-show-case\"></ng-content>\n\n @if (isDevMode) {\n <app-video-stats />\n }\n</div>\n", styles: [".unreal-container{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff}.unreal-container #player{position:absolute;width:100%;height:100%;display:flex;justify-content:center;align-items:center;overflow:hidden}.unreal-container.select{cursor:url('data:image/svg+xml,<svg width=\"24\" height=\"24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M15.15 21.375a1.423 1.423 0 0 1-1.15.063 1.487 1.487 0 0 1-.85-.788l-3-6.45-2.325 3.25c-.283.4-.658.525-1.125.375-.467-.15-.7-.466-.7-.95V4.05c0-.417.188-.717.563-.9.375-.183.729-.142 1.062.125l10.1 7.95c.383.284.496.65.337 1.1-.158.45-.479.675-.962.675h-4.2l2.975 6.375c.183.384.204.767.063 1.15a1.487 1.487 0 0 1-.788.85Z\" fill=\"%23000\"/><path d=\"m12.697 20.861.002.005c.237.495.617.852 1.128 1.04.515.191 1.039.16 1.539-.08a1.987 1.987 0 0 0 1.04-1.128 1.922 1.922 0 0 0-.079-1.536v-.003L13.684 13.5H17.1c.32 0 .628-.075.89-.26.263-.184.438-.447.544-.749.106-.3.135-.617.04-.925a1.458 1.458 0 0 0-.545-.738L7.936 2.883a1.51 1.51 0 0 0-.768-.336 1.471 1.471 0 0 0-.825.154 1.495 1.495 0 0 0-.626.547 1.492 1.492 0 0 0-.217.802v12.825c0 .324.08.634.272.897.193.261.467.43.775.53.31.099.632.12.942.016.31-.103.555-.313.743-.578h.001l1.825-2.552 2.639 5.673Z\" stroke=\"%23fff\" stroke-opacity=\".7\"/></svg>') 6 2,auto}.unreal-container.orbit{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.998 21.6c-1.966 0-3.737-.53-5.312-1.588-1.575-1.058-2.738-2.437-3.488-4.137a.852.852 0 0 1 .013-.713.867.867 0 0 1 .512-.487.86.86 0 0 1 .688.037c.225.109.387.28.487.513.617 1.35 1.559 2.45 2.825 3.3 1.267.85 2.692 1.275 4.275 1.275 1.384 0 2.646-.33 3.788-.988a7.985 7.985 0 0 0 2.762-2.612h-.85a.873.873 0 0 1-.64-.257.863.863 0 0 1-.26-.638.88.88 0 0 1 .26-.643.864.864 0 0 1 .64-.262h3c.255 0 .47.086.642.259a.87.87 0 0 1 .258.64v3a.874.874 0 0 1-.257.642.863.863 0 0 1-.637.259.88.88 0 0 1-.643-.259.865.865 0 0 1-.263-.641v-.725a9.95 9.95 0 0 1-3.35 2.925c-1.35.733-2.833 1.1-4.45 1.1Zm0-17.4c-1.383 0-2.645.33-3.787.987A7.985 7.985 0 0 0 5.448 7.8h.85c.255 0 .47.086.642.257a.863.863 0 0 1 .258.638.88.88 0 0 1-.258.642.864.864 0 0 1-.642.263h-3a.87.87 0 0 1-.64-.259.87.87 0 0 1-.26-.641v-3c0-.255.086-.469.258-.641a.863.863 0 0 1 .637-.26.88.88 0 0 1 .643.26.864.864 0 0 1 .262.64v.726A9.95 9.95 0 0 1 7.548 3.5c1.35-.733 2.834-1.1 4.45-1.1 2 0 3.788.542 5.363 1.625 1.575 1.083 2.736 2.49 3.482 4.22a.724.724 0 0 1-.045.646.926.926 0 0 1-.525.437.87.87 0 0 1-.687-.04.977.977 0 0 1-.488-.513c-.616-1.35-1.558-2.45-2.825-3.3-1.266-.85-2.691-1.275-4.275-1.275Zm0 10.8a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 8.998 12c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 11.998 9c.834 0 1.542.292 2.125.875.584.583.875 1.292.875 2.125s-.291 1.542-.875 2.125a2.893 2.893 0 0 1-2.125.875Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"m2.739 16.072.002.005c.789 1.788 2.014 3.24 3.666 4.35 1.66 1.116 3.53 1.673 5.591 1.673 1.696 0 3.262-.386 4.69-1.16a10.54 10.54 0 0 0 2.714-2.097c.069.167.171.32.308.454.272.268.613.403.994.403.38 0 .722-.135.992-.406s.402-.614.402-.994v-3c0-.381-.134-.724-.405-.995a1.37 1.37 0 0 0-.995-.405h-3c-.383 0-.727.137-.997.411a1.38 1.38 0 0 0-.403.994c0 .38.135.723.407.992.238.237.532.369.858.397a7.552 7.552 0 0 1-2.027 1.685c-1.062.613-2.237.92-3.538.92-1.488 0-2.815-.397-3.996-1.19-1.19-.798-2.07-1.826-2.647-3.087-.147-.34-.393-.6-.727-.76a1.36 1.36 0 0 0-1.073-.058c-.37.133-.64.397-.8.754a1.351 1.351 0 0 0-.016 1.114Zm4.553-8.37a1.36 1.36 0 0 0-.858-.396A7.552 7.552 0 0 1 8.46 5.62c1.062-.613 2.237-.921 3.537-.921 1.489 0 2.816.398 3.997 1.19 1.19.8 2.07 1.827 2.646 3.088.148.34.394.6.728.76a1.37 1.37 0 0 0 1.068.063c.347-.12.62-.346.799-.668.187-.338.215-.71.072-1.072l-.003-.007-.003-.007c-.784-1.818-2.006-3.298-3.658-4.434C15.982 2.47 14.094 1.9 11.998 1.9c-1.695 0-3.261.385-4.688 1.16a10.536 10.536 0 0 0-2.715 2.096 1.364 1.364 0 0 0-.308-.453 1.38 1.38 0 0 0-.994-.403c-.38 0-.722.135-.992.406s-.403.614-.403.994v3c0 .38.135.724.406.995.27.27.614.405.994.405h3c.384 0 .727-.137.998-.412a1.38 1.38 0 0 0 .402-.993c0-.38-.135-.723-.406-.992ZM9.52 14.48a3.393 3.393 0 0 0 2.478 1.02c.965 0 1.8-.342 2.479-1.02a3.392 3.392 0 0 0 1.021-2.48c0-.963-.343-1.8-1.021-2.478A3.392 3.392 0 0 0 11.998 8.5c-.964 0-1.8.343-2.478 1.021A3.392 3.392 0 0 0 8.498 12c0 .964.344 1.8 1.022 2.479Z\"/></svg>') 12 12,auto}.unreal-container.pan{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><path fill=\"%23000\" d=\"M11.997 10.075a1.01 1.01 0 0 1-.738-.313 1.008 1.008 0 0 1-.312-.737V6.15l-.95.95a.998.998 0 0 1-.763.312 1.09 1.09 0 0 1-.763-.337 1.06 1.06 0 0 1-.324-.775c0-.3.108-.558.324-.775l2.776-2.775c.117-.116.237-.2.362-.25a1.04 1.04 0 0 1 .775 0c.125.05.246.134.363.25l2.8 2.8c.216.217.32.47.313.762a1.096 1.096 0 0 1-.338.763 1.058 1.058 0 0 1-.775.325c-.3 0-.558-.108-.775-.325l-.925-.925v2.875c0 .283-.104.53-.312.737a1.01 1.01 0 0 1-.738.313Zm0 11.5a1.04 1.04 0 0 1-.388-.075 1.103 1.103 0 0 1-.362-.25l-2.8-2.8a1.002 1.002 0 0 1-.313-.762c.009-.292.121-.546.337-.763.217-.217.475-.325.775-.325.3 0 .559.108.775.325l.926.925v-2.875c0-.283.104-.529.312-.737a1.01 1.01 0 0 1 .738-.313c.283 0 .529.104.738.313.208.208.312.454.312.737v2.875l.95-.95a.998.998 0 0 1 .762-.312c.292.008.546.12.763.337.216.217.324.475.324.775 0 .3-.108.559-.324.775l-2.775 2.775c-.117.116-.238.2-.363.25a1.04 1.04 0 0 1-.387.075Zm4.925-6.05a1.058 1.058 0 0 1-.326-.774c0-.3.109-.559.326-.776l.925-.925H14.97c-.283 0-.528-.104-.736-.312a1.01 1.01 0 0 1-.313-.738c0-.283.104-.53.313-.738.208-.208.453-.312.737-.312h2.875l-.95-.95a.998.998 0 0 1-.313-.762 1.09 1.09 0 0 1 .338-.764 1.06 1.06 0 0 1 .775-.324c.3 0 .558.108.775.324l2.774 2.776c.117.117.2.238.251.363a1.04 1.04 0 0 1 0 .775c-.05.124-.134.245-.25.362l-2.8 2.8c-.217.216-.471.32-.762.313a1.096 1.096 0 0 1-.763-.337Zm-11.4 0L2.746 12.75a1.103 1.103 0 0 1-.25-.362 1.04 1.04 0 0 1 0-.775c.05-.125.133-.246.25-.363l2.8-2.8c.216-.216.47-.32.762-.313.292.01.546.121.763.337.217.217.325.476.325.776 0 .3-.108.558-.325.775l-.925.925h2.875c.283 0 .529.104.737.312.208.209.313.455.313.738s-.105.53-.313.738a1.008 1.008 0 0 1-.737.312H6.147l.95.95c.217.217.32.471.312.762a1.09 1.09 0 0 1-.337.764 1.06 1.06 0 0 1-.775.324c-.3 0-.559-.108-.776-.324Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M9.22 7.912c.436.013.821-.15 1.13-.458l.097-.097v1.668c0 .422.16.793.458 1.09.298.299.67.46 1.092.46.422 0 .793-.161 1.091-.46.298-.297.459-.668.459-1.09V7.357l.071.072c.309.308.693.471 1.13.471.435 0 .82-.163 1.128-.471.301-.303.47-.676.483-1.102v-.001a1.501 1.501 0 0 0-.46-1.13l-2.799-2.8a1.6 1.6 0 0 0-.528-.36l-.003-.001a1.54 1.54 0 0 0-1.145 0l-.002.001a1.6 1.6 0 0 0-.529.36L8.118 5.172l-.001.001a1.56 1.56 0 0 0-.47 1.128c0 .435.163.82.47 1.128.303.303.676.472 1.103.484Zm0 0 .014-.5-.015.5Zm-1.311 6.865a1.497 1.497 0 0 0-.458-1.13l-.097-.097h1.668c.421 0 .793-.16 1.09-.458.298-.299.46-.67.46-1.092a1.51 1.51 0 0 0-.46-1.091 1.507 1.507 0 0 0-1.09-.459H7.354l.071-.072c.309-.308.472-.692.472-1.128 0-.436-.163-.82-.472-1.13a1.596 1.596 0 0 0-1.101-.482h-.002a1.501 1.501 0 0 0-1.129.459l-2.8 2.8a1.6 1.6 0 0 0-.36.528l-.001.003a1.54 1.54 0 0 0 0 1.144v.003c.081.2.208.375.362.529l2.774 2.775c.309.308.693.47 1.129.47.435 0 .82-.162 1.128-.47.303-.302.472-.676.484-1.102Zm0 0-.5-.014.5.015v-.001Zm3.038-8.627-.5.5.5.207V6.15Zm.475 15.814.002.001a1.54 1.54 0 0 0 1.145 0h.002c.2-.081.375-.208.53-.362l2.775-2.774a1.56 1.56 0 0 0 .47-1.129c0-.435-.162-.82-.47-1.128a1.589 1.589 0 0 0-1.103-.484 1.497 1.497 0 0 0-1.13.458l-.096.097v-1.668c0-.421-.161-.793-.459-1.09a1.509 1.509 0 0 0-1.091-.46c-.422 0-.794.161-1.092.46a1.507 1.507 0 0 0-.458 1.09v1.668l-.072-.072a1.558 1.558 0 0 0-1.129-.471c-.436 0-.82.163-1.128.471l-.001.001c-.301.302-.47.675-.483 1.101v.001c-.012.437.151.821.46 1.13l2.8 2.8c.153.153.328.28.528.36Zm5.146-6.085c.303.302.676.47 1.102.484a1.5 1.5 0 0 0 1.13-.46l2.8-2.8a1.6 1.6 0 0 0 .36-.528l.002-.003a1.54 1.54 0 0 0 0-1.144l-.001-.003a1.6 1.6 0 0 0-.361-.529l-2.774-2.775h-.001a1.56 1.56 0 0 0-1.128-.47c-.436 0-.82.162-1.128.47h-.001a1.59 1.59 0 0 0-.484 1.102c-.012.437.15.822.459 1.13l.097.097h-1.668c-.422 0-.793.16-1.09.459-.299.298-.46.67-.46 1.091 0 .422.161.793.46 1.092.297.297.668.458 1.09.458h1.668l-.072.072a1.558 1.558 0 0 0-.472 1.129c0 .435.164.82.472 1.128ZM6.147 13.05l.5.5.207-.5h-.707Z\"/></svg>') 12 12,auto}.unreal-container.dolly{cursor:url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" fill=\"none\"><mask id=\"a\" width=\"24\" height=\"24\" x=\"0\" y=\"0\" maskUnits=\"userSpaceOnUse\" style=\"mask-type:alpha\"><path fill=\"%23127392\" d=\"M0 0h24v24H0z\"/></mask><g mask=\"url(%23a)\"><path fill=\"%23000\" d=\"M12.002 20.575a1.026 1.026 0 0 1-.75-.325l-2.55-2.55a.933.933 0 0 1-.287-.713c.008-.275.113-.513.313-.713.2-.2.441-.3.725-.3.283 0 .524.1.724.3l.776.776V6.95l-.8.8c-.2.2-.438.296-.712.288a1.018 1.018 0 0 1-.713-.312c-.2-.2-.3-.442-.3-.726s.1-.525.3-.725l2.524-2.525a1.026 1.026 0 0 1 1.5 0l2.55 2.55c.2.2.296.438.289.713a1.023 1.023 0 0 1-.314.713c-.2.2-.441.3-.724.3a.988.988 0 0 1-.725-.3l-.775-.776v10.1l.8-.8c.2-.2.437-.296.712-.288.275.008.512.112.712.312.2.2.3.442.3.726s-.1.525-.3.725l-2.524 2.525a1.026 1.026 0 0 1-.75.325Z\"/><path stroke=\"%23fff\" stroke-opacity=\".7\" d=\"M13.553 8.153c.28.245.62.373 1 .373.415 0 .784-.153 1.078-.447.287-.287.446-.644.46-1.05v-.002a1.432 1.432 0 0 0-.435-1.08l-2.55-2.55-.354.353.354-.354a1.526 1.526 0 0 0-2.207 0L8.374 5.921A1.487 1.487 0 0 0 7.928 7c0 .416.152.785.446 1.08.288.287.645.446 1.052.457l4.127-.384Zm0 0v7.693m0-7.693a1.667 1.667 0 0 1-.079-.074l-3.021.075v7.693a1.482 1.482 0 0 0-1-.373c-.416 0-.785.153-1.079.447a1.522 1.522 0 0 0-.46 1.05v.002c-.011.417.14.786.435 1.08l2.55 2.55a1.527 1.527 0 0 0 2.207 0l2.525-2.524c.294-.294.446-.663.446-1.079 0-.416-.152-.785-.446-1.08a1.518 1.518 0 0 0-1.052-.457m0 0-.014.5.014-.5Zm0 0a1.424 1.424 0 0 0-1.027.383m0 0a1.583 1.583 0 0 0-.053.05l.354.354m-.3-.404v.104l.3.3m0 0-.3-.3v.6l.3-.3Z\"/></g></svg>%0A') 12 12,auto}.unreal-container.disabled:before{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}#playerUI{position:absolute;z-index:10;width:100%}#statsContainer{display:none;text-align:left;background-color:#000c}#stats{padding:6px;font-weight:700;font-size:14px;color:#0f0}canvas{position:absolute;image-rendering:crisp-edges}#overlay{position:absolute;top:0;right:2%;z-index:100;padding:4px;border-top-width:0;-webkit-border-bottom-right-radius:5px;-moz-border-radius-bottomright:5px;border-bottom-right-radius:5px;-webkit-border-bottom-left-radius:5px;-moz-border-radius-bottomleft:5px;border-bottom-left-radius:5px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-touch-callout:none}.overlay{font-weight:lighter;font-family:var(--src-font-family-body, system-ui, sans-serif)}#overlayButton:hover{cursor:pointer}#overlayButton{font-size:40px;text-align:right}#overlaySettings{display:none;width:300px;padding:4px}#videoMessageOverlay{position:absolute;z-index:20;width:100%;margin:auto;font-size:1.8em;font-family:var(--src-font-family-body, system-ui, sans-serif)}#playButton{display:inline-block;height:auto}img#playButton{width:10%;max-width:241px}#UIInteraction{position:fixed}#UIInteractionButtonBoundary{padding:2px}#UIInteractionButton{cursor:pointer}#hiddenInput{position:absolute;left:-10%;width:0;opacity:0}#editTextButton{position:absolute;width:40px;height:40px}\n"], dependencies: [{ kind: "component", type: AfkTimeoutModalComponent, selector: "app-afk-timeout-modal", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: FreezeFrameComponent, selector: "app-freeze-frame" }, { kind: "component", type: VideoStatsComponent, selector: "app-video-stats" }, { kind: "component", type: ResumeStreamButtonComponent, selector: "app-resume-stream-button", inputs: ["uiClasses", "contentTemplate"] }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
8432
8966
  }
8433
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSceneComponent, decorators: [{
8967
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.3", ngImport: i0, type: UnrealSceneComponent, decorators: [{
8434
8968
  type: Component,
8435
8969
  args: [{ selector: 'app-unreal-scene', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8436
8970
  AfkTimeoutModalComponent,
@@ -8500,5 +9034,5 @@ function provideUnrealModule() {
8500
9034
  * Generated bundle index. Do not edit.
8501
9035
  */
8502
9036
 
8503
- 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 };
9037
+ 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 };
8504
9038
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map