@3dsource/angular-unreal-module 0.0.175 → 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.
@@ -1,11 +1,13 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, output, ChangeDetectionStrategy, Component, InjectionToken, inject, DestroyRef, Injectable, signal, effect, untracked, computed, ElementRef, viewChild, afterNextRender, HostListener, TemplateRef, Directive, SecurityContext, Pipe, contentChild, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
2
+ import { input, output, ChangeDetectionStrategy, Component, inject, DestroyRef, Injectable, signal, effect, untracked, computed, ElementRef, viewChild, afterNextRender, HostListener, TemplateRef, Directive, SecurityContext, Pipe, contentChild, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
3
3
  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
- 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';
7
+ import { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
8
+ export { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
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';
9
11
  import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
10
12
  import { HttpClient } from '@angular/common/http';
11
13
  import { ScrollStrategyOptions } from '@angular/cdk/overlay';
@@ -34,10 +36,10 @@ class UnrealButtonComponent {
34
36
  event.preventDefault();
35
37
  this.clicked.emit(event);
36
38
  }
37
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
38
- 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 }); }
39
41
  }
40
- 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: [{
41
43
  type: Component,
42
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"] }]
43
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"] }] } });
@@ -305,6 +307,157 @@ const MouseButtonsMask = {
305
307
  FifthButton: 16, // Browser Forward button.
306
308
  };
307
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
+
308
461
  const ControlScheme = {
309
462
  // A mouse can lock inside the WebRTC player, so the user can simply move the
310
463
  // mouse to control the orientation of the camera. The user presses the
@@ -370,7 +523,19 @@ const ToClientMessage = {
370
523
  Protocol: 255,
371
524
  };
372
525
 
373
- const UNREAL_CONFIG = new InjectionToken('Unreal Initial Configuration');
526
+ /**
527
+ * Re-export only. `UNREAL_CONFIG` lives in the `config` secondary entry point so
528
+ * that an app can provide it without pulling the engine in.
529
+ *
530
+ * The package is one FESM: esbuild puts a module in the common-ancestor chunk of
531
+ * its importers, so importing the token from the primary entry point in an eager
532
+ * `app.config.ts` hoists the whole engine into `main` — measured at ~180 kB raw
533
+ * (~49 kB gzip) in a metabox production build. Importing
534
+ * `@3dsource/angular-unreal-module/config` instead costs nothing but the token.
535
+ *
536
+ * Kept here so every existing consumer of the primary entry point, and the 26
537
+ * files inside this library, keep working unchanged.
538
+ */
374
539
 
375
540
  const InputOptions = {
376
541
  controlScheme: ControlScheme.Default,
@@ -477,8 +642,12 @@ const UnrealStatusMessage = {
477
642
  };
478
643
 
479
644
  const COMMAND_EXILE_TIMEOUT_MS = 10_000;
645
+ const normalizeId = (value) => String(value).trim().replace(/ +/gi, '_').toLowerCase();
480
646
  function exileExpiredCommands(commandsInProgress, idToRemove, now) {
481
- 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);
482
651
  const expired = remaining.filter(({ timeStamp }) => now - timeStamp > COMMAND_EXILE_TIMEOUT_MS);
483
652
  if (expired.length) {
484
653
  const expiredIds = new Set(expired.map(({ id }) => id));
@@ -811,6 +980,488 @@ const unrealFeature = createFeature({
811
980
  reducer: unrealReducer,
812
981
  });
813
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
+
814
1465
  /**
815
1466
  * `'metabox'` — the default when `UNREAL_CONFIG` omits `mode` — speaks the full
816
1467
  * Metabox command protocol. `'default'` targets a stock Pixel Streaming
@@ -939,10 +1590,10 @@ class WebrtcErrorModalComponent {
939
1590
  close() {
940
1591
  this.#dialogRef.close();
941
1592
  }
942
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
943
- 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 }); }
944
1595
  }
945
- 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: [{
946
1597
  type: Component,
947
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"] }]
948
1599
  }] });
@@ -1165,10 +1816,10 @@ class CommandTelemetryService extends SubService {
1165
1816
  this.trackTime(out);
1166
1817
  funcToDecorate(out);
1167
1818
  }
1168
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1169
- 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' }); }
1170
1821
  }
1171
- 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: [{
1172
1823
  type: Injectable,
1173
1824
  args: [{ providedIn: 'root' }]
1174
1825
  }], ctorParameters: () => [] });
@@ -1189,10 +1840,10 @@ class DevModeService {
1189
1840
  localStorage.removeItem('devMode');
1190
1841
  }
1191
1842
  }
1192
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1193
- 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' }); }
1194
1845
  }
1195
- 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: [{
1196
1847
  type: Injectable,
1197
1848
  args: [{
1198
1849
  providedIn: 'root',
@@ -1511,10 +2162,10 @@ class RegionsPingService {
1511
2162
  }
1512
2163
  }
1513
2164
  }
1514
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1515
- 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' }); }
1516
2167
  }
1517
- 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: [{
1518
2169
  type: Injectable,
1519
2170
  args: [{ providedIn: 'root' }]
1520
2171
  }], ctorParameters: () => [] });
@@ -1557,101 +2208,25 @@ function withStreamRequestId(wsUrl, streamRequestId) {
1557
2208
  }
1558
2209
 
1559
2210
  /**
1560
- * Factory that creates a WebSocket connection and wraps it in an RxJS Observable.
1561
- *
1562
- * Returns a **single-value Observable**: it emits the open `WebSocket` instance
1563
- * once the connection is established, then completes. This design allows
1564
- * downstream operators (e.g. `tap` in `connectToCirrus`) to attach their own
1565
- * event handlers before any messages arrive.
1566
- *
1567
- * ## Ownership model
1568
- *
1569
- * The factory **transfers ownership** of the WebSocket to the consumer upon
1570
- * emission. Once `observer.next(ws)` fires, the consumer is responsible for
1571
- * the socket's lifecycle (attaching handlers, closing when done). The teardown
1572
- * logic will **never** close a socket that has been successfully delivered.
1573
- *
1574
- * ## Timeout
1575
- *
1576
- * The observable is piped through `timeout({ first: WS_TIMEOUT })`. If the
1577
- * WebSocket `open` event does not fire within `WS_TIMEOUT` milliseconds, the
1578
- * stream errors with a timeout message and the teardown disposes the socket.
1579
- *
1580
- * ## Teardown & leak prevention
1581
- *
1582
- * When the observable is unsubscribed (timeout, manual unsubscribe, or error),
1583
- * the teardown function runs and handles two cases:
1584
- *
1585
- * 1. **Socket was emitted (`emitted === true`):** The consumer owns the socket.
1586
- * Teardown only removes the internal event listeners — it does NOT close
1587
- * the socket. Closing it here would break downstream consumers like
1588
- * `connectToCirrus` that rely on the connection staying alive.
1589
- *
1590
- * 2. **Socket was NOT emitted (`emitted === false`):** The socket is orphaned
1591
- * (nobody has a reference to it). Teardown must close it to prevent a leak.
1592
- * However, calling `ws.close()` on a `CONNECTING` socket does not reliably
1593
- * abort the underlying HTTP upgrade request in all browsers — the TCP
1594
- * handshake may still complete asynchronously. To handle this edge case,
1595
- * teardown replaces the `open` listener with a one-shot handler that calls
1596
- * `ws.close()` if the connection succeeds after teardown has already run.
1597
- * This ensures the orphaned socket is closed regardless of timing:
1598
- *
1599
- * - If `close()` aborts the connection immediately — done.
1600
- * - If the browser lets the handshake complete anyway — the `open` handler
1601
- * fires and closes the now-connected socket, so it doesn't linger in
1602
- * the browser's network tab as a "Pending" request.
1603
- *
1604
- * ## Why `defer`?
1605
- *
1606
- * The outer `defer()` ensures a **new WebSocket** is created on each
1607
- * subscription. Without it, retry operators would reuse the same (possibly
1608
- * closed) socket instance. With `defer`, each retry gets a fresh connection.
1609
- *
1610
- * ## SSR guard
1611
- *
1612
- * If `window` or `WebSocket` is not available (server-side rendering),
1613
- * the function returns an error observable immediately without attempting
1614
- * a connection.
1615
- *
1616
- * ## Lifecycle flags
1617
- *
1618
- * - `opened` — set to `true` when the `open` event fires. Guards the `close`
1619
- * handler so it only errors the observable if the socket was closed *before*
1620
- * the connection was established (e.g. server rejected the upgrade).
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.
1621
2214
  *
1622
- * - `emitted` — set to `true` right before `observer.next(ws)`. Tells the
1623
- * teardown function whether the socket was delivered to the consumer.
1624
- * This is the flag that controls whether teardown closes the socket or
1625
- * leaves it for the consumer.
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.
1626
2218
  *
1627
- * ## Abort
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.
1628
2223
  *
1629
- * The factory does **not** accept an abort signal — cancellation is handled
1630
- * by the caller via standard RxJS unsubscription. When the caller's
1631
- * `takeUntil` (or manual `unsubscribe`) fires, the observable tears down
1632
- * and the orphaned socket is closed. For retry loops, place `takeUntil`
1633
- * at two levels:
2224
+ * The outer `defer` gives every subscription a fresh socket, so `retry` does not
2225
+ * reuse a closed one.
1634
2226
  *
1635
- * 1. **Inside the per-attempt chain** (e.g. in `connectToCirrus`) — cancels
1636
- * the current in-flight WebSocket attempt and triggers teardown.
1637
- * 2. **Outside `retry`** (e.g. in `connectToSignaling`) — kills the entire
1638
- * retry loop so no further re-subscriptions occur.
1639
- *
1640
- * ## Usage
1641
- *
1642
- * ```typescript
1643
- * // In connectToCirrus — takeUntil cancels the current attempt:
1644
- * createWebSocket(wsUrl).pipe(
1645
- * takeUntil(abort$),
1646
- * tap(ws => addWsHandlers(ws)),
1647
- * );
1648
- *
1649
- * // In connectToSignaling — takeUntil outside retry kills the loop:
1650
- * defer(() => innerChain$).pipe(
1651
- * retry({ delay: ... }),
1652
- * takeUntil(abort$),
1653
- * );
1654
- * ```
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.
1655
2230
  *
1656
2231
  * @param wsUrl - The `ws://` or `wss://` URL to connect to.
1657
2232
  * @returns Observable that emits a single open WebSocket, then completes.
@@ -1659,17 +2234,18 @@ function withStreamRequestId(wsUrl, streamRequestId) {
1659
2234
  * does not support WebSocket.
1660
2235
  */
1661
2236
  function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1662
- // --- SSR guard: bail immediately if WebSocket API is unavailable ---
2237
+ // SSR guard.
1663
2238
  if (typeof window === 'undefined' || !('WebSocket' in window)) {
1664
2239
  return throwError(() => new Error("Your browser doesn't support WebSocket"));
1665
2240
  }
1666
2241
  Logger.info('Creating socket', wsUrl);
1667
2242
  return defer(() => new Observable((observer) => {
1668
2243
  const ws = new WebSocket(wsUrl);
1669
- // 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.
1670
2247
  let opened = false;
1671
2248
  let emitted = false;
1672
- // --- Event handlers (attached during connection, removed in teardown) ---
1673
2249
  const onOpen = () => {
1674
2250
  opened = true;
1675
2251
  emitted = true;
@@ -1690,23 +2266,20 @@ function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1690
2266
  ws.addEventListener('open', onOpen);
1691
2267
  ws.addEventListener('error', onError);
1692
2268
  ws.addEventListener('close', onClose);
1693
- // --- Teardown: runs on unsubscribe, timeout, error, or complete ---
2269
+ // Teardown: unsubscribe, timeout, error, or complete.
1694
2270
  return () => {
1695
2271
  ws.removeEventListener('error', onError);
1696
2272
  ws.removeEventListener('close', onClose);
1697
2273
  if (!emitted &&
1698
2274
  (ws.readyState === WebSocket.CONNECTING ||
1699
2275
  ws.readyState === WebSocket.OPEN)) {
1700
- // Socket was never delivered — it's orphaned. Must close it.
1701
- // Replace the open listener with a cleanup handler: if the
1702
- // browser completes the handshake after this teardown, the
1703
- // 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.
1704
2278
  ws.removeEventListener('open', onOpen);
1705
2279
  ws.addEventListener('open', () => ws.close());
1706
- // Some strict implementations (including `ws`) emit an `error`
1707
- // when a CONNECTING socket is closed. The observable is already
1708
- // torn down, so consume that expected lifecycle error and remove
1709
- // 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.
1710
2283
  const onOrphanError = () => undefined;
1711
2284
  const onOrphanClose = () => {
1712
2285
  ws.removeEventListener('error', onOrphanError);
@@ -1717,8 +2290,7 @@ function createWebSocket(wsUrl, timeoutMs = WS_TIMEOUT) {
1717
2290
  ws.close();
1718
2291
  }
1719
2292
  else {
1720
- // Socket was delivered to consumer — they own it now.
1721
- // Just remove our internal listener; don't touch the socket.
2293
+ // Delivered — the consumer owns it; only drop our listener.
1722
2294
  ws.removeEventListener('open', onOpen);
1723
2295
  }
1724
2296
  };
@@ -2352,10 +2924,10 @@ class SignallingService extends SubService {
2352
2924
  Logger.log('Start Getting Region');
2353
2925
  return this.regionsPingService.getFastest().pipe(take(1));
2354
2926
  }
2355
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2356
- 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' }); }
2357
2929
  }
2358
- 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: [{
2359
2931
  type: Injectable,
2360
2932
  args: [{ providedIn: 'root' }]
2361
2933
  }], ctorParameters: () => [] });
@@ -2770,10 +3342,10 @@ class VideoService extends SubService {
2770
3342
  this.armGestureRetry(audio);
2771
3343
  }
2772
3344
  }
2773
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2774
- 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' }); }
2775
3347
  }
2776
- 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: [{
2777
3349
  type: Injectable,
2778
3350
  args: [{ providedIn: 'root' }]
2779
3351
  }], ctorParameters: () => [] });
@@ -3439,10 +4011,10 @@ class WebRtcPlayerService extends SubService {
3439
4011
  Logger.error("Couldn't create answer", e);
3440
4012
  }
3441
4013
  }
3442
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3443
- 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' }); }
3444
4016
  }
3445
- 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: [{
3446
4018
  type: Injectable,
3447
4019
  args: [{ providedIn: 'root' }]
3448
4020
  }], ctorParameters: () => [] });
@@ -3612,10 +4184,10 @@ class StreamAdoptionService {
3612
4184
  this.webRtc.adopt(parked.pc, parked.dc, parked.stream, parked.config ?? {}, parked.dcBuffer);
3613
4185
  return true;
3614
4186
  }
3615
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamAdoptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3616
- 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' }); }
3617
4189
  }
3618
- 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: [{
3619
4191
  type: Injectable,
3620
4192
  args: [{ providedIn: 'root' }]
3621
4193
  }] });
@@ -3683,10 +4255,10 @@ class UnrealCallbackService {
3683
4255
  return observable;
3684
4256
  });
3685
4257
  }
3686
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3687
- 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' }); }
3688
4260
  }
3689
- 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: [{
3690
4262
  type: Injectable,
3691
4263
  args: [{ providedIn: 'root' }]
3692
4264
  }] });
@@ -3803,10 +4375,10 @@ class UnrealCommunicatorService extends SubService {
3803
4375
  requestQualityControl() {
3804
4376
  this.sendInputData(new Uint8Array([MessageType.RequestQualityControl]).buffer);
3805
4377
  }
3806
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3807
- 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' }); }
3808
4380
  }
3809
- 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: [{
3810
4382
  type: Injectable,
3811
4383
  args: [{ providedIn: 'root' }]
3812
4384
  }], ctorParameters: () => [] });
@@ -3820,10 +4392,10 @@ class UnrealErrorModalComponent {
3820
4392
  close() {
3821
4393
  this.#dialogRef.close();
3822
4394
  }
3823
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3824
- 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 }); }
3825
4397
  }
3826
- 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: [{
3827
4399
  type: Component,
3828
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"] }]
3829
4401
  }] });
@@ -4219,10 +4791,10 @@ class UnrealEffects {
4219
4791
  // Fails silently if cross-origin
4220
4792
  }
4221
4793
  }
4222
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4223
- 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 }); }
4224
4796
  }
4225
- 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: [{
4226
4798
  type: Injectable
4227
4799
  }] });
4228
4800
 
@@ -4374,10 +4946,9 @@ const floatToSmoothPercents = () => (source) => source.pipe(timestamp(), startWi
4374
4946
  * Smoothly interpolates from `start` to `end` over `duration`ms using the
4375
4947
  * animation frame scheduler. Completes when the transition finishes.
4376
4948
  *
4377
- * NOTE: duplicates `smoothTransition` from `@3dsource/utils`. The utils version
4378
- * is missing `takeWhile(... < 1, true)` and never completes — which would
4379
- * deadlock the `concatMap` above. Keep this local copy until utils is fixed.
4380
- * 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`.
4381
4952
  */
4382
4953
  const smoothTransition = (start, end, duration) => {
4383
4954
  const startTime = animationFrameScheduler.now();
@@ -4739,10 +5310,10 @@ class AFKService extends SubService {
4739
5310
  }
4740
5311
  }, 1000);
4741
5312
  }
4742
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4743
- 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' }); }
4744
5315
  }
4745
- 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: [{
4746
5317
  type: Injectable,
4747
5318
  args: [{ providedIn: 'root' }]
4748
5319
  }], ctorParameters: () => [] });
@@ -4792,10 +5363,10 @@ class LoadingProgressService {
4792
5363
  // `switchMap` cancels the inner subscription on every visibility change
4793
5364
  // so stale emissions can't leak through.
4794
5365
  #boundedProgress$;
4795
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4796
- 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' }); }
4797
5368
  }
4798
- 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: [{
4799
5370
  type: Injectable,
4800
5371
  args: [{ providedIn: 'root' }]
4801
5372
  }] });
@@ -4881,10 +5452,10 @@ class FreezeFrameService extends SubService {
4881
5452
  progress: 1,
4882
5453
  }));
4883
5454
  }
4884
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4885
- 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' }); }
4886
5457
  }
4887
- 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: [{
4888
5459
  type: Injectable,
4889
5460
  args: [{ providedIn: 'root' }]
4890
5461
  }], ctorParameters: () => [] });
@@ -5036,19 +5607,21 @@ class FileReceiverService extends SubService {
5036
5607
  this.reset();
5037
5608
  }
5038
5609
  }
5039
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileReceiverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5040
- 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' }); }
5041
5612
  }
5042
- 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: [{
5043
5614
  type: Injectable,
5044
5615
  args: [{ providedIn: 'root' }]
5045
5616
  }], ctorParameters: () => [] });
5046
5617
 
5047
- const filteredLogs = [
5048
- {
5049
- command: 'onChangeSequence',
5050
- },
5051
- ];
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));
5052
5625
  class AggregatorService extends SubService {
5053
5626
  constructor() {
5054
5627
  super();
@@ -5135,7 +5708,11 @@ class AggregatorService extends SubService {
5135
5708
  });
5136
5709
  }
5137
5710
  showMessageFromUnreal(json) {
5138
- 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))) {
5139
5716
  return;
5140
5717
  }
5141
5718
  Logger.colored(...COLOR_CODES.FROM_UNREAL, json);
@@ -5218,10 +5795,10 @@ class AggregatorService extends SubService {
5218
5795
  Logger.warn(`unrecognized data received, packet ID ${view[0]}`);
5219
5796
  }
5220
5797
  }
5221
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5222
- 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' }); }
5223
5800
  }
5224
- 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: [{
5225
5802
  type: Injectable,
5226
5803
  args: [{ providedIn: 'root' }]
5227
5804
  }], ctorParameters: () => [] });
@@ -5382,10 +5959,10 @@ class ConsoleExtensionsService extends SubService {
5382
5959
  Logger.info('emitCommand(command) => send command to Unreal Engine.');
5383
5960
  Logger.info('stressTest({iterations}) => hammer reconnect: repeatedly drop DC/PC and assert recovery.');
5384
5961
  }
5385
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5386
- 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' }); }
5387
5964
  }
5388
- 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: [{
5389
5966
  type: Injectable,
5390
5967
  args: [{ providedIn: 'root' }]
5391
5968
  }], ctorParameters: () => [] });
@@ -5399,30 +5976,48 @@ const RESIZE_DEBOUNCE_MS = 300;
5399
5976
  const FUNCTION_KEY_MIN = 112;
5400
5977
  const FUNCTION_KEY_MAX = 123;
5401
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"])';
5402
5985
  class InputService extends SubService {
5403
5986
  constructor() {
5404
5987
  super();
5405
5988
  this.commandsSender = inject(UnrealCommunicatorService);
5406
- 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);
5407
5992
  this.videoService = inject(VideoService);
5408
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`.
5409
5999
  this.defaultKeys = [
5410
- KeyboardNumericCode.KeyW,
5411
- KeyboardNumericCode.KeyS,
5412
- KeyboardNumericCode.KeyA,
5413
- KeyboardNumericCode.KeyD,
5414
- KeyboardNumericCode.ArrowUp,
5415
- KeyboardNumericCode.ArrowDown,
5416
- KeyboardNumericCode.ArrowLeft,
5417
- KeyboardNumericCode.ArrowRight,
5418
- KeyboardNumericCode.NumpadAdd,
5419
- KeyboardNumericCode.NumpadSubtract,
5420
- KeyboardNumericCode.ControlRight,
5421
- KeyboardNumericCode.Equal,
5422
- KeyboardNumericCode.Minus,
5423
- 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',
5424
6019
  ];
5425
- this.availableKeys = this.defaultKeys;
6020
+ this.availableKeys = new Set(this.defaultKeys);
5426
6021
  this.options = InputOptions;
5427
6022
  this.reInit$ = new Subject();
5428
6023
  this.deactivatedVideoTrigger$ = NEVER;
@@ -5439,6 +6034,12 @@ class InputService extends SubService {
5439
6034
  this.fingerIds = new Map();
5440
6035
  this.maxByteValue = 255;
5441
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();
5442
6043
  // Reusable wire buffers. Input events fire at up to ~60 Hz; the data channel
5443
6044
  // copies the bytes synchronously on sending (RTCDataChannel.send), so a single
5444
6045
  // buffer per message shape is safe and avoids per-event allocation/GC churn.
@@ -5471,8 +6072,17 @@ class InputService extends SubService {
5471
6072
  .pipe(filter(() => !!this.video), takeUntil$1(this.destroy$))
5472
6073
  .subscribe(() => this.setup());
5473
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
+ */
5474
6084
  useKeyboardKeys(keys) {
5475
- this.availableKeys = keys;
6085
+ this.availableKeys = new Set(keys.map((key) => (typeof key === 'number' ? InvertedKeyMap[key] : key)));
5476
6086
  }
5477
6087
  get video() {
5478
6088
  return this.videoService.video;
@@ -5487,6 +6097,10 @@ class InputService extends SubService {
5487
6097
  // Reset touch state to prevent stale identifiers across sessions
5488
6098
  this.fingerIds.clear();
5489
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();
5490
6104
  merge(fromResizeObserver(video), this.actions$.pipe(ofType(videoAdaptedToContainer)))
5491
6105
  .pipe(debounceTime$1(RESIZE_DEBOUNCE_MS), takeUntil$1(this.deactivatedVideoTrigger$))
5492
6106
  .subscribe(() => this.setupNormalizeAndQuantize(video));
@@ -5574,9 +6188,17 @@ class InputService extends SubService {
5574
6188
  fromEvent(window, 'blur')
5575
6189
  .pipe(filter(() => !!video), takeUntil$1(this.deactivatedVideoTrigger$))
5576
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());
5577
6199
  this.registerMouseEnterAndLeaveEvents(video);
5578
6200
  this.registerHoveringMouseEvents(video);
5579
- this.registerKeyboardEvents(this.availableKeys);
6201
+ this.registerKeyboardEvents();
5580
6202
  this.registerTouchEvents(video);
5581
6203
  }
5582
6204
  getTouchPositionWithOffset({ clientX, clientY }) {
@@ -5798,62 +6420,108 @@ class InputService extends SubService {
5798
6420
  this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
5799
6421
  });
5800
6422
  }
5801
- // Browser keys do not have a charCode so we only need to test keyCode.
5802
- 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) {
5803
6432
  return ((keyCode >= FUNCTION_KEY_MIN && keyCode <= FUNCTION_KEY_MAX) ||
6433
+ (keyCode >= ARROW_KEY_MIN && keyCode <= ARROW_KEY_MAX) ||
5804
6434
  keyCode === TAB_KEY_CODE);
5805
6435
  }
5806
6436
  /**
5807
- * 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.
5808
6439
  *
5809
- * `e.keyCode` is not that on Gecko: Firefox reports the punctuation keys by
5810
- * their ASCII value — `=` as 61 and `-` as 173, where Chrome sends 187 and 189
5811
- * — so zoom-on-plus/minus reached the engine as codes it does not know.
5812
- * `KeyboardNumericCode` is keyed by `e.code` and holds the virtual-key value,
5813
- * 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.
5814
6443
  *
5815
- * Checked after the right-hand modifiers, which need their own distinct codes
5816
- * 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.
5817
6447
  */
5818
6448
  getKeyCode(e) {
5819
- if (e.keyCode === SpecialKeyCodes.Shift && e.code === 'ShiftRight')
5820
- return SpecialKeyCodes.RightShift;
5821
- else if (e.keyCode === SpecialKeyCodes.Control && e.code === 'ControlRight')
5822
- return SpecialKeyCodes.RightControl;
5823
- else if (e.keyCode === SpecialKeyCodes.Alt && e.code === 'AltRight')
5824
- return SpecialKeyCodes.RightAlt;
5825
- return (KeyboardNumericCode[e.code] ??
5826
- e.keyCode);
5827
- }
5828
- registerKeyboardEvents(availableKeys) {
5829
- const availableKeyNames = availableKeys.map((value) => InvertedKeyMap[value]);
5830
- const keyBlock = (event) => {
5831
- const code = event.code;
5832
- const isInputFieldFocused = event.target?.localName === 'input';
5833
- const keyMatch = availableKeyNames.includes(code);
5834
- if (this.isDevMode && !keyMatch) {
5835
- Logger.warn('Code=>', code, 'Keyboard event is not filtered in DEV Mode. App may behave inappropriately!');
5836
- return isInputFieldFocused;
5837
- }
5838
- return isInputFieldFocused || !keyMatch;
5839
- };
5840
- 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;
5841
6479
  }
5842
- startListenKeys(_filter) {
5843
- const filter = _filter ?? (() => false);
6480
+ sendKeyUp(keyCode) {
6481
+ const keyUp = this.keyUpBytes;
6482
+ keyUp[0] = MessageType.KeyUp;
6483
+ keyUp[1] = keyCode;
6484
+ this.sendInputData(keyUp.buffer);
6485
+ }
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() {
5844
6499
  fromEvent(document, 'keydown')
5845
6500
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5846
6501
  .subscribe((e) => {
5847
- 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)) {
5848
6505
  return;
5849
6506
  }
5850
- const isCtrlOrCommand = e.ctrlKey || e.metaKey;
5851
- // Normalised first: on Firefox the raw `e.keyCode` for these keys is 61
5852
- // and 173, so comparing it left browser zoom to reach the engine too.
5853
6507
  const keyCode = this.getKeyCode(e);
5854
- const isPlusOrMinus = keyCode === KeyboardNumericCode.Equal ||
5855
- keyCode === KeyboardNumericCode.Minus;
5856
- 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)) {
5857
6525
  return;
5858
6526
  }
5859
6527
  const keyDown = this.keyDownBytes;
@@ -5861,38 +6529,46 @@ class InputService extends SubService {
5861
6529
  keyDown[1] = keyCode;
5862
6530
  keyDown[2] = Number(e.repeat);
5863
6531
  this.sendInputData(keyDown.buffer);
6532
+ this.heldKeys.add(keyCode);
5864
6533
  // Backspace isn't a keypress in JS, but UE4 text-entry fields need it
5865
6534
  // to delete characters.
5866
- if (e.keyCode === SpecialKeyCodes.BackSpace) {
6535
+ if (keyCode === SpecialKeyCodes.BackSpace) {
5867
6536
  const data = this.keyPressView;
5868
6537
  data.setUint8(0, MessageType.KeyPress);
5869
6538
  data.setUint16(1, SpecialKeyCodes.BackSpace, true);
5870
6539
  this.sendInputData(data.buffer);
5871
6540
  }
5872
- if (this.options.suppressBrowserKeys &&
5873
- this.isKeyCodeBrowserKey(e.keyCode)) {
5874
- e.preventDefault();
5875
- }
5876
6541
  });
5877
6542
  fromEvent(document, 'keyup')
5878
6543
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5879
6544
  .subscribe((e) => {
5880
- if (filter(e)) {
6545
+ const keyCode = this.getKeyCode(e);
6546
+ if (keyCode === null) {
5881
6547
  return;
5882
6548
  }
5883
- const keyUp = this.keyUpBytes;
5884
- keyUp[0] = MessageType.KeyUp;
5885
- keyUp[1] = this.getKeyCode(e);
5886
- this.sendInputData(keyUp.buffer);
5887
6549
  if (this.options.suppressBrowserKeys &&
5888
- this.isKeyCodeBrowserKey(e.keyCode)) {
6550
+ this.isBrowserKey(keyCode) &&
6551
+ !this.isTextEntryTarget(e)) {
5889
6552
  e.preventDefault();
5890
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);
5891
6562
  });
5892
6563
  fromEvent(document, 'keypress')
5893
6564
  .pipe(takeUntil$1(this.deactivatedVideoTrigger$))
5894
6565
  .subscribe((e) => {
5895
- 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) {
5896
6572
  return;
5897
6573
  }
5898
6574
  const data = this.keyPressView;
@@ -5901,10 +6577,10 @@ class InputService extends SubService {
5901
6577
  this.sendInputData(data.buffer);
5902
6578
  });
5903
6579
  }
5904
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: InputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5905
- 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' }); }
5906
6582
  }
5907
- 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: [{
5908
6584
  type: Injectable,
5909
6585
  args: [{ providedIn: 'root' }]
5910
6586
  }], ctorParameters: () => [] });
@@ -5973,46 +6649,36 @@ class StreamStatusTelemetryService extends SubService {
5973
6649
  VideoEncoderQP,
5974
6650
  };
5975
6651
  }
5976
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5977
- 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' }); }
5978
6654
  }
5979
- 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: [{
5980
6656
  type: Injectable,
5981
6657
  args: [{ providedIn: 'root' }]
5982
6658
  }], ctorParameters: () => [] });
5983
6659
 
5984
6660
  /**
5985
- * Pro-grade telemetry for the WebRTC stream lifecycle.
6661
+ * Telemetry for the WebRTC stream lifecycle.
5986
6662
  *
5987
- * Wire-level event taxonomy (intentionally small — all phase-specific
5988
- * details ride inside `stream.phase_ended`'s payload):
5989
- * - `stream.session_started` — once per session, when a real connection
5990
- * attempt begins (`setEstablishingConnection(true)`)
5991
- * - `stream.phase_ended` — once per closer in {@link phaseCatalog};
5992
- * carries phaseNo / id / color /
5993
- * milestones / params / overlays
5994
- * - `stream.sample` — every 5s, window-aggregated (p. 50/p. 95/min/max)
5995
- * - `stream.quality_degraded` — on QP entering `red` (>35)
5996
- * - `stream.quality_recovered` — on QP leaving `red`
5997
- * - `stream.fps_low` — when the window FPS p50 < 70% of the target
5998
- * - `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.
5999
6671
  *
6000
- * Every event carries `sessionId` + `userId`. The `sessionId` IS the stream's
6001
- * connection id (`selectCirrusConnectionId`) — the same value the debug overlay
6002
- * shows as "Connection Id" — so a telemetry session can be matched to a Cirrus
6003
- * player session by id. Since that id only exists once the orchestration
6004
- * 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
6005
6676
  * {@link SessionState.wireId}); a session that never got one falls back to a
6006
- * locally generated uuid. Samples carry a monotonic `seq` for server-side
6007
- * ordering & dedup. Durations use `performance.now()` — immune to wall-clock
6008
- * 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.
6009
6679
  *
6010
- * NOTE: For `stream.session_ended` on the tab close to survive teardown,
6011
- * the Mixpanel client must be configured with `api_transport: 'sendBeacon'`.
6012
- *
6013
- * Every wire shape lives in `interfaces/stream-telemetry-events.interface.ts`
6014
- * — `track()` / `emitLifecycle()` are typed against it, so a payload that
6015
- * 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'`.
6016
6682
  */
6017
6683
  /** Trim a resource URL to its path: drop origin and query string so the
6018
6684
  * emitted name is short, stable, and not a PII-bearing query. */
@@ -6337,45 +7003,31 @@ class StreamStatusTelemetryService2 {
6337
7003
  }
6338
7004
  }
6339
7005
  init() {
6340
- // Start a new session when a real connection attempt BEGINS, not on
6341
- // every `initSignalling` dispatch. `initSignalling` is fired multiple
6342
- // times per page load (the unreal-scene effect, `startStream`, the
6343
- // reconnect effect), but the connection layer collapses them into one
6344
- // attempt via `connectToSignaling$` (`exhaustMap` + `!cirrusConnected`
6345
- // filter). `setEstablishingConnection(true)` is dispatched exactly once
6346
- // per real attempt, by that effect's `startWith(...)` — the SINGLE
6347
- // dispatch site, downstream of the guard and ahead of both branches
6348
- // (prefetch adoption / fresh connect). So binding here yields one session
6349
- // per attempt instead of a burst of `restart` churn sessions. Genuine
6350
- // reconnects (e.g. DataChannelTimeout) re-enter the same path and
6351
- // 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`.
6352
7014
  //
6353
- // Do NOT add a second dispatch site: this binding turns every extra
6354
- // `true` into `endSession('restart')` + a new session, which throws away
6355
- // 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.
6356
7018
  this.actions$
6357
7019
  .pipe(ofType(setEstablishingConnection), filter$1(({ value }) => value === true), takeUntil$1(this.destroy$))
6358
7020
  .subscribe(() => this.startSession());
6359
- // Action-stream end triggers (NOT a state-observable subscription).
6360
- //
6361
- // We can't observe the cirrus connection flag via `pairwise`
6362
- // because the `destroyUnrealScene` reducer resets the entire
6363
- // unreal state to `initialState` in one step — `cirrusConnected`
6364
- // flips from `true` to `false` AND `disconnectReason` resets to
6365
- // `None` simultaneously. A state-observable subscriber would fire
6366
- // synchronously inside that reducer's emission, before the
6367
- // follow-up effect (`destroyUnrealScene → destroyRemoteConnections({Destroy})`)
6368
- // gets a chance to write `Destroy` back into state — so the
6369
- // subscriber would see `None` and end as `'disconnected'`.
6370
- //
6371
- // Subscribing to the actions instead lets us pick the right
6372
- // reason per dispatched action, and `endSession` is idempotent
6373
- // (`if (!this.session) return`) so any cascading second trigger
6374
- // 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:
6375
7028
  // - `destroyUnrealScene` → `'destroy'` (route change, removed from DOM)
6376
- // - `setCirrusDisconnected` → `'disconnected'` (WS close, AFK,
6377
- // manual drop, WebRTCError, etc. — granular cause + message
6378
- // read from current state in `endSession`)
7029
+ // - `setCirrusDisconnected` → `'disconnected'` (WS close, AFK, manual
7030
+ // drop, WebRTCError — granular cause read from state in `endSession`)
6379
7031
  this.actions$
6380
7032
  .pipe(ofType(destroyUnrealScene), takeUntil$1(this.destroy$))
6381
7033
  .subscribe(() => this.endSession('destroy'));
@@ -6671,11 +7323,10 @@ class StreamStatusTelemetryService2 {
6671
7323
  /**
6672
7324
  * Emit the region / signaling / webrtc phases for an ADOPTED connection.
6673
7325
  *
6674
- * The prefetch script established the connection in PARALLEL with page load,
6675
- * so the phases are positioned at their real (anchor-relative) spans — shifted
6676
- * LEFT to overlap the HTML-loading / route-idle rows — rather than sequentially
6677
- * after them. That's the whole point of the prefetch: the handshake already
6678
- * 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:
6679
7330
  * - `duration` — the real sub-step time the prefetch spent.
6680
7331
  * - `spanStartMs` — anchor-relative start, so the viewer left-shifts the bar.
6681
7332
  * - `adoptionAtMs` — where Angular took over (the next phase, Unreal
@@ -6941,22 +7592,15 @@ class StreamStatusTelemetryService2 {
6941
7592
  }
6942
7593
  }
6943
7594
  /**
6944
- * Aggregate all resources the browser has loaded so far
6945
- * (`performance.getEntriesByType('resource')`), grouped by `initiatorType`,
6946
- * **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.
6947
7601
  *
6948
- * - `resources` — fetched before `loadEventEnd` (= part of the actual
6949
- * page weight). What you'd traditionally call "page load."
6950
- * - `lazyResources` — fetched after `loadEventEnd` (= typically the
6951
- * route's lazy-loaded chunks, JSON manifests etc. that download
6952
- * when the user navigates to a sub-route the streamer lives on).
6953
- *
6954
- * When `loadEventEnd` is 0/unknown, everything goes into `resources`
6955
- * and `lazyResources` is null — old behavior.
6956
- *
6957
- * `transferSize === 0 && decodedBodySize > 0` means the resource came from
6958
- * the HTTP cache — counted separately so cached hits don't inflate
6959
- * 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.
6960
7604
  */
6961
7605
  collectResourceTotals(loadEventEnd) {
6962
7606
  try {
@@ -7078,10 +7722,10 @@ class StreamStatusTelemetryService2 {
7078
7722
  emit(event, data) {
7079
7723
  this.store.dispatch(trackTelemetryData({ event, data }));
7080
7724
  }
7081
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7082
- 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' }); }
7083
7727
  }
7084
- 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: [{
7085
7729
  type: Injectable,
7086
7730
  args: [{ providedIn: 'root' }]
7087
7731
  }], ctorParameters: () => [] });
@@ -7167,10 +7811,10 @@ class FileHandlerService {
7167
7811
  return observable;
7168
7812
  });
7169
7813
  }
7170
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7171
- 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' }); }
7172
7816
  }
7173
- 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: [{
7174
7818
  type: Injectable,
7175
7819
  args: [{ providedIn: 'root' }]
7176
7820
  }] });
@@ -7259,86 +7903,31 @@ class AnalyticsService extends SubService {
7259
7903
  Logger.log('Analytics Event:', name, params);
7260
7904
  this.store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
7261
7905
  }
7262
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7263
- 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' }); }
7264
7908
  }
7265
- 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: [{
7266
7910
  type: Injectable,
7267
7911
  args: [{ providedIn: 'root' }]
7268
7912
  }], ctorParameters: () => [] });
7269
7913
 
7270
7914
  /**
7271
- * Adaptive FPS controller that monitors the incoming WebRTC video stream
7272
- * quality and dynamically adjusts Unreal Engine's `t.MaxFPS` console variable.
7273
- *
7274
- * ## How it works
7275
- *
7276
- * The service subscribes to `VideoService.videoStats$` (emitted every 250 ms)
7277
- * and collects samples of FPS, bitrate, and QP (Quantization Parameter).
7278
- * Every {@link SAMPLE_WINDOW} ticks (~2 s) it averages the collected samples
7279
- * and feeds them into a set of **fuzzy-logic membership functions** that
7280
- * classify each metric into overlapping quality bands.
7281
- *
7282
- * ### Input signals
7283
- *
7284
- * **1. FPS ratio** = `actualFPS / currentTargetFPS` (0..1+)
7285
- *
7286
- * | Band | Range | Meaning |
7287
- * |--------|---------------------|-----------------------------------------------|
7288
- * | low | ratio < 0.7 → 1.0, linear ramp 0.7..0.9 → 0 | Stream cannot keep up with the target |
7289
- * | okays | triangle 0.8..1.0, peak at 0.95 | Stream roughly matches the target |
7290
- * | 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}.
7291
7917
  *
7292
- * **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.
7293
7922
  *
7294
- * `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.
7295
7927
  *
7296
- * | Band | Range | Meaning |
7297
- * |--------|-------------------------------------------------|-----------------------------------------------|
7298
- * | high | load 0.5..0.8 → ramp, >0.8 → 1.0 | Encoder is using most of the available bandwidth |
7299
- * | not high | load < 0.5 → 0.0 | Bandwidth headroom is available |
7300
- *
7301
- * **3. Quality** — mapped from QP via `mapQpToQuality()` (see {@link Quality}):
7302
- *
7303
- * | Quality | QP range | Meaning |
7304
- * |----------|-----------|-------------------------------------------------|
7305
- * | `lime` | QP <= 26 | Good — encoder has enough headroom |
7306
- * | `orange` | QP 27..35 | Fair — encoder is under moderate pressure |
7307
- * | `red` | QP > 35 | Poor — heavy compression, visible artifacts |
7308
- *
7309
- * ### Decision rules (evaluated in priority order, first match wins)
7310
- *
7311
- * | # | Condition | Decision |
7312
- * |---|------------------------------------------------------------|--------------|
7313
- * | 1 | FPS ratio is **low** (membership > 0.5) | **decrease** |
7314
- * | 2 | FPS ratio is **ok** AND quality is `red` | **decrease** |
7315
- * | 3 | FPS ratio is **ok** AND bitrate **high** AND quality is `orange` | **hold** |
7316
- * | 4 | FPS ratio is **high** AND quality is `lime` AND bitrate **not high** | **increase** |
7317
- * | — | Everything else | **hold** |
7318
- *
7319
- * ## Cooldown-based throttling
7320
- *
7321
- * The service continuously evaluates fuzzy rules in every sample window.
7322
- * After each FPS step change it enforces a cooldown before the next
7323
- * change can be applied. The cooldown duration depends on the
7324
- * (previous decision → next decision) pair:
7325
- *
7326
- * | Previous | Next | Cooldown |
7327
- * |-----------|-----------|-----------------------------------------------------|
7328
- * | decrease | decrease | {@link COOLDOWN_DECREASE_AFTER_DECREASE_MS} (2 s) |
7329
- * | decrease | increase | {@link COOLDOWN_INCREASE_AFTER_DECREASE_MS} (10 s) |
7330
- * | increase | decrease | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
7331
- * | increase | increase | {@link COOLDOWN_AFTER_INCREASE_MS} (2 s) |
7332
- *
7333
- * This means the system reacts quickly after an increase (2 s in any
7334
- * direction) but is cautious about stepping back up after a decrease
7335
- * (10 s), while still allowing consecutive decreases at 2 s intervals.
7336
- *
7337
- * ## FPS dispatch
7338
- *
7339
- * FPS changes are dispatched via the NgRx `setMaxFps` action, which triggers
7340
- * the existing `UnrealEffects.setMaxFps$` effect that sends the
7341
- * `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.
7342
7931
  */
7343
7932
  class FpsMonitorService extends SubService {
7344
7933
  constructor() {
@@ -7439,15 +8028,7 @@ class FpsMonitorService extends SubService {
7439
8028
  const decision = this.evaluateDecision(avg, currentTarget);
7440
8029
  this.upgradeFps(decision, avg.qp);
7441
8030
  }
7442
- /**
7443
- * Combines fuzzy membership values into a single discrete decision.
7444
- * Rules are evaluated in priority order — first match wins.
7445
- *
7446
- * Inputs:
7447
- * - `fpsRatio` = avgFPS / currentTargetFPS (e.g., 45/60 = 0.75)
7448
- * - `bitrateLoad` = avgBitrate / 20 Mbit/s (e.g., 12M/20M = 0.6)
7449
- * - `quality` = mapQpToQuality(avgQP) → 'lime' | 'orange' | 'red'
7450
- */
8031
+ /** Rules are evaluated in priority order — first match wins. */
7451
8032
  evaluateDecision(avg, currentTarget) {
7452
8033
  const referenceTarget = currentTarget > 0 ? currentTarget : this.UNLIMITED_REFERENCE_FPS;
7453
8034
  const fpsRatio = avg.fps / referenceTarget;
@@ -7487,21 +8068,10 @@ class FpsMonitorService extends SubService {
7487
8068
  }
7488
8069
  return 'hold';
7489
8070
  }
7490
- // ---------------------------------------------------------------------------
7491
- // Fuzzy membership functions
7492
- //
7493
- // Each function maps a normalised input (0..1+) to a membership degree (0..1).
7494
- // Overlapping trapezoid/triangle shapes let multiple categories be partially
7495
- // active at the same time, which makes the rules less brittle than hard thresholds.
7496
- // ---------------------------------------------------------------------------
7497
- /**
7498
- * FPS ratio membership "low".
7499
- * Input: a ratio = actualFPS / targetFPS (e.g., 42/60 = 0.70).
7500
- *
7501
- * ratio < 0.7 → 1.0 (clearly failing, e.g., 40/60)
7502
- * ratio 0.7..0.9 → linear ramp down (e.g., 0.8 → 0.5)
7503
- * a ratio > 0.9 → 0.0 (keeping up)
7504
- */
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. */
7505
8075
  membershipFpsLow(ratio) {
7506
8076
  if (ratio < 0.7)
7507
8077
  return 1.0;
@@ -7509,15 +8079,7 @@ class FpsMonitorService extends SubService {
7509
8079
  return 0.0;
7510
8080
  return (0.9 - ratio) / 0.2;
7511
8081
  }
7512
- /**
7513
- * FPS ratio membership "ok" — triangle shape.
7514
- * Input: ratio = actualFPS / targetFPS.
7515
- *
7516
- * ratio < 0.8 → 0.0
7517
- * ratio 0.8..0.95 → ramp up, peak at 0.95 (e.g., 57/60)
7518
- * ratio 0.95..1.0 → ramp down
7519
- * a ratio > 1.0 → 0.0
7520
- */
8082
+ /** "ok" — triangle peaking just under the target. */
7521
8083
  membershipFpsOk(ratio) {
7522
8084
  if (ratio < 0.8)
7523
8085
  return 0.0;
@@ -7527,14 +8089,7 @@ class FpsMonitorService extends SubService {
7527
8089
  return (1.0 - ratio) / 0.05;
7528
8090
  return 0.0;
7529
8091
  }
7530
- /**
7531
- * FPS ratio membership "high".
7532
- * Input: ratio = actualFPS / targetFPS.
7533
- *
7534
- * ratio < 0.9 → 0.0
7535
- * ratio 0.9..1.0 → linear ramp up (e.g., 0.95 → 0.5)
7536
- * a ratio >= 1.0 → 1.0 (meeting or exceeding target, e.g., 60/60)
7537
- */
8092
+ /** "high" — the stream meets or exceeds its target. */
7538
8093
  membershipFpsHigh(ratio) {
7539
8094
  if (ratio < 0.9)
7540
8095
  return 0.0;
@@ -7542,14 +8097,7 @@ class FpsMonitorService extends SubService {
7542
8097
  return 1.0;
7543
8098
  return (ratio - 0.9) / 0.1;
7544
8099
  }
7545
- /**
7546
- * Bitrate load membership "high".
7547
- * Input: a load = currentBitrate / MAX_BITRATE (20 Mbit/s).
7548
- *
7549
- * load < 0.5 → 0.0 (below 10 Mbit/s — plenty of headroom)
7550
- * load 0.5..0.8 → linear ramp (e.g., 13M/20M = 0.65 → 0.5)
7551
- * load > 0.8 → 1.0 (above 16 Mbit/s — near capacity)
7552
- */
8100
+ /** "high" — the encoder is using most of the available bandwidth. */
7553
8101
  membershipBitrateHigh(load) {
7554
8102
  if (load < 0.5)
7555
8103
  return 0.0;
@@ -7629,10 +8177,10 @@ class FpsMonitorService extends SubService {
7629
8177
  }
7630
8178
  return { fps: fps / len, bitrate: bitrate / len, qp: qp / len };
7631
8179
  }
7632
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7633
- 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' }); }
7634
8182
  }
7635
- 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: [{
7636
8184
  type: Injectable,
7637
8185
  args: [{ providedIn: 'root' }]
7638
8186
  }], ctorParameters: () => [] });
@@ -7667,10 +8215,10 @@ class AfkTimeoutModalComponent {
7667
8215
  resetAfk() {
7668
8216
  this.#afkService.reset();
7669
8217
  }
7670
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7671
- 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 }); }
7672
8220
  }
7673
- 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: [{
7674
8222
  type: Component,
7675
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"] }]
7676
8224
  }], propDecorators: { uiClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiClasses", required: false }] }], contentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentTemplate", required: false }] }] } });
@@ -7682,10 +8230,10 @@ class FreezeFrameComponent {
7682
8230
  this.combinedFreeze = this.#store.selectSignal(selectFreezeFrameCombinedDataUrl);
7683
8231
  }
7684
8232
  #store;
7685
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7686
- 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 }); }
7687
8235
  }
7688
- 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: [{
7689
8237
  type: Component,
7690
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"] }]
7691
8239
  }] });
@@ -7700,10 +8248,10 @@ class ImageLoadingSrcComponent {
7700
8248
  loadingImageError() {
7701
8249
  this.#store.dispatch(setLoadingImageSrc({ src: '' }));
7702
8250
  }
7703
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7704
- 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 }); }
7705
8253
  }
7706
- 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: [{
7707
8255
  type: Component,
7708
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"] }]
7709
8257
  }] });
@@ -7786,10 +8334,10 @@ class StatGraphComponent {
7786
8334
  }
7787
8335
  this.draw();
7788
8336
  }
7789
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7790
- 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 }); }
7791
8339
  }
7792
- 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: [{
7793
8341
  type: Component,
7794
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"] }]
7795
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: [{
@@ -7804,10 +8352,10 @@ class UnrealResumeSlotDirective {
7804
8352
  static ngTemplateContextGuard(_dir, _ctx) {
7805
8353
  return true;
7806
8354
  }
7807
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealResumeSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7808
- 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 }); }
7809
8357
  }
7810
- 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: [{
7811
8359
  type: Directive,
7812
8360
  args: [{ selector: 'ng-template[unrealResumeSlot]' }]
7813
8361
  }] });
@@ -7818,10 +8366,10 @@ class UnrealAfkSlotDirective {
7818
8366
  static ngTemplateContextGuard(_dir, _ctx) {
7819
8367
  return true;
7820
8368
  }
7821
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealAfkSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7822
- 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 }); }
7823
8371
  }
7824
- 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: [{
7825
8373
  type: Directive,
7826
8374
  args: [{ selector: 'ng-template[unrealAfkSlot]' }]
7827
8375
  }] });
@@ -7832,10 +8380,10 @@ class UnrealStatusSlotDirective {
7832
8380
  static ngTemplateContextGuard(_dir, _ctx) {
7833
8381
  return true;
7834
8382
  }
7835
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusSlotDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
7836
- 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 }); }
7837
8385
  }
7838
- 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: [{
7839
8387
  type: Directive,
7840
8388
  args: [{ selector: 'ng-template[unrealStatusSlot]' }]
7841
8389
  }] });
@@ -7858,10 +8406,10 @@ class UnrealSpinnerComponent {
7858
8406
  this.viewBox = computed(() => `0 0 ${this.size()} ${this.size()}`, /* @ts-ignore */
7859
8407
  ...(ngDevMode ? [{ debugName: "viewBox" }] : /* istanbul ignore next */ []));
7860
8408
  }
7861
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSpinnerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7862
- 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 }); }
7863
8411
  }
7864
- 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: [{
7865
8413
  type: Component,
7866
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"] }]
7867
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 }] }] } });
@@ -7892,10 +8440,10 @@ class UnrealStatusComponent {
7892
8440
  }
7893
8441
  #store;
7894
8442
  #loadingProgress;
7895
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7896
- 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 }); }
7897
8445
  }
7898
- 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: [{
7899
8447
  type: Component,
7900
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"] }]
7901
8449
  }], propDecorators: { uiClasses: [{ type: i0.Input, args: [{ isSignal: true, alias: "uiClasses", required: false }] }], contentTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentTemplate", required: false }] }] } });
@@ -7928,10 +8476,10 @@ class SafePipe {
7928
8476
  // content instead of disabling framework security checks.
7929
8477
  return (this.sanitizer.sanitize(context, value) ?? '');
7930
8478
  }
7931
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7932
- 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" }); }
7933
8481
  }
7934
- 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: [{
7935
8483
  type: Pipe,
7936
8484
  args: [{
7937
8485
  name: 'safe',
@@ -8044,10 +8592,10 @@ class VideoStatsComponent {
8044
8592
  elements: this.elementsToShow(),
8045
8593
  }));
8046
8594
  }
8047
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8048
- 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 }); }
8049
8597
  }
8050
- 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: [{
8051
8599
  type: Component,
8052
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"] }]
8053
8601
  }], ctorParameters: () => [] });
@@ -8109,10 +8657,10 @@ class ResumeStreamButtonComponent {
8109
8657
  disconnect() {
8110
8658
  this.isSecondStart.set(true);
8111
8659
  }
8112
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8113
- 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 }); }
8114
8662
  }
8115
- 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: [{
8116
8664
  type: Component,
8117
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"] }]
8118
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 }] }] } });
@@ -8179,10 +8727,10 @@ class IntroSrcComponent {
8179
8727
  sourceElement.addEventListener('error', () => this.#store.dispatch(resetIntroSrc()));
8180
8728
  videoContainer.appendChild(videoElement);
8181
8729
  }
8182
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8183
- 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 }); }
8184
8732
  }
8185
- 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: [{
8186
8734
  type: Component,
8187
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"] }]
8188
8736
  }], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
@@ -8413,10 +8961,10 @@ class UnrealSceneComponent {
8413
8961
  },
8414
8962
  });
8415
8963
  }
8416
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.2", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
8417
- 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 }); }
8418
8966
  }
8419
- 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: [{
8420
8968
  type: Component,
8421
8969
  args: [{ selector: 'app-unreal-scene', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
8422
8970
  AfkTimeoutModalComponent,
@@ -8486,5 +9034,5 @@ function provideUnrealModule() {
8486
9034
  * Generated bundle index. Do not edit.
8487
9035
  */
8488
9036
 
8489
- export { AFKService, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, MINIMAL_FPS, MIN_CONTAINER_SIZE, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, 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 };
8490
9038
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map