@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
|
-
*
|
|
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
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
-
|
|
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
|
|
1246
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0
|
|
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
|
|
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
|
|
1456
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
1480
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
1786
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
-
|
|
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
|
|
2068
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
2314
|
-
|
|
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
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2434
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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>
|
|
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
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
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
|
|
2550
|
-
|
|
2551
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
2776
|
-
|
|
2777
|
-
|
|
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
|
|
2801
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
3435
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
3561
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
3632
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
3752
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
3769
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.0
|
|
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
|
|
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
|
-
|
|
4007
|
-
|
|
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
|
|
4147
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4268
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4321
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4410
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4565
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4747
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
4833
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
5337
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
5409
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
6437
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
6526
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
6618
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
6988
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0
|
|
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
|
|
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
|
|
7011
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7026
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7044
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7130
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0
|
|
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
|
|
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
|
|
7154
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7190
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0
|
|
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
|
|
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
|
|
7306
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7353
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
7423
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0
|
|
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
|
|
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
|
|
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
|
|
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,
|