@3dsource/angular-unreal-module 0.0.159 → 0.0.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, DestroyRef, ChangeDetectionStrategy, Component, Injectable, signal, effect, untracked, computed, input, ElementRef, viewChild, afterNextRender, HostListener, SecurityContext, Pipe, output, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
3
3
  import { SourceButtonComponent, SourceLoadingComponent } from '@3dsource/source-ui-native';
4
- import { Subject, skip, share, merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, timer, take, defer, switchMap, from, throwError, EMPTY, retry, timeout as timeout$1, repeat, BehaviorSubject, takeUntil as takeUntil$1, fromEvent, tap as tap$1, throttleTime, delay, concat, debounceTime, mergeMap, NEVER, ignoreElements, shareReplay, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith } from 'rxjs';
4
+ import { Subject, skip, share, merge, map, scan, timestamp, startWith, pairwise, concatMap, of, animationFrameScheduler, interval, Observable, timer, take, defer, switchMap, from, throwError, EMPTY, retry, timeout as timeout$1, repeat, BehaviorSubject, takeUntil as takeUntil$1, fromEvent, tap as tap$1, throttleTime, delay, concat, debounceTime, mergeMap, NEVER, ignoreElements, shareReplay, firstValueFrom, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith } from 'rxjs';
5
5
  import { createAction, props, createReducer, on, createFeature, Store, createSelector, provideState } from '@ngrx/store';
6
6
  import { whereNot, Falsy, Logger, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, generateUuid, Truthy, tapLog, Semaphore, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
7
7
  import { filter, map as map$1, distinctUntilChanged, takeWhile, takeUntil, catchError, tap, timeout, finalize, first, switchMap as switchMap$1, withLatestFrom, exhaustMap, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
@@ -172,7 +172,6 @@ const OrchestrationMessageTypes = {
172
172
  p2pEstablished: 'p2pEstablished',
173
173
  instanceReserved: 'instanceReserved',
174
174
  playerDisconnected: 'playerDisconnected',
175
- streamPreparedForUserInteraction: 'streamPreparedForUserInteraction',
176
175
  streamerDisconnected: 'streamerDisconnected',
177
176
  interruptClientStream: 'interruptClientStream',
178
177
  };
@@ -305,9 +304,11 @@ const setOrchestrationParameters = createAction(scoped `set orchestration parame
305
304
  /**
306
305
  * Dispatched when the orchestration server signals that the reserved
307
306
  * instance is ready to serve the stream (`instanceReady` WS message).
308
- * Emitted for observability only — no reducer state change.
307
+ * Carries the stream-machine coordinates — `originInstanceUri` is used to
308
+ * open the second (stream-machine) signalling socket. Also consumed by
309
+ * telemetry as a phase marker.
309
310
  */
310
- const instanceReady = createAction(scoped `instance ready`);
311
+ const instanceReady = createAction(scoped `instance ready`, props());
311
312
  /**
312
313
  * Dispatched when `RegionsPingService.getFastest()` begins — right
313
314
  * before the region-list HTTP request goes out. Bookends the entire
@@ -771,10 +772,18 @@ function IceCandidateHandler(msg) {
771
772
  this.onWebRtcIce$.next(msg.candidate);
772
773
  }
773
774
 
774
- function InstanceReadyHandler() {
775
- // No state change — emit the action purely so services that want to
776
- // observe orchestration progress (e.g. telemetry) have a hook.
777
- this.store.dispatch(instanceReady());
775
+ function InstanceReadyHandler(msg) {
776
+ const payload = msg?.payload;
777
+ if (!payload) {
778
+ Logger.warn('instanceReady received without payload — ignoring');
779
+ return;
780
+ }
781
+ // Emit for observability (telemetry) and store the stream-machine coords.
782
+ this.store.dispatch(instanceReady(payload));
783
+ // Open the SECOND socket directly to the stream machine and route the whole
784
+ // WebRTC signalling through it. The orchestration socket stays open for
785
+ // control/lifecycle.
786
+ this.connectToStreamMachine(payload.originInstanceUri);
778
787
  }
779
788
 
780
789
  function InstanceReservedHandler(msg) {
@@ -786,7 +795,9 @@ function InstanceReservedHandler(msg) {
786
795
  }
787
796
 
788
797
  function PingHandler() {
789
- this.send(JSON.stringify({
798
+ // Reply pong on the active signalling socket (stream socket in the new flow,
799
+ // relay socket in the old polling flow).
800
+ this.sendPlayerSignal(JSON.stringify({
790
801
  type: 'pong',
791
802
  source: 'front',
792
803
  }));
@@ -1242,10 +1253,10 @@ class WebrtcErrorModalComponent {
1242
1253
  close() {
1243
1254
  this.#dialogRef.close();
1244
1255
  }
1245
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1246
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: WebrtcErrorModalComponent, isStandalone: true, selector: "app-webrtc-error-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"src-modal\">\r\n <header class=\"src-modal__header\"></header>\r\n <section class=\"src-modal__body\">\r\n <div [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\r\n WebRTC error\r\n </div>\r\n <div style=\"text-align: left\">\r\n An internet connection type (WebRTC) appears to be blocked either by your\r\n browser settings or your current network. If WebRTC is blocked on your\r\n browser you may be able to adjust these settings yourself based on\r\n instructions here:\r\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\r\n >https://myownconference.com/blog/en/webrtc/</a\r\n >\r\n <br /><br />\r\n Trying a different web browser may help confirm this as well. If WebRTC is\r\n blocked by your network, try switching to a different network if possible\r\n or contact your network administrator.<br /><br />\r\n WebRTC is common, safe and increasingly utilised method for streaming real\r\n time 3D experiences via a web browser. It typically consumes no more\r\n bandwidth than streaming an HD video.\r\n </div>\r\n </section>\r\n <footer class=\"src-modal__footer\">\r\n <src-button\r\n (onClick)=\"closeModalWithCirrusDisconnect()\"\r\n [data-testid]=\"'close-webrtc-error-modal'\"\r\n weight=\"primary\"\r\n >\r\n Ok\r\n </src-button>\r\n </footer>\r\n</div>\r\n", styles: [".src-modal{--modalBodyPadding: 20px 8px 20px 20px;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto}.src-modal__body{width:100%}.src-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}.src-modal__body a{word-wrap:break-word;white-space:normal}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1256
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1257
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: WebrtcErrorModalComponent, isStandalone: true, selector: "app-webrtc-error-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"src-modal\">\r\n <header class=\"src-modal__header\"></header>\r\n <section class=\"src-modal__body\">\r\n <div [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\r\n WebRTC error\r\n </div>\r\n <div style=\"text-align: left\">\r\n An internet connection type (WebRTC) appears to be blocked either by your\r\n browser settings or your current network. If WebRTC is blocked on your\r\n browser you may be able to adjust these settings yourself based on\r\n instructions here:\r\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\r\n >https://myownconference.com/blog/en/webrtc/</a\r\n >\r\n <br /><br />\r\n Trying a different web browser may help confirm this as well. If WebRTC is\r\n blocked by your network, try switching to a different network if possible\r\n or contact your network administrator.<br /><br />\r\n WebRTC is common, safe and increasingly utilised method for streaming real\r\n time 3D experiences via a web browser. It typically consumes no more\r\n bandwidth than streaming an HD video.\r\n </div>\r\n </section>\r\n <footer class=\"src-modal__footer\">\r\n <src-button\r\n (onClick)=\"closeModalWithCirrusDisconnect()\"\r\n [data-testid]=\"'close-webrtc-error-modal'\"\r\n weight=\"primary\"\r\n >\r\n Ok\r\n </src-button>\r\n </footer>\r\n</div>\r\n", styles: [".src-modal{--modalBodyPadding: 20px 8px 20px 20px;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto}.src-modal__body{width:100%}.src-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}.src-modal__body a{word-wrap:break-word;white-space:normal}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1247
1258
  }
1248
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WebrtcErrorModalComponent, decorators: [{
1259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WebrtcErrorModalComponent, decorators: [{
1249
1260
  type: Component,
1250
1261
  args: [{ selector: 'app-webrtc-error-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "<div [attr.data-testid]=\"'webrtc-error-modal'\" class=\"src-modal\">\r\n <header class=\"src-modal__header\"></header>\r\n <section class=\"src-modal__body\">\r\n <div [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\r\n WebRTC error\r\n </div>\r\n <div style=\"text-align: left\">\r\n An internet connection type (WebRTC) appears to be blocked either by your\r\n browser settings or your current network. If WebRTC is blocked on your\r\n browser you may be able to adjust these settings yourself based on\r\n instructions here:\r\n <a href=\"https://myownconference.com/blog/en/webrtc/\" target=\"blank\"\r\n >https://myownconference.com/blog/en/webrtc/</a\r\n >\r\n <br /><br />\r\n Trying a different web browser may help confirm this as well. If WebRTC is\r\n blocked by your network, try switching to a different network if possible\r\n or contact your network administrator.<br /><br />\r\n WebRTC is common, safe and increasingly utilised method for streaming real\r\n time 3D experiences via a web browser. It typically consumes no more\r\n bandwidth than streaming an HD video.\r\n </div>\r\n </section>\r\n <footer class=\"src-modal__footer\">\r\n <src-button\r\n (onClick)=\"closeModalWithCirrusDisconnect()\"\r\n [data-testid]=\"'close-webrtc-error-modal'\"\r\n weight=\"primary\"\r\n >\r\n Ok\r\n </src-button>\r\n </footer>\r\n</div>\r\n", styles: [".src-modal{--modalBodyPadding: 20px 8px 20px 20px;display:grid;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto}.src-modal__body{width:100%}.src-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}.src-modal__body a{word-wrap:break-word;white-space:normal}\n"] }]
1251
1262
  }] });
@@ -1452,10 +1463,10 @@ class CommandTelemetryService extends SubService {
1452
1463
  this.trackTime(out);
1453
1464
  funcToDecorate(out);
1454
1465
  }
1455
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1456
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CommandTelemetryService, providedIn: 'root' }); }
1466
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1467
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CommandTelemetryService, providedIn: 'root' }); }
1457
1468
  }
1458
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: CommandTelemetryService, decorators: [{
1469
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: CommandTelemetryService, decorators: [{
1459
1470
  type: Injectable,
1460
1471
  args: [{ providedIn: 'root' }]
1461
1472
  }], ctorParameters: () => [] });
@@ -1476,10 +1487,10 @@ class DevModeService {
1476
1487
  localStorage.removeItem('devMode');
1477
1488
  }
1478
1489
  }
1479
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1480
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DevModeService, providedIn: 'root' }); }
1490
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1491
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DevModeService, providedIn: 'root' }); }
1481
1492
  }
1482
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: DevModeService, decorators: [{
1493
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: DevModeService, decorators: [{
1483
1494
  type: Injectable,
1484
1495
  args: [{
1485
1496
  providedIn: 'root',
@@ -1782,16 +1793,19 @@ class RegionsPingService {
1782
1793
  }
1783
1794
  }
1784
1795
  }
1785
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1786
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RegionsPingService, providedIn: 'root' }); }
1796
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1797
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: RegionsPingService, providedIn: 'root' }); }
1787
1798
  }
1788
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: RegionsPingService, decorators: [{
1799
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: RegionsPingService, decorators: [{
1789
1800
  type: Injectable,
1790
1801
  args: [{ providedIn: 'root' }]
1791
1802
  }], ctorParameters: () => [] });
1792
1803
 
1793
1804
  function StreamerListHandler(msg) {
1794
- this.send(JSON.stringify({
1805
+ // `subscribe` is player signalling — reply on the active signalling socket
1806
+ // (stream socket in the new flow, relay socket in the old polling flow),
1807
+ // matching the socket the `streamerList` arrived on.
1808
+ this.sendPlayerSignal(JSON.stringify({
1795
1809
  type: 'subscribe',
1796
1810
  streamerId: msg.ids[0],
1797
1811
  }));
@@ -2042,6 +2056,16 @@ class SignallingService extends SubService {
2042
2056
  // of how many pipelines have `takeUntil(this.abort$)` attached.
2043
2057
  this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
2044
2058
  this.ws = null;
2059
+ // Second socket, opened directly to the stream machine (originInstanceUri
2060
+ // from `instanceReady`). Carries the whole WebRTC signalling
2061
+ // (config/offer/answer/iceCandidate/ping); `this.ws` stays for control.
2062
+ this.streamWs = null;
2063
+ // True once the stream-machine socket is established. After this the
2064
+ // orchestration server intentionally closes the browser's orchestration
2065
+ // socket (instance is "occupied") — that close must NOT be treated as a drop.
2066
+ this.streamMachineConnected = false;
2067
+ // Bounded retry counter for the stream-machine connection (new-flow only).
2068
+ this.streamMachineRetryCount = 0;
2045
2069
  this.wsMsgHandlers = {};
2046
2070
  this.init();
2047
2071
  }
@@ -2064,27 +2088,18 @@ class SignallingService extends SubService {
2064
2088
  // and `init()` runs once in the constructor. If these subs were torn
2065
2089
  // down by abort$, a second in-injector session (e.g. navigating between
2066
2090
  // two routes that both host <app-unreal-scene>) would never send
2067
- // `p2pEstablished` / `streamPreparedForUserInteraction`. `send()` is
2068
- // already a no-op on a closed WS, so there's no risk of stray messages.
2091
+ // `p2pEstablished`. `sendPlayerSignal()` is already a no-op on a closed WS,
2092
+ // so there's no risk of stray messages.
2069
2093
  this.actions$
2070
2094
  .pipe(ofType(dataChannelReady), takeUntil(this.destroy$))
2071
2095
  .subscribe(() => {
2072
- this.send({
2096
+ // Player signalling → active signalling socket (stream/relay).
2097
+ this.sendPlayerSignal({
2073
2098
  type: 'p2pEstablished',
2074
2099
  source: 'front',
2075
2100
  sessionId: generateUuid(),
2076
2101
  });
2077
2102
  });
2078
- this.actions$
2079
- .pipe(ofType(dataChannelReady), switchMap(() => this.store
2080
- .select(unrealFeature.selectViewportReady)
2081
- .pipe(filter(Truthy), take(1))), takeUntil(this.destroy$))
2082
- .subscribe(() => {
2083
- this.send({
2084
- type: 'streamPreparedForUserInteraction',
2085
- correlationId: generateUuid(),
2086
- });
2087
- });
2088
2103
  }
2089
2104
  connectToSignaling(urlList) {
2090
2105
  if (this.unrealConfig?.playwright) {
@@ -2310,12 +2325,28 @@ class SignallingService extends SubService {
2310
2325
  }
2311
2326
  addWsHandlers(ws) {
2312
2327
  ws.onmessage = OnMessageHandler.bind(this);
2313
- ws.onclose = OnCloseHandler.bind(this);
2314
- ws.onerror = OnErrorHandler.bind(this);
2328
+ // Orchestration socket close/error is benign ONCE the stream machine is
2329
+ // connected (orchestration closes it after marking the instance occupied).
2330
+ ws.onclose = (e) => this.onOrchestrationClose(e);
2331
+ ws.onerror = (e) => this.onOrchestrationError(e);
2315
2332
  this.ws = ws;
2316
2333
  Logger.log('WS HANDLERS ADDED');
2317
2334
  OnOpenHandler.call(this);
2318
2335
  }
2336
+ onOrchestrationClose(e) {
2337
+ if (this.streamMachineConnected) {
2338
+ Logger.log(`Orchestration socket closed after occupancy (benign): ${e.code}`);
2339
+ return;
2340
+ }
2341
+ OnCloseHandler.call(this, e);
2342
+ }
2343
+ onOrchestrationError(e) {
2344
+ if (this.streamMachineConnected) {
2345
+ Logger.log('Orchestration socket error after occupancy (benign)');
2346
+ return;
2347
+ }
2348
+ OnErrorHandler.call(this, e);
2349
+ }
2319
2350
  /**
2320
2351
  * Adopt a live signalling socket opened by the `stream-prefetch.js`
2321
2352
  * head-script. The socket is already OPEN and `requestStream` was already
@@ -2332,6 +2363,79 @@ class SignallingService extends SubService {
2332
2363
  this.ws = ws;
2333
2364
  Logger.log('WS ADOPTED (handshake already done, requestStream already sent)');
2334
2365
  }
2366
+ /**
2367
+ * Open the SECOND socket, directly to the stream machine's Cirrus
2368
+ * (`originInstanceUri` from `instanceReady`). The whole WebRTC signalling
2369
+ * (config/offer/answer/iceCandidate/ping) flows over this socket; the
2370
+ * orchestration socket (`this.ws`) stays open for control/lifecycle.
2371
+ *
2372
+ * "Just connect" — nothing is sent on open (the machine is already reserved
2373
+ * for this session): Cirrus pushes `config` and Unreal sends the `offer`.
2374
+ */
2375
+ connectToStreamMachine(originInstanceUri) {
2376
+ if (!originInstanceUri) {
2377
+ Logger.warn('instanceReady without originInstanceUri — stream socket not opened');
2378
+ return;
2379
+ }
2380
+ // Guard: a stream socket is already live. A duplicate / re-sent
2381
+ // `instanceReady` must NOT tear down the active stream.
2382
+ if (this.streamMachineConnected) {
2383
+ Logger.warn('Stream machine already connected — ignoring duplicate instanceReady');
2384
+ return;
2385
+ }
2386
+ // Drop any prior (half-open) stream socket AND cancel any in-flight connect
2387
+ // before a fresh attempt — e.g. a retry that resolved to a different machine
2388
+ // or two instanceReady messages before the first connect resolves.
2389
+ this.teardownStreamWs();
2390
+ this.streamWsSub = createWebSocket(originInstanceUri)
2391
+ .pipe(takeUntil(this.abort$), takeUntil(this.destroy$), take(1))
2392
+ .subscribe({
2393
+ next: (ws) => {
2394
+ this.streamMachineRetryCount = 0;
2395
+ this.addStreamWsHandlers(ws);
2396
+ },
2397
+ error: (err) => {
2398
+ Logger.error('Stream-machine WS failed to connect', err?.message);
2399
+ this.retryStreamMachine();
2400
+ },
2401
+ });
2402
+ }
2403
+ /**
2404
+ * The stream-machine (Cirrus) connection failed. Re-request a machine over
2405
+ * the still-open orchestration socket: orchestration issues a new
2406
+ * streamRequestId and a fresh `instanceReady(originInstanceUri)`, which calls
2407
+ * {@link connectToStreamMachine} again on a different machine. Bounded by
2408
+ * {@link INNER_RETRY_LIMIT}; on exhaustion the stream is dropped.
2409
+ */
2410
+ retryStreamMachine() {
2411
+ if (this.streamMachineRetryCount >= INNER_RETRY_LIMIT) {
2412
+ Logger.error('Stream-machine connection exhausted retries — dropping');
2413
+ this.store.dispatch(disconnectStream({
2414
+ reason: DisconnectReason.ReconnectFailed,
2415
+ message: 'Could not connect to the stream machine',
2416
+ }));
2417
+ return;
2418
+ }
2419
+ this.streamMachineRetryCount++;
2420
+ this.orchestrationRetrySub?.unsubscribe();
2421
+ this.orchestrationRetrySub = timer(POLLING_TIME)
2422
+ .pipe(takeUntil(this.abort$))
2423
+ .subscribe(() => this.sendRequestStream());
2424
+ }
2425
+ addStreamWsHandlers(ws) {
2426
+ ws.onmessage = OnMessageHandler.bind(this);
2427
+ ws.onclose = OnCloseHandler.bind(this);
2428
+ ws.onerror = OnErrorHandler.bind(this);
2429
+ this.streamWs = ws;
2430
+ this.streamMachineConnected = true;
2431
+ Logger.log('STREAM-MACHINE WS CONNECTED');
2432
+ }
2433
+ teardownStreamWs() {
2434
+ this.streamWsSub?.unsubscribe();
2435
+ this.teardownSocket(this.streamWs, 1000, DisconnectReason.NewConnectionAttempt);
2436
+ this.streamWs = null;
2437
+ this.streamMachineConnected = false;
2438
+ }
2335
2439
  showStatusMessage(statusMessage) {
2336
2440
  this.store.dispatch(setStatusMessage({ statusMessage }));
2337
2441
  }
@@ -2343,36 +2447,54 @@ class SignallingService extends SubService {
2343
2447
  const { code, reason } = data;
2344
2448
  this.onWebRtcAnswer$.next(null);
2345
2449
  this.orchestrationRetrySub?.unsubscribe();
2346
- if (this.ws) {
2347
- Logger.warn('Closing existing WebSocket connection');
2348
- const ws = this.ws;
2349
- ws.onmessage = () => null;
2350
- ws.onerror = () => null;
2351
- ws.onclose = () => null;
2352
- ws.onopen = () => null;
2353
- this.ws = null;
2354
- if (ws.readyState === WebSocket.CONNECTING ||
2355
- ws.readyState === WebSocket.OPEN) {
2356
- ws.addEventListener('open', () => ws.close(code, reason), {
2357
- once: true,
2358
- });
2359
- ws.close(code, reason);
2360
- }
2450
+ this.streamWsSub?.unsubscribe();
2451
+ this.teardownSocket(this.ws, code, reason);
2452
+ this.ws = null;
2453
+ this.teardownSocket(this.streamWs, code, reason);
2454
+ this.streamWs = null;
2455
+ this.streamMachineConnected = false;
2456
+ this.streamMachineRetryCount = 0;
2457
+ }
2458
+ teardownSocket(ws, code, reason) {
2459
+ if (!ws) {
2460
+ return;
2461
+ }
2462
+ Logger.warn('Closing existing WebSocket connection');
2463
+ ws.onmessage = () => null;
2464
+ ws.onerror = () => null;
2465
+ ws.onclose = () => null;
2466
+ ws.onopen = () => null;
2467
+ if (ws.readyState === WebSocket.CONNECTING ||
2468
+ ws.readyState === WebSocket.OPEN) {
2469
+ ws.addEventListener('open', () => ws.close(code, reason), { once: true });
2470
+ ws.close(code, reason);
2361
2471
  }
2362
2472
  }
2363
2473
  send(data) {
2474
+ this.sendTo(this.ws, data);
2475
+ }
2476
+ /**
2477
+ * Send player signalling (offer/answer/iceCandidate/pong/p2pEstablished/
2478
+ * subscribe) over the ACTIVE signalling socket: the stream-machine socket in
2479
+ * the new flow (once connected), or the orchestration/relay socket in the old
2480
+ * polling flow (where there is no separate stream socket).
2481
+ */
2482
+ sendPlayerSignal(data) {
2483
+ this.sendTo(this.streamMachineConnected ? this.streamWs : this.ws, data);
2484
+ }
2485
+ sendTo(ws, data) {
2364
2486
  let dataString = data;
2365
2487
  if (typeof dataString !== 'string') {
2366
2488
  dataString = JSON.stringify(data);
2367
2489
  }
2368
- if (this.ws && this.ws.readyState === WS_OPEN_STATE) {
2490
+ if (ws && ws.readyState === WS_OPEN_STATE) {
2369
2491
  // Only parse-for-display when the logger would actually print — avoids a
2370
2492
  // JSON.parse on every outbound message in production (logger is a no-op).
2371
2493
  if (Logger.isDevMode) {
2372
2494
  Logger.colored(...COLOR_CODES.TO_CIRRUS, JSON.parse(dataString));
2373
2495
  }
2374
2496
  try {
2375
- this.ws.send(dataString);
2497
+ ws.send(dataString);
2376
2498
  }
2377
2499
  catch {
2378
2500
  Logger.error('No Websocket');
@@ -2421,19 +2543,27 @@ class SignallingService extends SubService {
2421
2543
  });
2422
2544
  }
2423
2545
  /**
2424
- * Check if the WebSocket connection is open and ready.
2546
+ * Check if the ACTIVE player-signalling socket is open and ready. Mirrors
2547
+ * {@link sendPlayerSignal}'s socket choice so peer-reconnect gating is correct
2548
+ * in both flows:
2549
+ * - NEW flow (`streamMachineConnected`): the stream-machine socket
2550
+ * (`streamWs`). The orchestration socket (`this.ws`) is intentionally
2551
+ * closed after occupancy, so checking it would wrongly report "not
2552
+ * connected" and block reconnect.
2553
+ * - OLD polling flow: the single relay socket (`this.ws`) — unchanged.
2425
2554
  */
2426
2555
  isConnected() {
2427
- return !!(this.ws && this.ws.readyState === WS_OPEN_STATE);
2556
+ const activeWs = this.streamMachineConnected ? this.streamWs : this.ws;
2557
+ return !!(activeWs && activeWs.readyState === WS_OPEN_STATE);
2428
2558
  }
2429
2559
  getRegion() {
2430
2560
  Logger.log('Start Getting Region');
2431
2561
  return this.regionsPingService.getFastest().pipe(take(1));
2432
2562
  }
2433
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2434
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SignallingService, providedIn: 'root' }); }
2563
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2564
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SignallingService, providedIn: 'root' }); }
2435
2565
  }
2436
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SignallingService, decorators: [{
2566
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SignallingService, decorators: [{
2437
2567
  type: Injectable,
2438
2568
  args: [{ providedIn: 'root' }]
2439
2569
  }], ctorParameters: () => [] });
@@ -2464,10 +2594,57 @@ class VideoService extends SubService {
2464
2594
  ? interval(250).pipe(map(() => track.pcClient), switchMap$1((pcClient) => from(this.getStats(pcClient))), filter(Truthy))
2465
2595
  : of(null)), filter(Truthy), share());
2466
2596
  this.destroyAbort = new AbortController();
2597
+ // Teardown is wired ONCE here instead of per `create()`. The per-create
2598
+ // subscription added one more live handler for every session, and the
2599
+ // `take(1)` that bounded it let the shared `disconnect$` refCount fall to
2600
+ // zero between sessions — which re-armed its `skip(1)` and could swallow
2601
+ // the next real disconnect. `destroy()` is idempotent, so reacting to every
2602
+ // emission from a single subscription is equivalent but leak-free.
2603
+ this.disconnect$
2604
+ .pipe(takeUntil$1(this.destroy$))
2605
+ .subscribe(() => this.destroy());
2467
2606
  this.destroyRef.onDestroy(() => this.destroy());
2468
2607
  }
2608
+ /**
2609
+ * Sets (or replaces) the DOM target for the streaming `<video>`.
2610
+ *
2611
+ * Attaching is idempotent and order-independent on purpose: `create()` is
2612
+ * driven by the network (`config`, or a synchronous prefetch adoption) while
2613
+ * the container comes from the scene component's render pass, so either side
2614
+ * can win the race. If `create()` got there first the element is detached —
2615
+ * adopt it here instead of leaving it orphaned (which showed up as a white
2616
+ * scene with an empty `#player` and no `videoStreamInitialized`).
2617
+ */
2469
2618
  setContainer(container = null) {
2470
2619
  this.container = container;
2620
+ this.attachVideoToContainer();
2621
+ }
2622
+ /**
2623
+ * Moves `this.video` under the current container unless it is already there.
2624
+ * `parentElement` (not `isConnected`) is the guard: it also covers a swap to a
2625
+ * different container while the element sits in a still-live old one.
2626
+ */
2627
+ attachVideoToContainer() {
2628
+ const container = this.container;
2629
+ const video = this.video;
2630
+ if (!container || !video || video.parentElement === container) {
2631
+ return;
2632
+ }
2633
+ container.appendChild(video);
2634
+ // Positive confirmation that the element the stream renders into actually
2635
+ // exists in the document — the failure this guards against is silent
2636
+ // (detached element, white scene), so the healthy path must be visible too.
2637
+ Logger.info(`VideoService: <video id="${video.id}"> attached to #${container.id || '(container without id)'} | inDom=${video.isConnected} | hasStream=${!!video.srcObject}`);
2638
+ // A detached element cannot start playing, so the play() issued by
2639
+ // attachVideoStream before this point was lost — re-issue it now that the
2640
+ // element is in the DOM. Deliberately NOT gated on `video.paused`: per spec
2641
+ // play() clears `paused` synchronously, so a play that was accepted but
2642
+ // never produced frames (detached element) is indistinguishable from a
2643
+ // healthy one by that flag. play() on an already-playing element is a
2644
+ // no-op, which makes the unconditional call the safe direction.
2645
+ if (video.srcObject) {
2646
+ void this.safePlay(video);
2647
+ }
2471
2648
  }
2472
2649
  setLatencyTimings(latencyTimings) {
2473
2650
  this.latencyTestTimings.SetUETimings(latencyTimings);
@@ -2477,11 +2654,8 @@ class VideoService extends SubService {
2477
2654
  }
2478
2655
  create() {
2479
2656
  if (!this.container) {
2480
- Logger.warn('VideoService.create(): no container set; <video> will be built but never attached to the DOM, which suppresses autoplay.');
2657
+ Logger.warn('VideoService.create(): no container set yet; <video> is built detached and will be attached (and replayed) by the next setContainer().');
2481
2658
  }
2482
- this.disconnect$
2483
- .pipe(take(1), takeUntil$1(this.destroy$))
2484
- .subscribe(() => this.destroy());
2485
2659
  this.destroy();
2486
2660
  this.createWebRtcVideo();
2487
2661
  this.createWebRtcAudio();
@@ -2529,10 +2703,10 @@ class VideoService extends SubService {
2529
2703
  return;
2530
2704
  }
2531
2705
  catch (error) {
2532
- if (error &&
2533
- typeof error === 'object' &&
2534
- 'name' in error &&
2535
- error?.name !== 'NotAllowedError') {
2706
+ // Only an autoplay block is worth retrying. Anything else (AbortError,
2707
+ // a decode failure, a non-Error rejection value) is reported and left
2708
+ // alone — retrying muted or on a gesture cannot fix those.
2709
+ if (!isAutoplayBlocked(error)) {
2536
2710
  Logger.warn('safePlay() rejected with non-autoplay error:', error);
2537
2711
  return;
2538
2712
  }
@@ -2546,33 +2720,43 @@ class VideoService extends SubService {
2546
2720
  catch {
2547
2721
  /* fall through */
2548
2722
  }
2549
- // Last resort: wait for the next real user gesture and play with/without sound
2550
- const resume = async () => {
2551
- try {
2552
- // choose whether to unmute here
2553
- // video.muted = false;
2554
- await video.play();
2555
- }
2556
- catch {
2557
- /* ignore, maybe another gesture needed */
2558
- }
2559
- finally {
2560
- window.removeEventListener('pointerdown', resume, true);
2561
- window.removeEventListener('keydown', resume, true);
2562
- }
2563
- };
2564
- window.addEventListener('pointerdown', resume, {
2565
- once: true,
2566
- capture: true,
2567
- signal: this.destroyAbort.signal,
2568
- });
2569
- window.addEventListener('keydown', resume, {
2570
- once: true,
2571
- capture: true,
2572
- signal: this.destroyAbort.signal,
2573
- });
2723
+ // Last resort: wait for the next real user gesture.
2724
+ // choose whether to unmute in armGestureRetry's caller
2725
+ this.armGestureRetry(video);
2574
2726
  }
2575
2727
  }
2728
+ /**
2729
+ * Retries playback on the next real user gesture — the only thing that lifts
2730
+ * a browser autoplay block. Shared by `safePlay()` and `playAudio()` so both
2731
+ * media elements use one arming/cleanup path.
2732
+ *
2733
+ * Listeners are `once` + `capture` and bound to `destroyAbort.signal`, so a
2734
+ * `destroy()` in this session drops them instead of leaking into the next one.
2735
+ */
2736
+ armGestureRetry(element) {
2737
+ const resume = async () => {
2738
+ try {
2739
+ await element.play();
2740
+ }
2741
+ catch {
2742
+ /* ignore — another gesture may be needed */
2743
+ }
2744
+ finally {
2745
+ window.removeEventListener('pointerdown', resume, true);
2746
+ window.removeEventListener('keydown', resume, true);
2747
+ }
2748
+ };
2749
+ window.addEventListener('pointerdown', resume, {
2750
+ once: true,
2751
+ capture: true,
2752
+ signal: this.destroyAbort.signal,
2753
+ });
2754
+ window.addEventListener('keydown', resume, {
2755
+ once: true,
2756
+ capture: true,
2757
+ signal: this.destroyAbort.signal,
2758
+ });
2759
+ }
2576
2760
  async getStats(pcClient) {
2577
2761
  if (!pcClient || pcClient.connectionState === 'closed') {
2578
2762
  return null;
@@ -2603,47 +2787,7 @@ class VideoService extends SubService {
2603
2787
  stats.forEach((report) => {
2604
2788
  // Get the inbound-rtp for video
2605
2789
  if (report.type === 'inbound-rtp' && report.kind === 'video') {
2606
- Object.assign(newStat, report);
2607
- newStat.bytesReceivedStart =
2608
- this.aggregatedStats && this.aggregatedStats.bytesReceivedStart
2609
- ? this.aggregatedStats.bytesReceivedStart
2610
- : report.bytesReceived;
2611
- newStat.framesDecodedStart =
2612
- this.aggregatedStats && this.aggregatedStats.framesDecodedStart
2613
- ? this.aggregatedStats.framesDecodedStart
2614
- : report.framesDecoded;
2615
- newStat.timestampStart =
2616
- this.aggregatedStats && this.aggregatedStats.timestampStart
2617
- ? this.aggregatedStats.timestampStart
2618
- : report.timestamp;
2619
- if (this.aggregatedStats && this.aggregatedStats.timestamp) {
2620
- // Get the mimetype of the video codec being used
2621
- if (report.codecId &&
2622
- this.aggregatedStats.codecs &&
2623
- Object.hasOwn(this.aggregatedStats.codecs, report.codecId)) {
2624
- newStat.videoCodec = this.aggregatedStats.codecs[report.codecId];
2625
- }
2626
- if (this.aggregatedStats.bytesReceived) {
2627
- // bitrate = bits received since last time / number of ms since last time
2628
- // This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
2629
- newStat.bitrate = Math.floor((8 *
2630
- ((newStat.bytesReceived || 0) -
2631
- this.aggregatedStats.bytesReceived)) /
2632
- (newStat.timestamp - this.aggregatedStats.timestamp));
2633
- }
2634
- if (this.aggregatedStats.bytesReceivedStart) {
2635
- newStat.avgBitrate = Math.floor((8 *
2636
- ((newStat.bytesReceived || 0) -
2637
- this.aggregatedStats.bytesReceivedStart)) /
2638
- (newStat.timestamp - this.aggregatedStats.timestampStart));
2639
- }
2640
- if (this.aggregatedStats.framesDecodedStart) {
2641
- newStat.avgFrameRate = Math.floor(((newStat.framesDecoded || 0) -
2642
- this.aggregatedStats.framesDecodedStart) /
2643
- ((newStat.timestamp - this.aggregatedStats.timestampStart) /
2644
- 1000));
2645
- }
2646
- }
2790
+ this.applyInboundVideoReport(newStat, report);
2647
2791
  }
2648
2792
  // Only read RTT from the *nominated* ICE candidate pair. The
2649
2793
  // stats report contains multiple `candidate-pair` entries (one
@@ -2662,15 +2806,7 @@ class VideoService extends SubService {
2662
2806
  }
2663
2807
  if (report.type === 'transport' &&
2664
2808
  Object.hasOwn(report, 'selectedCandidatePairId')) {
2665
- const selectedPair = stats.get(report.selectedCandidatePairId);
2666
- if (selectedPair) {
2667
- const local = stats.get(selectedPair.localCandidateId);
2668
- const remote = stats.get(selectedPair.remoteCandidateId);
2669
- newStat.selectedPair = {
2670
- local: { type: local?.candidateType, url: local?.url },
2671
- remote: { type: remote?.candidateType, url: remote?.url },
2672
- };
2673
- }
2809
+ this.applySelectedPair(newStat, stats, report.selectedCandidatePairId);
2674
2810
  }
2675
2811
  // Store mimetype of each codec
2676
2812
  if (Object.hasOwn(newStat, 'codecs') &&
@@ -2683,6 +2819,68 @@ class VideoService extends SubService {
2683
2819
  .replace('audio/', '');
2684
2820
  }
2685
2821
  });
2822
+ this.applySessionTotals(newStat);
2823
+ this.aggregatedStats = newStat;
2824
+ return this.onAggregatedStats(newStat);
2825
+ }
2826
+ /**
2827
+ * Folds one `inbound-rtp` video report into the accumulator: copies the raw
2828
+ * report, pins the per-session baselines on the first pass, then derives the
2829
+ * deltas relative to the previous pass.
2830
+ */
2831
+ applyInboundVideoReport(newStat, report) {
2832
+ Object.assign(newStat, report);
2833
+ const previous = this.aggregatedStats;
2834
+ newStat.bytesReceivedStart = pinBaseline(previous?.bytesReceivedStart, report.bytesReceived);
2835
+ newStat.framesDecodedStart = pinBaseline(previous?.framesDecodedStart, report.framesDecoded);
2836
+ newStat.timestampStart = pinBaseline(previous?.timestampStart, report.timestamp);
2837
+ // Nothing to diff against on the first report of a session.
2838
+ if (!previous?.timestamp) {
2839
+ return;
2840
+ }
2841
+ // Get the mimetype of the video codec being used
2842
+ if (report.codecId &&
2843
+ previous.codecs &&
2844
+ Object.hasOwn(previous.codecs, report.codecId)) {
2845
+ newStat.videoCodec = previous.codecs[report.codecId];
2846
+ }
2847
+ if (previous.bytesReceived) {
2848
+ // bitrate = bits received since last time / number of ms since last time
2849
+ // This is automatically in kbits (where k=1000) since time is in ms and stat we want is in seconds (so a '* 1000' then a '/ 1000' would negate each other)
2850
+ newStat.bitrate = Math.floor((8 * ((newStat.bytesReceived || 0) - previous.bytesReceived)) /
2851
+ (newStat.timestamp - previous.timestamp));
2852
+ }
2853
+ if (previous.bytesReceivedStart) {
2854
+ newStat.avgBitrate = Math.floor((8 * ((newStat.bytesReceived || 0) - previous.bytesReceivedStart)) /
2855
+ (newStat.timestamp - previous.timestampStart));
2856
+ }
2857
+ if (previous.framesDecodedStart) {
2858
+ newStat.avgFrameRate = Math.floor(((newStat.framesDecoded || 0) - previous.framesDecodedStart) /
2859
+ ((newStat.timestamp - previous.timestampStart) / 1000));
2860
+ }
2861
+ }
2862
+ /**
2863
+ * Resolves the transport's selected candidate pair into the local/remote
2864
+ * descriptors shown in the stats overlay. Silently skips when the pair (or
2865
+ * either of its candidates) is missing from this report.
2866
+ */
2867
+ applySelectedPair(newStat, stats, selectedCandidatePairId) {
2868
+ if (!selectedCandidatePairId) {
2869
+ return;
2870
+ }
2871
+ const selectedPair = stats.get(selectedCandidatePairId);
2872
+ if (!selectedPair) {
2873
+ return;
2874
+ }
2875
+ const local = stats.get(selectedPair.localCandidateId);
2876
+ const remote = stats.get(selectedPair.remoteCandidateId);
2877
+ newStat.selectedPair = {
2878
+ local: { type: local?.candidateType, url: local?.url },
2879
+ remote: { type: remote?.candidateType, url: remote?.url },
2880
+ };
2881
+ }
2882
+ /** Per-session values that do not come from a single report. */
2883
+ applySessionTotals(newStat) {
2686
2884
  newStat.pixelRatio = +window.devicePixelRatio.toFixed(2);
2687
2885
  if (this.aggregatedStats.receiveToCompositeMs) {
2688
2886
  newStat.receiveToCompositeMs = this.aggregatedStats.receiveToCompositeMs;
@@ -2690,8 +2888,6 @@ class VideoService extends SubService {
2690
2888
  }
2691
2889
  // Calculate duration of run
2692
2890
  newStat.runTime = Math.floor((newStat.timestamp - newStat.timestampStart) / 1000);
2693
- this.aggregatedStats = newStat;
2694
- return this.onAggregatedStats(newStat);
2695
2891
  }
2696
2892
  onAggregatedStats(aggregatedStats) {
2697
2893
  return {
@@ -2716,6 +2912,11 @@ class VideoService extends SubService {
2716
2912
  // on to the RTCRtpReceiver until GC eventually clears srcObject.
2717
2913
  this.video.pause();
2718
2914
  this.video.srcObject = null;
2915
+ // Remove exactly the element we own. Clearing every child of
2916
+ // `this.container` instead would wipe DOM this service never created —
2917
+ // including the children of a container that was swapped in after
2918
+ // `create()`, since `container` always points at the newest one.
2919
+ this.video.remove();
2719
2920
  if (this.audio) {
2720
2921
  this.audio.pause();
2721
2922
  this.audio.srcObject = null;
@@ -2723,12 +2924,12 @@ class VideoService extends SubService {
2723
2924
  this.video = null;
2724
2925
  this.audio = null;
2725
2926
  this.videoTrack$.next(null);
2726
- // Reset stat state to prevent stale deltas on next session
2927
+ // Reset stat state to prevent stale deltas on next session — including the
2928
+ // latency timings, which otherwise carry the previous session's UE/display
2929
+ // deltas into the first measurements of the next one.
2727
2930
  this.aggregatedStats = {};
2728
2931
  this.VideoEncoderQP = 0;
2729
- while (this.container?.firstChild) {
2730
- this.container.removeChild(this.container.firstChild);
2731
- }
2932
+ this.latencyTestTimings = new LatencyTimings();
2732
2933
  }
2733
2934
  changeStatusMainVideoOnScene(isVideoPlaying) {
2734
2935
  if (this.isVideoPlaying() === isVideoPlaying) {
@@ -2753,7 +2954,8 @@ class VideoService extends SubService {
2753
2954
  video.preload = 'auto';
2754
2955
  video.disablePictureInPicture = true;
2755
2956
  this.video = video;
2756
- this.container?.appendChild(this.video);
2957
+ this.attachVideoToContainer();
2958
+ Logger.info(`VideoService.create(): <video id="${video.id}"> created | inDom=${video.isConnected}`);
2757
2959
  fromEvent(this.video, 'play')
2758
2960
  .pipe(takeUntil$1(merge(this.disconnect$, this.destroy$)))
2759
2961
  .subscribe(() => this.changeStatusMainVideoOnScene(true));
@@ -2769,41 +2971,40 @@ class VideoService extends SubService {
2769
2971
  async playAudio(audio) {
2770
2972
  try {
2771
2973
  await audio.play();
2772
- return;
2773
2974
  }
2774
2975
  catch {
2775
- // Wait for the next real user gesture and retry.
2776
- const resume = async () => {
2777
- try {
2778
- await audio.play();
2779
- }
2780
- catch {
2781
- /* ignore — caller may need another gesture */
2782
- }
2783
- finally {
2784
- window.removeEventListener('pointerdown', resume, true);
2785
- window.removeEventListener('keydown', resume, true);
2786
- }
2787
- };
2788
- window.addEventListener('pointerdown', resume, {
2789
- once: true,
2790
- capture: true,
2791
- signal: this.destroyAbort.signal,
2792
- });
2793
- window.addEventListener('keydown', resume, {
2794
- once: true,
2795
- capture: true,
2796
- signal: this.destroyAbort.signal,
2797
- });
2976
+ // Any rejection here is treated as "needs a gesture": unlike video, audio
2977
+ // has no muted fallback left to try.
2978
+ this.armGestureRetry(audio);
2798
2979
  }
2799
2980
  }
2800
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2801
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VideoService, providedIn: 'root' }); }
2981
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: VideoService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
2982
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: VideoService, providedIn: 'root' }); }
2802
2983
  }
2803
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VideoService, decorators: [{
2984
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: VideoService, decorators: [{
2804
2985
  type: Injectable,
2805
2986
  args: [{ providedIn: 'root' }]
2806
2987
  }], ctorParameters: () => [] });
2988
+ /**
2989
+ * Keeps a per-session baseline once it has been pinned, otherwise starts it from
2990
+ * the current report. Mirrors the original `previous?.x ? previous.x : now`
2991
+ * chain — `0` counts as "not pinned yet", which is what the deltas below expect.
2992
+ */
2993
+ function pinBaseline(pinned, current) {
2994
+ return (pinned || current);
2995
+ }
2996
+ /**
2997
+ * Whether a rejected `play()` was blocked by the browser autoplay policy.
2998
+ * Narrows an `unknown` rejection value without assuming it is an Error — a
2999
+ * rejection with a non-object value must NOT be mistaken for an autoplay block,
3000
+ * or it would trigger a pointless muted/gesture retry cycle.
3001
+ */
3002
+ function isAutoplayBlocked(error) {
3003
+ return (!!error &&
3004
+ typeof error === 'object' &&
3005
+ 'name' in error &&
3006
+ error.name === 'NotAllowedError');
3007
+ }
2807
3008
 
2808
3009
  class WebRtcPlayerService extends SubService {
2809
3010
  constructor() {
@@ -3257,10 +3458,25 @@ class WebRtcPlayerService extends SubService {
3257
3458
  .pipe(map$1(() => this.pcClient?.connectionState), takeUntil(merge(this.disconnect$, this.destroy$)))
3258
3459
  .subscribe((state) => {
3259
3460
  Logger.info('[RTCPeerConnection state] |', state, '|');
3461
+ // A genuine ICE failure (not a manual dropDC/dropPC, which recover via
3462
+ // the DataChannel-close path) leaves the PC in `failed` with no recovery.
3463
+ // Trigger peer reconnection. Guarded by `isReconnecting`; the effect
3464
+ // additionally gates on signaling being alive (`canPeerReconnect`), so a
3465
+ // normal teardown (WS already closed) will NOT reconnect.
3466
+ if (state === 'failed' && !this.isReconnecting()) {
3467
+ Logger.warn('[Reconnect] PeerConnection failed, initiating peer reconnection');
3468
+ this.store.dispatch(disconnectStream({
3469
+ reason: DisconnectReason.IceConnectionFailed,
3470
+ message: 'PeerConnection failed',
3471
+ }));
3472
+ }
3260
3473
  });
3261
3474
  fromEvent(pcClient, 'icecandidate')
3262
3475
  .pipe(map$1((e) => e.candidate), filter(Truthy), tap$1((candidate) => Logger.info('[Browser ICE candidate]', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|')), takeUntil(merge(this.disconnect$, this.destroy$)))
3263
- .subscribe((candidate) => this.signallingSrv.send({ type: 'iceCandidate', candidate }));
3476
+ .subscribe((candidate) => this.signallingSrv.sendPlayerSignal({
3477
+ type: 'iceCandidate',
3478
+ candidate,
3479
+ }));
3264
3480
  // When the server creates the DataChannel (server-offer flow),
3265
3481
  // we receive it here via the 'datachannel' event
3266
3482
  fromEvent(pcClient, 'datachannel')
@@ -3407,7 +3623,7 @@ class WebRtcPlayerService extends SubService {
3407
3623
  const offer = await pcClient.createOffer();
3408
3624
  offer.sdp = this.mungeSDP(offer.sdp ?? '');
3409
3625
  await pcClient.setLocalDescription(offer);
3410
- this.signallingSrv.send(offer);
3626
+ this.signallingSrv.sendPlayerSignal(offer);
3411
3627
  }
3412
3628
  catch {
3413
3629
  Logger.error("Couldn't create offer");
@@ -3422,7 +3638,7 @@ class WebRtcPlayerService extends SubService {
3422
3638
  const answer = await pcClient.createAnswer();
3423
3639
  answer.sdp = this.mungeSDP(answer.sdp ?? '');
3424
3640
  await pcClient.setLocalDescription(answer);
3425
- this.signallingSrv.send(answer);
3641
+ this.signallingSrv.sendPlayerSignal(answer);
3426
3642
  // Observability signal for the signaling waterfall — the handshake
3427
3643
  // went "answer sent" between `offer received` and `datachannel_ready`.
3428
3644
  this.store.dispatch(answerSent());
@@ -3431,10 +3647,10 @@ class WebRtcPlayerService extends SubService {
3431
3647
  Logger.error("Couldn't create answer", e);
3432
3648
  }
3433
3649
  }
3434
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3435
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WebRtcPlayerService, providedIn: 'root' }); }
3650
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3651
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WebRtcPlayerService, providedIn: 'root' }); }
3436
3652
  }
3437
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: WebRtcPlayerService, decorators: [{
3653
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: WebRtcPlayerService, decorators: [{
3438
3654
  type: Injectable,
3439
3655
  args: [{ providedIn: 'root' }]
3440
3656
  }], ctorParameters: () => [] });
@@ -3557,10 +3773,10 @@ class StreamAdoptionService {
3557
3773
  this.webRtc.adopt(parked.pc, parked.dc, parked.stream, parked.config ?? {}, parked.dcBuffer);
3558
3774
  return true;
3559
3775
  }
3560
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamAdoptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3561
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamAdoptionService, providedIn: 'root' }); }
3776
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamAdoptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3777
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamAdoptionService, providedIn: 'root' }); }
3562
3778
  }
3563
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamAdoptionService, decorators: [{
3779
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamAdoptionService, decorators: [{
3564
3780
  type: Injectable,
3565
3781
  args: [{ providedIn: 'root' }]
3566
3782
  }] });
@@ -3628,10 +3844,10 @@ class UnrealCallbackService {
3628
3844
  return observable;
3629
3845
  });
3630
3846
  }
3631
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3632
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCallbackService, providedIn: 'root' }); }
3847
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3848
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCallbackService, providedIn: 'root' }); }
3633
3849
  }
3634
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCallbackService, decorators: [{
3850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCallbackService, decorators: [{
3635
3851
  type: Injectable,
3636
3852
  args: [{ providedIn: 'root' }]
3637
3853
  }] });
@@ -3748,10 +3964,10 @@ class UnrealCommunicatorService extends SubService {
3748
3964
  requestQualityControl() {
3749
3965
  this.sendInputData(new Uint8Array([MessageType.RequestQualityControl]).buffer);
3750
3966
  }
3751
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3752
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCommunicatorService, providedIn: 'root' }); }
3967
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
3968
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCommunicatorService, providedIn: 'root' }); }
3753
3969
  }
3754
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealCommunicatorService, decorators: [{
3970
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealCommunicatorService, decorators: [{
3755
3971
  type: Injectable,
3756
3972
  args: [{ providedIn: 'root' }]
3757
3973
  }], ctorParameters: () => [] });
@@ -3765,10 +3981,10 @@ class UnrealErrorModalComponent {
3765
3981
  close() {
3766
3982
  this.#dialogRef.close();
3767
3983
  }
3768
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3769
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0.8", type: UnrealErrorModalComponent, isStandalone: true, selector: "app-unreal-error-modal", ngImport: i0, template: "<div class=\"src-modal src-modal--small\">\r\n <header class=\"src-modal__header\">\r\n <div\r\n [attr.data-testid]=\"'unreal-error-header-title'\"\r\n class=\"src-modal__title\"\r\n >\r\n Warning\r\n </div>\r\n </header>\r\n <div class=\"src-modal__body\">\r\n <div>{{ dialogData.content }}</div>\r\n </div>\r\n <div class=\"src-modal__footer\">\r\n <src-button (onClick)=\"close()\">Ok</src-button>\r\n </div>\r\n</div>\r\n", styles: [".src-modal__header{padding:16px 24px 0}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3984
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
3985
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.0", type: UnrealErrorModalComponent, isStandalone: true, selector: "app-unreal-error-modal", ngImport: i0, template: "<div class=\"src-modal src-modal--small\">\r\n <header class=\"src-modal__header\">\r\n <div\r\n [attr.data-testid]=\"'unreal-error-header-title'\"\r\n class=\"src-modal__title\"\r\n >\r\n Warning\r\n </div>\r\n </header>\r\n <div class=\"src-modal__body\">\r\n <div>{{ dialogData.content }}</div>\r\n </div>\r\n <div class=\"src-modal__footer\">\r\n <src-button (onClick)=\"close()\">Ok</src-button>\r\n </div>\r\n</div>\r\n", styles: [".src-modal__header{padding:16px 24px 0}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
3770
3986
  }
3771
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealErrorModalComponent, decorators: [{
3987
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealErrorModalComponent, decorators: [{
3772
3988
  type: Component,
3773
3989
  args: [{ selector: 'app-unreal-error-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "<div class=\"src-modal src-modal--small\">\r\n <header class=\"src-modal__header\">\r\n <div\r\n [attr.data-testid]=\"'unreal-error-header-title'\"\r\n class=\"src-modal__title\"\r\n >\r\n Warning\r\n </div>\r\n </header>\r\n <div class=\"src-modal__body\">\r\n <div>{{ dialogData.content }}</div>\r\n </div>\r\n <div class=\"src-modal__footer\">\r\n <src-button (onClick)=\"close()\">Ok</src-button>\r\n </div>\r\n</div>\r\n", styles: [".src-modal__header{padding:16px 24px 0}\n"] }]
3774
3990
  }] });
@@ -4002,9 +4218,10 @@ class UnrealEffects {
4002
4218
  * is still connected.
4003
4219
  */
4004
4220
  this.handleDataChannelClosed$ = createEffect(() => {
4005
- return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => reason === DisconnectReason.DataChannelClosed &&
4006
- this.canPeerReconnect()), tap(() => {
4007
- Logger.warn('[Reconnect] DataChannel closed, initiating peer reconnection');
4221
+ return this.actions$.pipe(ofType(disconnectStream), filter(({ reason }) => (reason === DisconnectReason.DataChannelClosed ||
4222
+ reason === DisconnectReason.IceConnectionFailed) &&
4223
+ this.canPeerReconnect()), tap(({ reason }) => {
4224
+ Logger.warn(`[Reconnect] ${reason}, initiating peer reconnection`);
4008
4225
  }), map(() => reconnectPeer({
4009
4226
  attempt: 1,
4010
4227
  maxAttempts: RECONNECT_CONFIG.maxAttempts,
@@ -4143,10 +4360,10 @@ class UnrealEffects {
4143
4360
  // Fails silently if cross-origin
4144
4361
  }
4145
4362
  }
4146
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4147
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealEffects }); }
4363
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4364
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealEffects }); }
4148
4365
  }
4149
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealEffects, decorators: [{
4366
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealEffects, decorators: [{
4150
4367
  type: Injectable
4151
4368
  }] });
4152
4369
 
@@ -4264,10 +4481,10 @@ class AFKService extends SubService {
4264
4481
  }
4265
4482
  }, 1000);
4266
4483
  }
4267
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4268
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AFKService, providedIn: 'root' }); }
4484
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4485
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AFKService, providedIn: 'root' }); }
4269
4486
  }
4270
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AFKService, decorators: [{
4487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AFKService, decorators: [{
4271
4488
  type: Injectable,
4272
4489
  args: [{ providedIn: 'root' }]
4273
4490
  }], ctorParameters: () => [] });
@@ -4317,10 +4534,10 @@ class LoadingProgressService {
4317
4534
  // `switchMap` cancels the inner subscription on every visibility change
4318
4535
  // so stale emissions can't leak through.
4319
4536
  #boundedProgress$;
4320
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4321
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LoadingProgressService, providedIn: 'root' }); }
4537
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: LoadingProgressService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4538
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: LoadingProgressService, providedIn: 'root' }); }
4322
4539
  }
4323
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: LoadingProgressService, decorators: [{
4540
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: LoadingProgressService, decorators: [{
4324
4541
  type: Injectable,
4325
4542
  args: [{ providedIn: 'root' }]
4326
4543
  }] });
@@ -4406,10 +4623,10 @@ class FreezeFrameService extends SubService {
4406
4623
  progress: 1,
4407
4624
  }));
4408
4625
  }
4409
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4410
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FreezeFrameService, providedIn: 'root' }); }
4626
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4627
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FreezeFrameService, providedIn: 'root' }); }
4411
4628
  }
4412
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FreezeFrameService, decorators: [{
4629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FreezeFrameService, decorators: [{
4413
4630
  type: Injectable,
4414
4631
  args: [{ providedIn: 'root' }]
4415
4632
  }], ctorParameters: () => [] });
@@ -4561,10 +4778,10 @@ class FileReceiverService extends SubService {
4561
4778
  this.reset();
4562
4779
  }
4563
4780
  }
4564
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileReceiverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4565
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileReceiverService, providedIn: 'root' }); }
4781
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileReceiverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4782
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileReceiverService, providedIn: 'root' }); }
4566
4783
  }
4567
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileReceiverService, decorators: [{
4784
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileReceiverService, decorators: [{
4568
4785
  type: Injectable,
4569
4786
  args: [{ providedIn: 'root' }]
4570
4787
  }], ctorParameters: () => [] });
@@ -4743,10 +4960,10 @@ class AggregatorService extends SubService {
4743
4960
  Logger.warn(`unrecognized data received, packet ID ${view[0]}`);
4744
4961
  }
4745
4962
  }
4746
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4747
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AggregatorService, providedIn: 'root' }); }
4963
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4964
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AggregatorService, providedIn: 'root' }); }
4748
4965
  }
4749
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AggregatorService, decorators: [{
4966
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AggregatorService, decorators: [{
4750
4967
  type: Injectable,
4751
4968
  args: [{ providedIn: 'root' }]
4752
4969
  }], ctorParameters: () => [] });
@@ -4776,6 +4993,7 @@ class ConsoleExtensionsService extends SubService {
4776
4993
  window.restartApp = undefined;
4777
4994
  window.unrealHelp = undefined;
4778
4995
  window.setWarnTime = undefined;
4996
+ window.stressTest = undefined;
4779
4997
  });
4780
4998
  }
4781
4999
  init() {
@@ -4819,6 +5037,82 @@ class ConsoleExtensionsService extends SubService {
4819
5037
  window.setWarnTime = (time) => {
4820
5038
  this.store.dispatch(setConfig({ config: { warnTimeout: time } }));
4821
5039
  };
5040
+ window.stressTest = (opts) => this.stressTest(opts);
5041
+ }
5042
+ /**
5043
+ * Hammer the reconnect paths: repeatedly drop the DataChannel / PeerConnection
5044
+ * (and rapid combos) and assert the stream recovers each time. Recovery is
5045
+ * measured off the store's `dataChannelConnected` flag (the source of truth,
5046
+ * regardless of transport flow — old relay or new stream socket).
5047
+ *
5048
+ * Console: await stressTest() · await stressTest({ iterations: 100 })
5049
+ */
5050
+ async stressTest(opts) {
5051
+ const cfg = {
5052
+ iterations: 20,
5053
+ recoverTimeoutMs: 20000,
5054
+ minGapMs: 800,
5055
+ maxGapMs: 2500,
5056
+ actions: ['dropDC', 'dropPC', 'rapidDcPc', 'doublePc'],
5057
+ ...opts,
5058
+ };
5059
+ const run = {
5060
+ dropDC: () => this.store.dispatch(dropDC()),
5061
+ dropPC: () => this.store.dispatch(dropPC()),
5062
+ // Rapid DC + PC drop in the same tick.
5063
+ rapidDcPc: () => {
5064
+ this.store.dispatch(dropDC());
5065
+ this.store.dispatch(dropPC());
5066
+ },
5067
+ // Drop the PC, then drop it again mid-reconnect.
5068
+ doublePc: () => {
5069
+ this.store.dispatch(dropPC());
5070
+ setTimeout(() => this.store.dispatch(dropPC()), 300);
5071
+ },
5072
+ };
5073
+ const results = [];
5074
+ Logger.info(`[StressTest] starting — ${cfg.iterations} cycles`);
5075
+ // Must be connected before we start hammering.
5076
+ if (!(await this.waitDataChannel(true, cfg.recoverTimeoutMs))) {
5077
+ Logger.error('[StressTest] aborted — stream is not connected to begin with');
5078
+ return { passed: 0, failed: 0, total: 0, results };
5079
+ }
5080
+ for (let i = 1; i <= cfg.iterations; i++) {
5081
+ const action = cfg.actions[Math.floor(Math.random() * cfg.actions.length)];
5082
+ Logger.warn(`[StressTest] #${i}/${cfg.iterations} → ${action}`);
5083
+ run[action]();
5084
+ // Best-effort: observe the drop (some drops keep video alive briefly).
5085
+ await this.waitDataChannel(false, 6000);
5086
+ const t0 = performance.now();
5087
+ const recovered = await this.waitDataChannel(true, cfg.recoverTimeoutMs);
5088
+ const ms = Math.round(performance.now() - t0);
5089
+ results.push({ i, action, recovered, ms });
5090
+ Logger[recovered ? 'info' : 'error'](`[StressTest] #${i} ${action} → ${recovered
5091
+ ? `RECOVERED in ${ms}ms`
5092
+ : `FAILED (no recovery in ${cfg.recoverTimeoutMs}ms)`}`);
5093
+ await this.sleep(cfg.minGapMs + Math.random() * (cfg.maxGapMs - cfg.minGapMs));
5094
+ }
5095
+ const passed = results.filter((r) => r.recovered).length;
5096
+ const summary = {
5097
+ passed,
5098
+ failed: results.length - passed,
5099
+ total: results.length,
5100
+ results,
5101
+ };
5102
+ Logger.info(`[StressTest] DONE — ${passed}/${results.length} recovered`);
5103
+ // eslint-disable-next-line no-console
5104
+ console.table(results);
5105
+ return summary;
5106
+ }
5107
+ /** Resolve true once `dataChannelConnected` reaches `target`, or false on timeout. */
5108
+ waitDataChannel(target, timeoutMs) {
5109
+ // `store.select` replays the current value synchronously, so if it already
5110
+ // equals `target` this resolves immediately; otherwise it waits for the
5111
+ // transition or falls back to `false` after `timeoutMs`.
5112
+ return firstValueFrom(this.store.select(unrealFeature.selectDataChannelConnected).pipe(filter((connected) => connected === target), take(1), map(() => true), timeout$1({ first: timeoutMs, with: () => of(false) })));
5113
+ }
5114
+ sleep(ms) {
5115
+ return new Promise((resolve) => setTimeout(resolve, ms));
4822
5116
  }
4823
5117
  unrealHelp() {
4824
5118
  Logger.info('dropConnection() => Closes Web Socket and abort communication.');
@@ -4828,11 +5122,12 @@ class ConsoleExtensionsService extends SubService {
4828
5122
  Logger.info('startShowcase(time) => start Showcase.');
4829
5123
  Logger.info('setWarnTime() => set time to appear the AFK window.');
4830
5124
  Logger.info('emitCommand(command) => send command to Unreal Engine.');
5125
+ Logger.info('stressTest({iterations}) => hammer reconnect: repeatedly drop DC/PC and assert recovery.');
4831
5126
  }
4832
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
4833
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConsoleExtensionsService, providedIn: 'root' }); }
5127
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5128
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ConsoleExtensionsService, providedIn: 'root' }); }
4834
5129
  }
4835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ConsoleExtensionsService, decorators: [{
5130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ConsoleExtensionsService, decorators: [{
4836
5131
  type: Injectable,
4837
5132
  args: [{ providedIn: 'root' }]
4838
5133
  }], ctorParameters: () => [] });
@@ -5333,10 +5628,10 @@ class InputService extends SubService {
5333
5628
  this.sendInputData(data.buffer);
5334
5629
  });
5335
5630
  }
5336
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: InputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5337
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: InputService, providedIn: 'root' }); }
5631
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: InputService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5632
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: InputService, providedIn: 'root' }); }
5338
5633
  }
5339
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: InputService, decorators: [{
5634
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: InputService, decorators: [{
5340
5635
  type: Injectable,
5341
5636
  args: [{ providedIn: 'root' }]
5342
5637
  }], ctorParameters: () => [] });
@@ -5405,10 +5700,10 @@ class StreamStatusTelemetryService extends SubService {
5405
5700
  VideoEncoderQP,
5406
5701
  };
5407
5702
  }
5408
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5409
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService, providedIn: 'root' }); }
5703
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5704
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService, providedIn: 'root' }); }
5410
5705
  }
5411
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService, decorators: [{
5706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService, decorators: [{
5412
5707
  type: Injectable,
5413
5708
  args: [{ providedIn: 'root' }]
5414
5709
  }], ctorParameters: () => [] });
@@ -6433,10 +6728,10 @@ class StreamStatusTelemetryService2 {
6433
6728
  data: { userId: this.userId, ...data },
6434
6729
  }));
6435
6730
  }
6436
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6437
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService2, providedIn: 'root' }); }
6731
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6732
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService2, providedIn: 'root' }); }
6438
6733
  }
6439
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StreamStatusTelemetryService2, decorators: [{
6734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StreamStatusTelemetryService2, decorators: [{
6440
6735
  type: Injectable,
6441
6736
  args: [{ providedIn: 'root' }]
6442
6737
  }], ctorParameters: () => [] });
@@ -6522,10 +6817,10 @@ class FileHandlerService {
6522
6817
  return observable;
6523
6818
  });
6524
6819
  }
6525
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6526
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileHandlerService, providedIn: 'root' }); }
6820
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6821
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileHandlerService, providedIn: 'root' }); }
6527
6822
  }
6528
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FileHandlerService, decorators: [{
6823
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FileHandlerService, decorators: [{
6529
6824
  type: Injectable,
6530
6825
  args: [{ providedIn: 'root' }]
6531
6826
  }] });
@@ -6614,10 +6909,10 @@ class AnalyticsService extends SubService {
6614
6909
  Logger.log('Analytics Event:', name, params);
6615
6910
  this.store.dispatch(saveAnalyticsEvent({ event: { name, params } }));
6616
6911
  }
6617
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6618
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
6912
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6913
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
6619
6914
  }
6620
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AnalyticsService, decorators: [{
6915
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AnalyticsService, decorators: [{
6621
6916
  type: Injectable,
6622
6917
  args: [{ providedIn: 'root' }]
6623
6918
  }], ctorParameters: () => [] });
@@ -6984,10 +7279,10 @@ class FpsMonitorService extends SubService {
6984
7279
  }
6985
7280
  return { fps: fps / len, bitrate: bitrate / len, qp: qp / len };
6986
7281
  }
6987
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6988
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FpsMonitorService, providedIn: 'root' }); }
7282
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
7283
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FpsMonitorService, providedIn: 'root' }); }
6989
7284
  }
6990
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FpsMonitorService, decorators: [{
7285
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FpsMonitorService, decorators: [{
6991
7286
  type: Injectable,
6992
7287
  args: [{ providedIn: 'root' }]
6993
7288
  }], ctorParameters: () => [] });
@@ -7007,10 +7302,10 @@ class AfkTimeoutModalComponent {
7007
7302
  resetAfk() {
7008
7303
  this.#afkService.reset();
7009
7304
  }
7010
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7011
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AfkTimeoutModalComponent, isStandalone: true, selector: "app-afk-timeout-modal", ngImport: i0, template: "@if (isShowAfkPopup()) {\r\n <div (click)=\"resetAfk()\" class=\"clickableState\" id=\"videoPlayOverlay\">\r\n <div class=\"resume-box\">\r\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\r\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\r\n </div>\r\n <div class=\"resume-box__text\">\r\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\r\n <p>\r\n No activity detected. Press 'Continue' if you wish to keep your\r\n session active\r\n </p>\r\n </div>\r\n <src-button\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'continue-session'\"\r\n weight=\"primary\"\r\n size=\"lg\"\r\n >\r\n Continue\r\n </src-button>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["#videoPlayOverlay{position:absolute;z-index:30;top:0;width:100%;height:100%;font-size:1.8em;font-family:var(--src-font-family-body);background-color:#646464b3}.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(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .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}.resume-box .resume-box__pic .text-number{color:var(--src-colors-text-default, #1f2937);text-align:center;font-family:var(--src-font-family-body);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(--src-font-family-body);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--src-text-body-secondary, #6b7280)}.resume-box__text .resume-box__heading{color:var(--src-text-body-main, #1f2937);text-align:center;font-family:var(--src-font-family-body);font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7305
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7306
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: AfkTimeoutModalComponent, isStandalone: true, selector: "app-afk-timeout-modal", ngImport: i0, template: "@if (isShowAfkPopup()) {\r\n <div (click)=\"resetAfk()\" class=\"clickableState\" id=\"videoPlayOverlay\">\r\n <div class=\"resume-box\">\r\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\r\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\r\n </div>\r\n <div class=\"resume-box__text\">\r\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\r\n <p>\r\n No activity detected. Press 'Continue' if you wish to keep your\r\n session active\r\n </p>\r\n </div>\r\n <src-button\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'continue-session'\"\r\n weight=\"primary\"\r\n size=\"lg\"\r\n >\r\n Continue\r\n </src-button>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["#videoPlayOverlay{position:absolute;z-index:30;top:0;width:100%;height:100%;font-size:1.8em;font-family:var(--src-font-family-body);background-color:#646464b3}.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(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .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}.resume-box .resume-box__pic .text-number{color:var(--src-colors-text-default, #1f2937);text-align:center;font-family:var(--src-font-family-body);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(--src-font-family-body);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--src-text-body-secondary, #6b7280)}.resume-box__text .resume-box__heading{color:var(--src-text-body-main, #1f2937);text-align:center;font-family:var(--src-font-family-body);font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7012
7307
  }
7013
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AfkTimeoutModalComponent, decorators: [{
7308
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: AfkTimeoutModalComponent, decorators: [{
7014
7309
  type: Component,
7015
7310
  args: [{ selector: 'app-afk-timeout-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "@if (isShowAfkPopup()) {\r\n <div (click)=\"resetAfk()\" class=\"clickableState\" id=\"videoPlayOverlay\">\r\n <div class=\"resume-box\">\r\n <div aria-hidden=\"true\" class=\"resume-box__pic\" role=\"presentation\">\r\n <div [innerHTML]=\"afkCountdown()\" class=\"text-number\"></div>\r\n </div>\r\n <div class=\"resume-box__text\">\r\n <h3 class=\"resume-box__heading\">Session will time out soon</h3>\r\n <p>\r\n No activity detected. Press 'Continue' if you wish to keep your\r\n session active\r\n </p>\r\n </div>\r\n <src-button\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'continue-session'\"\r\n weight=\"primary\"\r\n size=\"lg\"\r\n >\r\n Continue\r\n </src-button>\r\n </div>\r\n </div>\r\n}\r\n", styles: ["#videoPlayOverlay{position:absolute;z-index:30;top:0;width:100%;height:100%;font-size:1.8em;font-family:var(--src-font-family-body);background-color:#646464b3}.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(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .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}.resume-box .resume-box__pic .text-number{color:var(--src-colors-text-default, #1f2937);text-align:center;font-family:var(--src-font-family-body);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(--src-font-family-body);font-size:var(--src-font-size-sm, 14px);font-style:normal;font-weight:400;line-height:24px;color:var(--src-text-body-secondary, #6b7280)}.resume-box__text .resume-box__heading{color:var(--src-text-body-main, #1f2937);text-align:center;font-family:var(--src-font-family-body);font-size:18px;font-style:normal;font-weight:500;line-height:26px;margin-bottom:8px}\n"] }]
7016
7311
  }] });
@@ -7022,10 +7317,10 @@ class FreezeFrameComponent {
7022
7317
  this.combinedFreeze = this.#store.selectSignal(selectFreezeFrameCombinedDataUrl);
7023
7318
  }
7024
7319
  #store;
7025
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7026
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: FreezeFrameComponent, isStandalone: true, selector: "app-freeze-frame", ngImport: i0, template: "@let image = combinedFreeze();\r\n@let progress = freezeFrameProgressMessageFromVideo();\r\n\r\n<div class=\"freeze-images\">\r\n @if (image) {\r\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\r\n }\r\n\r\n @if (progress) {\r\n <div class=\"progress-status\">\r\n {{ progress }}\r\n </div>\r\n }\r\n</div>\r\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 }); }
7320
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7321
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: FreezeFrameComponent, isStandalone: true, selector: "app-freeze-frame", ngImport: i0, template: "@let image = combinedFreeze();\r\n@let progress = freezeFrameProgressMessageFromVideo();\r\n\r\n<div class=\"freeze-images\">\r\n @if (image) {\r\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\r\n }\r\n\r\n @if (progress) {\r\n <div class=\"progress-status\">\r\n {{ progress }}\r\n </div>\r\n }\r\n</div>\r\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 }); }
7027
7322
  }
7028
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: FreezeFrameComponent, decorators: [{
7323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: FreezeFrameComponent, decorators: [{
7029
7324
  type: Component,
7030
7325
  args: [{ selector: 'app-freeze-frame', changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = combinedFreeze();\r\n@let progress = freezeFrameProgressMessageFromVideo();\r\n\r\n<div class=\"freeze-images\">\r\n @if (image) {\r\n <img [src]=\"image\" class=\"videoStillImage\" alt=\"freezeFrameImage\" />\r\n }\r\n\r\n @if (progress) {\r\n <div class=\"progress-status\">\r\n {{ progress }}\r\n </div>\r\n }\r\n</div>\r\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"] }]
7031
7326
  }] });
@@ -7040,10 +7335,10 @@ class ImageLoadingSrcComponent {
7040
7335
  loadingImageError() {
7041
7336
  this.#store.dispatch(setLoadingImageSrc({ src: '' }));
7042
7337
  }
7043
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7044
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\r\n@if (isLoaderScreenVisible()) {\r\n <div class=\"preload\">\r\n <div class=\"back\">\r\n @if (image) {\r\n <img\r\n (error)=\"loadingImageError()\"\r\n [ngSrc]=\"image\"\r\n fill\r\n loading=\"lazy\"\r\n alt=\"image loading src\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n}\r\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:#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 }); }
7338
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7339
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ImageLoadingSrcComponent, isStandalone: true, selector: "app-image-loading-src", ngImport: i0, template: "@let image = imageLoadingSrc();\r\n@if (isLoaderScreenVisible()) {\r\n <div class=\"preload\">\r\n <div class=\"back\">\r\n @if (image) {\r\n <img\r\n (error)=\"loadingImageError()\"\r\n [ngSrc]=\"image\"\r\n fill\r\n loading=\"lazy\"\r\n alt=\"image loading src\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n}\r\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:#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 }); }
7045
7340
  }
7046
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
7341
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
7047
7342
  type: Component,
7048
7343
  args: [{ selector: 'app-image-loading-src', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgOptimizedImage], template: "@let image = imageLoadingSrc();\r\n@if (isLoaderScreenVisible()) {\r\n <div class=\"preload\">\r\n <div class=\"back\">\r\n @if (image) {\r\n <img\r\n (error)=\"loadingImageError()\"\r\n [ngSrc]=\"image\"\r\n fill\r\n loading=\"lazy\"\r\n alt=\"image loading src\"\r\n />\r\n }\r\n </div>\r\n </div>\r\n}\r\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:#fff}\n"] }]
7049
7344
  }] });
@@ -7126,10 +7421,10 @@ class StatGraphComponent {
7126
7421
  }
7127
7422
  this.draw();
7128
7423
  }
7129
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7130
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.8", 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\">\r\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\r\n <canvas #graph class=\"graph\"></canvas>\r\n</div>\r\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7424
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7425
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.0", 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\">\r\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\r\n <canvas #graph class=\"graph\"></canvas>\r\n</div>\r\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7131
7426
  }
7132
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: StatGraphComponent, decorators: [{
7427
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: StatGraphComponent, decorators: [{
7133
7428
  type: Component,
7134
7429
  args: [{ selector: 'app-stat-graph', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"content\">\r\n <p>{{ label() }}: {{ current() }} | Min:{{ min() }} | Max:{{ max() }}</p>\r\n <canvas #graph class=\"graph\"></canvas>\r\n</div>\r\n", styles: [".content{width:100%;height:100%}.content p{color:#fff;margin:5px 0 0;height:25px}\n"] }]
7135
7430
  }], 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: [{
@@ -7150,10 +7445,10 @@ class UnrealStatusComponent {
7150
7445
  }
7151
7446
  #store;
7152
7447
  #loadingProgress;
7153
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7154
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", ngImport: i0, template: "@if (isShowLoadingPercentsPopup()) {\r\n @let _awsInstance = awsInstance();\r\n <div class=\"status-box\">\r\n @if (_awsInstance?.message) {\r\n <p>{{ _awsInstance?.message }}</p>\r\n } @else {\r\n <div class=\"status-box__message\">\r\n <src-loading\r\n [size]=\"30\"\r\n [backgroundStrokeColor]=\"'#E5E7EB'\"\r\n [progressStrokeColor]=\"'#1F2937'\"\r\n />\r\n {{ smoothPercents() }}%\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: ["app-stat-graph{height:30px;width:100%}.status-box{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--src-surface-container-main, #fff);border:none;border-radius:var(--src-border-rounded-full, 9999px);box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814;pointer-events:none}.status-box p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color: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 src-loading{margin-right:12px}\n"], dependencies: [{ kind: "component", type: SourceLoadingComponent, selector: "src-loading", inputs: ["size", "progress", "lineCap", "backgroundStrokeColor", "progressStrokeColor", "strokeWidth", "data-testid", "role", "ariaLabel", "ariaValueNow", "ariaValueMin", "ariaValueMax"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7448
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7449
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", ngImport: i0, template: "@if (isShowLoadingPercentsPopup()) {\r\n @let _awsInstance = awsInstance();\r\n <div class=\"status-box\">\r\n @if (_awsInstance?.message) {\r\n <p>{{ _awsInstance?.message }}</p>\r\n } @else {\r\n <div class=\"status-box__message\">\r\n <src-loading\r\n [size]=\"30\"\r\n [backgroundStrokeColor]=\"'#E5E7EB'\"\r\n [progressStrokeColor]=\"'#1F2937'\"\r\n />\r\n {{ smoothPercents() }}%\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: ["app-stat-graph{height:30px;width:100%}.status-box{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--src-surface-container-main, #fff);border:none;border-radius:var(--src-border-rounded-full, 9999px);box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814;pointer-events:none}.status-box p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color: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 src-loading{margin-right:12px}\n"], dependencies: [{ kind: "component", type: SourceLoadingComponent, selector: "src-loading", inputs: ["size", "progress", "lineCap", "backgroundStrokeColor", "progressStrokeColor", "strokeWidth", "data-testid", "role", "ariaLabel", "ariaValueNow", "ariaValueMin", "ariaValueMax"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7155
7450
  }
7156
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealStatusComponent, decorators: [{
7451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealStatusComponent, decorators: [{
7157
7452
  type: Component,
7158
7453
  args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceLoadingComponent], template: "@if (isShowLoadingPercentsPopup()) {\r\n @let _awsInstance = awsInstance();\r\n <div class=\"status-box\">\r\n @if (_awsInstance?.message) {\r\n <p>{{ _awsInstance?.message }}</p>\r\n } @else {\r\n <div class=\"status-box__message\">\r\n <src-loading\r\n [size]=\"30\"\r\n [backgroundStrokeColor]=\"'#E5E7EB'\"\r\n [progressStrokeColor]=\"'#1F2937'\"\r\n />\r\n {{ smoothPercents() }}%\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: ["app-stat-graph{height:30px;width:100%}.status-box{position:absolute;top:80%;left:50%;transform:translate(-50%,-50%);z-index:5;display:flex;justify-content:center;align-items:center;width:auto;max-width:500px;margin:0;background:var(--src-surface-container-main, #fff);border:none;border-radius:var(--src-border-rounded-full, 9999px);box-shadow:0 8px 20px #1718181f,0 3px 6px #17181814;pointer-events:none}.status-box p{padding:10px 15px;margin:0;text-align:center}.status-box__message{display:flex;align-items:center;padding:12px 20px 12px 16px;color: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 src-loading{margin-right:12px}\n"] }]
7159
7454
  }] });
@@ -7186,10 +7481,10 @@ class SafePipe {
7186
7481
  // content instead of disabling framework security checks.
7187
7482
  return (this.sanitizer.sanitize(context, value) ?? '');
7188
7483
  }
7189
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7190
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" }); }
7484
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
7485
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.1.0", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" }); }
7191
7486
  }
7192
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: SafePipe, decorators: [{
7487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: SafePipe, decorators: [{
7193
7488
  type: Pipe,
7194
7489
  args: [{
7195
7490
  name: 'safe',
@@ -7302,10 +7597,10 @@ class VideoStatsComponent {
7302
7597
  elements: this.elementsToShow(),
7303
7598
  }));
7304
7599
  }
7305
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7306
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"fpsTick()!\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"videoQP()!\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"bitrateTick()!\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n </div>\r\n <div>\r\n @for (graph of graphList(); track graph.key) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [stat]=\"graph.stat()\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed()) {\r\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n}\r\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 }); }
7600
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7601
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: VideoStatsComponent, isStandalone: true, selector: "app-video-stats", ngImport: i0, template: "@if (videoStatus() && viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"fpsTick()!\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"videoQP()!\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"bitrateTick()!\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n </div>\r\n <div>\r\n @for (graph of graphList(); track graph.key) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [stat]=\"graph.stat()\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed()) {\r\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n}\r\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 }); }
7307
7602
  }
7308
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: VideoStatsComponent, decorators: [{
7603
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: VideoStatsComponent, decorators: [{
7309
7604
  type: Component,
7310
7605
  args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, JsonPipe, SafePipe], template: "@if (videoStatus() && viewportReady()) {\r\n <div class=\"settings-container\">\r\n <button (click)=\"toggle()\" class=\"gear-button\">Stats</button>\r\n <div (click)=\"toggle()\" [class.min]=\"collapsed()\" id=\"aggregatedStats\">\r\n <div class=\"forNerds\">\r\n <div (click)=\"$event.stopPropagation()\" class=\"static\">\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"fpsTick()!\"\r\n label=\"FPS (higher is better)\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"videoQP()!\"\r\n label=\"QP (lower is better)\"\r\n color=\"#D5ff07\"\r\n />\r\n </li>\r\n <li>\r\n <app-stat-graph\r\n [stat]=\"bitrateTick()!\"\r\n label=\"Bitrate\"\r\n color=\"#D57F07\"\r\n />\r\n </li>\r\n </div>\r\n <div>\r\n @for (graph of graphList(); track graph.key) {\r\n <li (click)=\"toggleGraph($event, graph.key, 0)\" class=\"graph\">\r\n <app-stat-graph\r\n [label]=\"graph.key\"\r\n [stat]=\"graph.stat()\"\r\n [color]=\"graph.color\"\r\n />\r\n </li>\r\n }\r\n </div>\r\n </div>\r\n\r\n @for (el of videoStatus(); track el.key + $index) {\r\n <div (click)=\"toggleGraph($event, el.key, el.value)\" class=\"stat\">\r\n <span>{{ el.key }}: </span>{{ el.value | json }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n @if (!collapsed()) {\r\n <div [innerHTML]=\"ssData() | safe: 'html'\" class=\"ssInfo\"></div>\r\n }\r\n}\r\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"] }]
7311
7606
  }], ctorParameters: () => [] });
@@ -7349,10 +7644,10 @@ class ResumeStreamButtonComponent {
7349
7644
  disconnect() {
7350
7645
  this.isSecondStart.set(true);
7351
7646
  }
7352
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7353
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: ResumeStreamButtonComponent, isStandalone: true, selector: "app-resume-stream-button", ngImport: i0, template: "@if (isShowResumeButton()) {\r\n <div class=\"resume-box\">\r\n @if (isSecondStart()) {\r\n <div class=\"resume-box__text\">\r\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\r\n inactivity\r\n </div>\r\n }\r\n\r\n <src-button\r\n (onClick)=\"connect()\"\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'connect-button'\"\r\n size=\"lg\"\r\n weight=\"primary\"\r\n class=\"connect-button\"\r\n >\r\n {{ isSecondStart() ? 'Resume' : 'Start' }}\r\n </src-button>\r\n </div>\r\n}\r\n", styles: [".resume-box{padding:16px;position:absolute;bottom:0;left:50%;gap:12px;z-index:6;display:flex;transform:translate(-50%,-50%);flex-direction:column;align-items:center;border-radius:var(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .connect-button{width:100%}.resume-box .resume-box__text{color:var(--src-color-grey-500, #6b7280);text-align:center;font-family:var(--src-font-family-body);font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7647
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7648
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", type: ResumeStreamButtonComponent, isStandalone: true, selector: "app-resume-stream-button", ngImport: i0, template: "@if (isShowResumeButton()) {\r\n <div class=\"resume-box\">\r\n @if (isSecondStart()) {\r\n <div class=\"resume-box__text\">\r\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\r\n inactivity\r\n </div>\r\n }\r\n\r\n <src-button\r\n (onClick)=\"connect()\"\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'connect-button'\"\r\n size=\"lg\"\r\n weight=\"primary\"\r\n class=\"connect-button\"\r\n >\r\n {{ isSecondStart() ? 'Resume' : 'Start' }}\r\n </src-button>\r\n </div>\r\n}\r\n", styles: [".resume-box{padding:16px;position:absolute;bottom:0;left:50%;gap:12px;z-index:6;display:flex;transform:translate(-50%,-50%);flex-direction:column;align-items:center;border-radius:var(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .connect-button{width:100%}.resume-box .resume-box__text{color:var(--src-color-grey-500, #6b7280);text-align:center;font-family:var(--src-font-family-body);font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"], dependencies: [{ kind: "component", type: SourceButtonComponent, selector: "src-button", inputs: ["type", "appearance", "weight", "size", "context", "customClass", "isFullWidth", "isPressed", "isDisabled", "isLoading", "isInverted", "srcButtonConfig", "formID", "data-testid", "ariaLabel"], outputs: ["onClick", "onSubmit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7354
7649
  }
7355
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
7650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
7356
7651
  type: Component,
7357
7652
  args: [{ selector: 'app-resume-stream-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "@if (isShowResumeButton()) {\r\n <div class=\"resume-box\">\r\n @if (isSecondStart()) {\r\n <div class=\"resume-box__text\">\r\n Your stream {{ isAfkDisconnect() ? 'has been' : 'is' }} paused due to\r\n inactivity\r\n </div>\r\n }\r\n\r\n <src-button\r\n (onClick)=\"connect()\"\r\n [isFullWidth]=\"true\"\r\n [data-testid]=\"'connect-button'\"\r\n size=\"lg\"\r\n weight=\"primary\"\r\n class=\"connect-button\"\r\n >\r\n {{ isSecondStart() ? 'Resume' : 'Start' }}\r\n </src-button>\r\n </div>\r\n}\r\n", styles: [".resume-box{padding:16px;position:absolute;bottom:0;left:50%;gap:12px;z-index:6;display:flex;transform:translate(-50%,-50%);flex-direction:column;align-items:center;border-radius:var(--src-border-rounded-parent, 8px);background:var(--src-surface-container-main, #fff);box-shadow:0 26px 80px #0003,0 0 1px #0003}.resume-box .connect-button{width:100%}.resume-box .resume-box__text{color:var(--src-color-grey-500, #6b7280);text-align:center;font-family:var(--src-font-family-body);font-size:14px;font-style:normal;font-weight:400;line-height:24px}\n"] }]
7358
7653
  }], ctorParameters: () => [] });
@@ -7419,10 +7714,10 @@ class IntroSrcComponent {
7419
7714
  sourceElement.addEventListener('error', () => this.#store.dispatch(resetIntroSrc()));
7420
7715
  videoContainer.appendChild(videoElement);
7421
7716
  }
7422
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7423
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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();\r\n@if (image) {\r\n <div class=\"backImage\">\r\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\r\n </div>\r\n} @else if (videoIntroSrc()) {\r\n <div #videoPlayerCover class=\"backVideo\"></div>\r\n}\r\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 }); }
7717
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7718
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", 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();\r\n@if (image) {\r\n <div class=\"backImage\">\r\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\r\n </div>\r\n} @else if (videoIntroSrc()) {\r\n <div #videoPlayerCover class=\"backVideo\"></div>\r\n}\r\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 }); }
7424
7719
  }
7425
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: IntroSrcComponent, decorators: [{
7720
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: IntroSrcComponent, decorators: [{
7426
7721
  type: Component,
7427
7722
  args: [{ selector: 'app-intro-src', imports: [NgOptimizedImage], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let image = imageIntroSrc();\r\n@if (image) {\r\n <div class=\"backImage\">\r\n <img (error)=\"loadingImageError()\" [ngSrc]=\"image\" fill alt=\"imageSrc\" />\r\n </div>\r\n} @else if (videoIntroSrc()) {\r\n <div #videoPlayerCover class=\"backVideo\"></div>\r\n}\r\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"] }]
7428
7723
  }], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
@@ -7493,6 +7788,12 @@ class UnrealSceneComponent {
7493
7788
  // keeps the next 3D route's connect unblocked (connectToSignaling$ gates
7494
7789
  // on `!cirrusConnected`).
7495
7790
  this.#store.dispatch(destroyUnrealScene());
7791
+ // Drop the container reference last: `VideoService` is a root singleton
7792
+ // that outlives this scene, so keeping the old `#player` wired would hold
7793
+ // a detached subtree reachable and let a late `create()` attach the
7794
+ // <video> to DOM nobody can see. The dispatch above already ran the
7795
+ // teardown that removes the element itself.
7796
+ this.videoService.setContainer(null);
7496
7797
  });
7497
7798
  effect(() => {
7498
7799
  const videoElement = this.videoElement();
@@ -7637,10 +7938,10 @@ class UnrealSceneComponent {
7637
7938
  },
7638
7939
  });
7639
7940
  }
7640
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7641
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", 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 } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()", "contextmenu": "onContextMenu($event)" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\r\n <div #videoContainer id=\"player\"></div>\r\n\r\n <app-intro-src />\r\n <app-image-loading-src />\r\n <app-resume-stream-button />\r\n <app-unreal-status />\r\n <app-freeze-frame />\r\n <app-afk-timeout-modal />\r\n\r\n <ng-content select=\"app-pdf\"></ng-content>\r\n <ng-content select=\"app-show-case\"></ng-content>\r\n\r\n @if (isDevMode) {\r\n <app-video-stats />\r\n }\r\n</div>\r\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>%0D%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)}#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)}#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" }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status" }, { 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" }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7941
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
7942
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.0", 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 } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()", "contextmenu": "onContextMenu($event)" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\r\n <div #videoContainer id=\"player\"></div>\r\n\r\n <app-intro-src />\r\n <app-image-loading-src />\r\n <app-resume-stream-button />\r\n <app-unreal-status />\r\n <app-freeze-frame />\r\n <app-afk-timeout-modal />\r\n\r\n <ng-content select=\"app-pdf\"></ng-content>\r\n <ng-content select=\"app-show-case\"></ng-content>\r\n\r\n @if (isDevMode) {\r\n <app-video-stats />\r\n }\r\n</div>\r\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>%0D%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)}#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)}#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" }, { kind: "component", type: UnrealStatusComponent, selector: "app-unreal-status" }, { 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" }, { kind: "component", type: ImageLoadingSrcComponent, selector: "app-image-loading-src" }, { kind: "component", type: IntroSrcComponent, selector: "app-intro-src" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
7642
7943
  }
7643
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: UnrealSceneComponent, decorators: [{
7944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.0", ngImport: i0, type: UnrealSceneComponent, decorators: [{
7644
7945
  type: Component,
7645
7946
  args: [{ selector: 'app-unreal-scene', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
7646
7947
  AfkTimeoutModalComponent,