@3dsource/angular-unreal-module 0.0.108 → 0.0.110-dev.0
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,16 +1,17 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { InjectionToken, inject, ChangeDetectionStrategy, Component, Injectable, DestroyRef, signal, Pipe, input, ElementRef, viewChild, effect, untracked, afterNextRender, HostListener, computed, output, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';
|
|
3
3
|
import { SourceButtonComponent, SourceLoadingComponent } from '@3dsource/source-ui-native';
|
|
4
|
-
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, withLatestFrom, exhaustMap, catchError, takeUntil, switchMap as switchMap$1, timeout as timeout$1, debounceTime as debounceTime$1, delay, skip as skip$1 } from 'rxjs/operators';
|
|
4
|
+
import { map as map$1, distinctUntilChanged, takeWhile, filter, tap, withLatestFrom, exhaustMap, catchError, takeUntil, switchMap as switchMap$1, timeout as timeout$1, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1, delay, skip as skip$1 } from 'rxjs/operators';
|
|
5
5
|
import { Logger, calculateMedian, clampf, lerp, getCanvasCached, getSnapshot, fitIntoRectangle, whereNot, Truthy, Falsy, tapLog, generateUuid, COLOR_CODES, where, KeyboardNumericCode, InvertedKeyMap, Semaphore, HEXtoRGB, RGBtoHSV, inverseLerp, HSVtoRGB, RGBtoHEX, fpIsASameAsB } from '@3dsource/utils';
|
|
6
|
-
import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, debounceTime, switchMap, take, fromEvent, EMPTY, timer, timeout, mergeMap, Subject, skip, share, from, throwError, defer, retry, tap as tap$1, startWith, takeUntil as takeUntil$1,
|
|
6
|
+
import { merge, map, scan, concatMap, of, animationFrameScheduler, interval, Observable, debounceTime, switchMap, take, fromEvent, EMPTY, timer, timeout, mergeMap, Subject, skip, share, from, throwError, defer, retry, tap as tap$1, startWith, takeUntil as takeUntil$1, throttleTime, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, pairwise, Subscription, bufferTime, BehaviorSubject, combineLatestWith, distinctUntilChanged as distinctUntilChanged$1, concat } from 'rxjs';
|
|
7
7
|
import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
|
|
8
8
|
import { Actions, createEffect, ofType, provideEffects } from '@ngrx/effects';
|
|
9
9
|
import { concatLatestFrom } from '@ngrx/operators';
|
|
10
10
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
11
11
|
import { HttpClient } from '@angular/common/http';
|
|
12
12
|
import { ScrollStrategyOptions } from '@angular/cdk/overlay';
|
|
13
|
-
import { MetaBoxCommand } from '@3dsource/types-unreal';
|
|
13
|
+
import { MetaBoxCommand, EPayloadVersion } from '@3dsource/types-unreal';
|
|
14
|
+
import { v4 } from 'uuid';
|
|
14
15
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
15
16
|
import { NgOptimizedImage, AsyncPipe, JsonPipe } from '@angular/common';
|
|
16
17
|
import { toSignal, takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
@@ -150,6 +151,7 @@ const commandCompleted = createAction(scoped$1 `command completed`, props());
|
|
|
150
151
|
|
|
151
152
|
const scoped = (templateString) => `[UNREAL] ${templateString[0]}`;
|
|
152
153
|
const trackMixpanelEvent = createAction(scoped `track mixpanel event`, props());
|
|
154
|
+
const trackTelemetryData = createAction(scoped `track telemetry data`, props());
|
|
153
155
|
const changeLowBandwidth = createAction(scoped `change low bandwidth`, props());
|
|
154
156
|
const setMaxFps = createAction(scoped `change fps`, props());
|
|
155
157
|
const destroyRemoteConnections = createAction(scoped `destroyRemoteConnections`, props());
|
|
@@ -184,6 +186,47 @@ const changeStreamResolutionAction = createAction(scoped `change stream resoluti
|
|
|
184
186
|
const changeStreamResolutionSuccessAction = createAction(scoped `change stream resolution success action`, props());
|
|
185
187
|
const setSignalingName = createAction(scoped `set aws instanceName`, props());
|
|
186
188
|
const setOrchestrationParameters = createAction(scoped `set orchestration parameters`, props());
|
|
189
|
+
/**
|
|
190
|
+
* Dispatched when the orchestration server signals that the reserved
|
|
191
|
+
* instance is ready to serve the stream (`instanceReady` WS message).
|
|
192
|
+
* Emitted for observability only — no reducer state change.
|
|
193
|
+
*/
|
|
194
|
+
const instanceReady = createAction(scoped `instance ready`);
|
|
195
|
+
/**
|
|
196
|
+
* Dispatched when `RegionsPingService.getFastest()` begins — right
|
|
197
|
+
* before the region-list HTTP request goes out. Bookends the entire
|
|
198
|
+
* region-discovery flow together with {@link regionResolved}.
|
|
199
|
+
* Observability-only — no state change.
|
|
200
|
+
*/
|
|
201
|
+
const regionPingFetchStart = createAction(scoped `region ping fetch start`);
|
|
202
|
+
/**
|
|
203
|
+
* Dispatched once `RegionsPingService.getProviders()` returns the region
|
|
204
|
+
* list — i.e. the fetch step is done and the actual per-region ping
|
|
205
|
+
* sweep is about to start. Splits the region-discovery waterfall into
|
|
206
|
+
* two legs: config fetch vs ping sweep. Observability-only.
|
|
207
|
+
*/
|
|
208
|
+
const regionPingFetchEnd = createAction(scoped `region ping fetch end`);
|
|
209
|
+
/**
|
|
210
|
+
* Dispatched once the closest region has been resolved by the
|
|
211
|
+
* regions-ping service. Carries every sampled ping RTT to the winning
|
|
212
|
+
* region (one entry per probe — `checkLatency` issues `ping_count`
|
|
213
|
+
* sequential pings per region) so consumers can inspect variance /
|
|
214
|
+
* jitter, not just the mean. Observability-only — no state change.
|
|
215
|
+
*/
|
|
216
|
+
const regionResolved = createAction(scoped `region resolved`, props());
|
|
217
|
+
/**
|
|
218
|
+
* Dispatched around the HTTP polling roundtrip inside `getAwsInstance`
|
|
219
|
+
* (old orchestration flow). Only fires when polling is actually used
|
|
220
|
+
* (i.e. signaling URL is not a direct `ws://` / `wss://` endpoint).
|
|
221
|
+
* Observability-only — no state change.
|
|
222
|
+
*/
|
|
223
|
+
const pollingStarted = createAction(scoped `polling started`);
|
|
224
|
+
const pollingEnded = createAction(scoped `polling ended`);
|
|
225
|
+
/**
|
|
226
|
+
* Dispatched when the client has just sent an SDP answer back to Cirrus
|
|
227
|
+
* after receiving the server-initiated offer. Observability-only.
|
|
228
|
+
*/
|
|
229
|
+
const answerSent = createAction(scoped `answer sent`);
|
|
187
230
|
const setOrchestrationMessage = createAction(scoped `set orchestration message`, props());
|
|
188
231
|
const setOrchestrationProgress = createAction(scoped `set orchestration progress`, props());
|
|
189
232
|
const setOrchestrationContext = createAction(scoped `set orchestration params`, props());
|
|
@@ -541,7 +584,9 @@ function IceCandidateHandler(msg) {
|
|
|
541
584
|
}
|
|
542
585
|
|
|
543
586
|
function InstanceReadyHandler() {
|
|
544
|
-
//
|
|
587
|
+
// No state change — emit the action purely so services that want to
|
|
588
|
+
// observe orchestration progress (e.g. telemetry) have a hook.
|
|
589
|
+
this.store.dispatch(instanceReady());
|
|
545
590
|
}
|
|
546
591
|
|
|
547
592
|
function InstanceReservedHandler(msg) {
|
|
@@ -1169,6 +1214,7 @@ const selectTotalProgress = createSelector(unrealFeature.selectStatusPercentSign
|
|
|
1169
1214
|
});
|
|
1170
1215
|
const selectClientAndViewIds = createSelector(unrealFeature.selectStreamClientCompanyId, unrealFeature.selectStreamViewId, (clientId, viewId) => ({ clientId, viewId }));
|
|
1171
1216
|
const selectIsLoaderScreenVisible = createSelector(unrealFeature.selectViewportReady, unrealFeature.selectDataChannelConnected, unrealFeature.selectCirrusConnected, (viewReady, cirrusConnected, dataChannelConnected) => !viewReady || !cirrusConnected || !dataChannelConnected);
|
|
1217
|
+
const selectCirrusConnectionId = createSelector(unrealFeature.selectSsData, (data) => data?.connectionId);
|
|
1172
1218
|
|
|
1173
1219
|
class UnrealErrorModalComponent {
|
|
1174
1220
|
constructor() {
|
|
@@ -1179,10 +1225,10 @@ class UnrealErrorModalComponent {
|
|
|
1179
1225
|
close() {
|
|
1180
1226
|
this.#dialogRef.close();
|
|
1181
1227
|
}
|
|
1182
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1183
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.
|
|
1228
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1229
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", 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 }); }
|
|
1184
1230
|
}
|
|
1185
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1231
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealErrorModalComponent, decorators: [{
|
|
1186
1232
|
type: Component,
|
|
1187
1233
|
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"] }]
|
|
1188
1234
|
}] });
|
|
@@ -1568,10 +1614,10 @@ class UnrealEffects {
|
|
|
1568
1614
|
}));
|
|
1569
1615
|
}, { dispatch: false });
|
|
1570
1616
|
}
|
|
1571
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1572
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1617
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1618
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealEffects }); }
|
|
1573
1619
|
}
|
|
1574
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1620
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealEffects, decorators: [{
|
|
1575
1621
|
type: Injectable
|
|
1576
1622
|
}] });
|
|
1577
1623
|
|
|
@@ -1585,10 +1631,10 @@ class CommandsEffects {
|
|
|
1585
1631
|
: json?.correlationId), filter(Truthy), map((id) => commandCompleted({ id })))));
|
|
1586
1632
|
});
|
|
1587
1633
|
}
|
|
1588
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1589
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1634
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandsEffects, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1635
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandsEffects }); }
|
|
1590
1636
|
}
|
|
1591
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1637
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandsEffects, decorators: [{
|
|
1592
1638
|
type: Injectable
|
|
1593
1639
|
}] });
|
|
1594
1640
|
|
|
@@ -1704,10 +1750,10 @@ class AFKService extends SubService {
|
|
|
1704
1750
|
}
|
|
1705
1751
|
}, 1000);
|
|
1706
1752
|
}
|
|
1707
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1708
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1753
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AFKService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1754
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AFKService }); }
|
|
1709
1755
|
}
|
|
1710
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1756
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AFKService, decorators: [{
|
|
1711
1757
|
type: Injectable
|
|
1712
1758
|
}], ctorParameters: () => [] });
|
|
1713
1759
|
|
|
@@ -1795,10 +1841,10 @@ class FreezeFrameService extends SubService {
|
|
|
1795
1841
|
progress: 1,
|
|
1796
1842
|
}));
|
|
1797
1843
|
}
|
|
1798
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
1799
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
1844
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FreezeFrameService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
1845
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FreezeFrameService }); }
|
|
1800
1846
|
}
|
|
1801
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
1847
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FreezeFrameService, decorators: [{
|
|
1802
1848
|
type: Injectable
|
|
1803
1849
|
}], ctorParameters: () => [] });
|
|
1804
1850
|
|
|
@@ -2074,10 +2120,10 @@ class VideoService extends SubService {
|
|
|
2074
2120
|
error: () => ({}),
|
|
2075
2121
|
});
|
|
2076
2122
|
}
|
|
2077
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2078
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
2123
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VideoService, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2124
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VideoService }); }
|
|
2079
2125
|
}
|
|
2080
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2126
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VideoService, decorators: [{
|
|
2081
2127
|
type: Injectable
|
|
2082
2128
|
}] });
|
|
2083
2129
|
|
|
@@ -2283,10 +2329,10 @@ class CommandTelemetryService {
|
|
|
2283
2329
|
this.trackTime(out);
|
|
2284
2330
|
funcToDecorate(out);
|
|
2285
2331
|
}
|
|
2286
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2287
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
2332
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2333
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandTelemetryService }); }
|
|
2288
2334
|
}
|
|
2289
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2335
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: CommandTelemetryService, decorators: [{
|
|
2290
2336
|
type: Injectable
|
|
2291
2337
|
}], ctorParameters: () => [] });
|
|
2292
2338
|
|
|
@@ -2567,7 +2613,9 @@ class SignallingService extends SubService {
|
|
|
2567
2613
|
this.correlationId = generateUuid();
|
|
2568
2614
|
this.commandTelemetryService.reset();
|
|
2569
2615
|
this.startEstablishingConnection();
|
|
2570
|
-
return defer(() =>
|
|
2616
|
+
return defer(() => {
|
|
2617
|
+
return this.getRegion().pipe(tap((region) => this.region.set(region ?? '')), map$1((region) => this.adaptUrlsToRegion(urlList, region)), switchMap((urls) => this.getAwsInstance(urls).pipe(take(1))), tap((awsInstance) => this.store.dispatch(setAwsInstance(awsInstance))), switchMap(({ wsUrl }) => this.connectToCirrus(wsUrl ?? '').pipe(filter(Truthy), take(1))), takeUntil(this.abort$));
|
|
2618
|
+
}).pipe(
|
|
2571
2619
|
// Retry the whole attempt on error, advancing to the next URL each time.
|
|
2572
2620
|
retry({
|
|
2573
2621
|
delay: (err, attempt) => {
|
|
@@ -2701,6 +2749,9 @@ class SignallingService extends SubService {
|
|
|
2701
2749
|
};
|
|
2702
2750
|
}));
|
|
2703
2751
|
}
|
|
2752
|
+
// Old (HTTP-polling) orchestration flow — mark the roundtrip so
|
|
2753
|
+
// the signaling-phase timeline can show it as a distinct segment.
|
|
2754
|
+
this.store.dispatch(pollingStarted());
|
|
2704
2755
|
return this.store.select(selectClientAndViewIds).pipe(
|
|
2705
2756
|
// Log missing IDs without altering the stream.
|
|
2706
2757
|
tap(({ clientId, viewId }) => {
|
|
@@ -2733,6 +2784,7 @@ class SignallingService extends SubService {
|
|
|
2733
2784
|
}),
|
|
2734
2785
|
// Map a valid orchestration payload into the final AwsInstance.
|
|
2735
2786
|
map$1((data) => {
|
|
2787
|
+
this.store.dispatch(pollingEnded());
|
|
2736
2788
|
const wsUrl = httpUrlToWs(`${location.protocol}//${data.signallingServer}`);
|
|
2737
2789
|
return {
|
|
2738
2790
|
wsUrl,
|
|
@@ -2860,10 +2912,10 @@ class SignallingService extends SubService {
|
|
|
2860
2912
|
Logger.log('Start Getting Region');
|
|
2861
2913
|
return this.regionsPingService.getFastest().pipe(take(1));
|
|
2862
2914
|
}
|
|
2863
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
2864
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
2915
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SignallingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
2916
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SignallingService }); }
|
|
2865
2917
|
}
|
|
2866
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
2918
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SignallingService, decorators: [{
|
|
2867
2919
|
type: Injectable
|
|
2868
2920
|
}], ctorParameters: () => [] });
|
|
2869
2921
|
|
|
@@ -2878,103 +2930,243 @@ class WebRtcPlayerService extends SubService {
|
|
|
2878
2930
|
this.useMic = false;
|
|
2879
2931
|
this.forceTURN = false;
|
|
2880
2932
|
this.forceMonoAudio = false;
|
|
2933
|
+
/** Whether the remote SDP has been applied to the current peer connection */
|
|
2934
|
+
this.remoteDescriptionSet = false;
|
|
2935
|
+
/** ICE candidates received before the remote description was set */
|
|
2936
|
+
this.iceCandidateBuffer = [];
|
|
2881
2937
|
this.isReconnecting = this.store.selectSignal(unrealFeature.selectIsReconnecting);
|
|
2882
2938
|
this.init();
|
|
2883
2939
|
this.actions$.pipe(ofType(dropDC)).subscribe(() => this.dcClient?.close());
|
|
2884
2940
|
this.actions$.pipe(ofType(dropPC)).subscribe(() => this.pcClient?.close());
|
|
2885
2941
|
}
|
|
2886
2942
|
init() {
|
|
2887
|
-
const iceCandidateBuffer = [];
|
|
2888
|
-
const processBuffer = () => {
|
|
2889
|
-
iceCandidateBuffer.forEach((cnd) => {
|
|
2890
|
-
this.handleCandidateFromServer(cnd);
|
|
2891
|
-
});
|
|
2892
|
-
iceCandidateBuffer.length = 0;
|
|
2893
|
-
};
|
|
2894
2943
|
this.signallingSrv.onConfig$.subscribe((config) => {
|
|
2895
2944
|
this.videoService.create();
|
|
2896
2945
|
this.setConfig(config);
|
|
2897
2946
|
});
|
|
2947
|
+
// Server-initiated offer: set remote description and respond with answer
|
|
2948
|
+
this.signallingSrv.onOffer$.pipe(filter(Truthy)).subscribe((offer) => {
|
|
2949
|
+
void this.handleServerOffer(offer);
|
|
2950
|
+
});
|
|
2951
|
+
// Answer to a client-initiated offer
|
|
2898
2952
|
this.signallingSrv.onWebRtcAnswer$
|
|
2899
2953
|
.pipe(filter(Truthy))
|
|
2900
2954
|
.subscribe((answer) => {
|
|
2901
|
-
this.
|
|
2902
|
-
processBuffer();
|
|
2903
|
-
});
|
|
2904
|
-
this.signallingSrv.onOffer$.pipe(filter(Truthy)).subscribe(() => {
|
|
2905
|
-
void this.createWebRtcOffer();
|
|
2955
|
+
void this.handleServerAnswer(answer);
|
|
2906
2956
|
});
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
.
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
if (
|
|
2913
|
-
|
|
2957
|
+
// ICE candidates — buffered until remote description is set
|
|
2958
|
+
this.signallingSrv.onWebRtcIce$.subscribe((candidate) => {
|
|
2959
|
+
this.commandTelemetryService.trackStopCommand('EXT-iceCandidate', true, {
|
|
2960
|
+
multi: true,
|
|
2961
|
+
});
|
|
2962
|
+
if (this.remoteDescriptionSet) {
|
|
2963
|
+
this.handleCandidateFromServer(candidate);
|
|
2964
|
+
}
|
|
2965
|
+
else {
|
|
2966
|
+
this.iceCandidateBuffer.push(candidate);
|
|
2914
2967
|
}
|
|
2915
2968
|
});
|
|
2916
2969
|
}
|
|
2970
|
+
/**
|
|
2971
|
+
* Handles an SDP offer received from the server.
|
|
2972
|
+
* Creates a new peer connection, applies the remote offer,
|
|
2973
|
+
* then responds with an SDP answer.
|
|
2974
|
+
*
|
|
2975
|
+
* The DataChannel is NOT created here — the server creates it,
|
|
2976
|
+
* and we receive it via the `datachannel` event on the peer connection.
|
|
2977
|
+
*/
|
|
2978
|
+
async handleServerOffer(offer) {
|
|
2979
|
+
Logger.info('Received offer from server, creating answer');
|
|
2980
|
+
try {
|
|
2981
|
+
this.remoteDescriptionSet = false;
|
|
2982
|
+
this.iceCandidateBuffer.length = 0;
|
|
2983
|
+
if (!this.pcClient) {
|
|
2984
|
+
this.setupPeerConnection(this.cfg);
|
|
2985
|
+
}
|
|
2986
|
+
// Offer already defines media transceivers — don't add extras
|
|
2987
|
+
await this.pcClient?.setRemoteDescription(offer);
|
|
2988
|
+
this.remoteDescriptionSet = true;
|
|
2989
|
+
this.flushIceCandidateBuffer();
|
|
2990
|
+
await this.createAnswer(this.pcClient);
|
|
2991
|
+
}
|
|
2992
|
+
catch (e) {
|
|
2993
|
+
Logger.error('Failed to handle server offer', e);
|
|
2994
|
+
this.store.dispatch(disconnectStream({
|
|
2995
|
+
reason: DisconnectReason.WebRTCError,
|
|
2996
|
+
message: 'WebRTCError: Failed to handle server offer',
|
|
2997
|
+
}));
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Handles an SDP answer received from the server in response to our offer.
|
|
3002
|
+
* Applies the remote description and flushes any buffered ICE candidates.
|
|
3003
|
+
*/
|
|
3004
|
+
async handleServerAnswer(answer) {
|
|
3005
|
+
Logger.info('Received answer from server');
|
|
3006
|
+
try {
|
|
3007
|
+
await this.pcClient?.setRemoteDescription(answer);
|
|
3008
|
+
this.remoteDescriptionSet = true;
|
|
3009
|
+
this.flushIceCandidateBuffer();
|
|
3010
|
+
}
|
|
3011
|
+
catch (e) {
|
|
3012
|
+
Logger.error('Failed to set remote description from answer', e);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
/** Drains buffered ICE candidates after the remote description has been set */
|
|
3016
|
+
flushIceCandidateBuffer() {
|
|
3017
|
+
for (const candidate of this.iceCandidateBuffer) {
|
|
3018
|
+
this.handleCandidateFromServer(candidate);
|
|
3019
|
+
}
|
|
3020
|
+
this.iceCandidateBuffer.length = 0;
|
|
3021
|
+
}
|
|
3022
|
+
/**
|
|
3023
|
+
* Creates a client-initiated WebRTC offer and sends it to the server.
|
|
3024
|
+
* When the client offers, it also creates the DataChannel.
|
|
3025
|
+
* Used for peer reconnection through the existing signaling connection.
|
|
3026
|
+
*/
|
|
2917
3027
|
async createWebRtcOffer() {
|
|
2918
|
-
Logger.info('Creating offer');
|
|
2919
|
-
let offer;
|
|
3028
|
+
Logger.info('Creating client-initiated offer');
|
|
2920
3029
|
try {
|
|
2921
|
-
|
|
3030
|
+
this.remoteDescriptionSet = false;
|
|
3031
|
+
this.iceCandidateBuffer.length = 0;
|
|
3032
|
+
if (!this.pcClient) {
|
|
3033
|
+
this.setupPeerConnection(this.cfg);
|
|
3034
|
+
}
|
|
3035
|
+
// Client offers: transceivers first, then DC, then offer
|
|
3036
|
+
await this.setupTransceiversAsync(this.pcClient);
|
|
3037
|
+
this.dcClient = this.createDataChannel(this.pcClient, 'UnrealDcConnection', { ordered: true });
|
|
3038
|
+
await this.createOffer(this.pcClient);
|
|
2922
3039
|
}
|
|
2923
|
-
catch {
|
|
3040
|
+
catch (e) {
|
|
3041
|
+
Logger.error('Failed to create WebRTC offer', e);
|
|
2924
3042
|
this.store.dispatch(disconnectStream({
|
|
2925
3043
|
reason: DisconnectReason.WebRTCError,
|
|
2926
3044
|
message: 'WebRTCError: Offer Error',
|
|
2927
3045
|
}));
|
|
2928
3046
|
}
|
|
2929
|
-
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* Initializes a new RTCPeerConnection with event handlers.
|
|
3050
|
+
* Transceivers are NOT added here — the caller is responsible for adding
|
|
3051
|
+
* them at the correct point depending on the negotiation flow.
|
|
3052
|
+
*/
|
|
3053
|
+
setupPeerConnection(config) {
|
|
3054
|
+
this.closePC();
|
|
3055
|
+
Logger.log('Creating new RTCPeerConnection with config');
|
|
3056
|
+
this.pcClient = new RTCPeerConnection(config);
|
|
3057
|
+
this.commandTelemetryService.trackStartCommand('EXT-Browser ICE gathering');
|
|
3058
|
+
this.commandTelemetryService.trackStartCommand('EXT-Browser ICE connection');
|
|
3059
|
+
this.commandTelemetryService.trackStartCommand('EXT-Signaling state change');
|
|
3060
|
+
this.handlePeerConnectionEvents(this.pcClient);
|
|
3061
|
+
this.disconnect$.pipe(take(1)).subscribe(() => {
|
|
3062
|
+
this.closePC();
|
|
3063
|
+
});
|
|
2930
3064
|
}
|
|
2931
3065
|
createDataChannel(pc, label, options) {
|
|
2932
|
-
// This is the primary data channel code path when we are "offering"
|
|
2933
3066
|
const datachannel = pc.createDataChannel(label, options);
|
|
2934
3067
|
return this.setupDataChannelCallbacks(datachannel);
|
|
2935
3068
|
}
|
|
2936
|
-
receiveAnswer(answer) {
|
|
2937
|
-
this.pcClient?.setRemoteDescription(answer);
|
|
2938
|
-
}
|
|
2939
3069
|
handleCandidateFromServer(iceCandidate) {
|
|
2940
3070
|
try {
|
|
2941
3071
|
const candidate = new RTCIceCandidate(iceCandidate);
|
|
2942
3072
|
Logger.info('[Unreal ICE candidate]', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|');
|
|
2943
|
-
//
|
|
3073
|
+
// If forcing TURN, reject any candidates not relay
|
|
2944
3074
|
if (this.forceTURN) {
|
|
2945
|
-
// check if no relay address is found, if so, we are assuming it means no TURN server
|
|
2946
3075
|
if (candidate.candidate.indexOf('relay') < 0) {
|
|
2947
3076
|
Logger.warn('Dropping candidate because it was not TURN relay.', '| Type=', candidate.type, '| Protocol=', candidate.protocol, '| Address=', candidate.address, '| Port=', candidate.port, '|');
|
|
2948
3077
|
return;
|
|
2949
3078
|
}
|
|
2950
3079
|
}
|
|
2951
|
-
this.pcClient?.addIceCandidate(candidate).catch(
|
|
2952
|
-
|
|
3080
|
+
this.pcClient?.addIceCandidate(candidate).catch((e) => {
|
|
3081
|
+
Logger.warn('Failed to add ICE candidate', e);
|
|
2953
3082
|
});
|
|
2954
3083
|
}
|
|
2955
3084
|
catch (e) {
|
|
2956
3085
|
Logger.error('Error handleCandidateFromServer', e);
|
|
2957
3086
|
}
|
|
2958
3087
|
}
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
3088
|
+
/**
|
|
3089
|
+
* Reorders the `m=video` line in the SDP so that payload types matching
|
|
3090
|
+
* the given H.264 `profile-level-id` appear first, making the browser
|
|
3091
|
+
* prefer that profile during codec negotiation.
|
|
3092
|
+
*
|
|
3093
|
+
* A `profile-level-id` is a 3-byte hex string (`XXYYZZ`) defined in
|
|
3094
|
+
* RFC 6184 that encodes three H.264 parameters:
|
|
3095
|
+
* - **Profile IDC** (`XX`) — the H.264 profile.
|
|
3096
|
+
* - **Profile IOP** (`YY`) — constraint flags that further narrow the profile.
|
|
3097
|
+
* - **Level IDC** (`ZZ`) — the level, which caps resolution/framerate/bitrate.
|
|
3098
|
+
*
|
|
3099
|
+
* ### H.264 profiles relevant to Unreal Engine Pixel Streaming
|
|
3100
|
+
*
|
|
3101
|
+
* | profile-level-id | Profile | Level | Notes |
|
|
3102
|
+
* |------------------|----------------------------|-------|----------------------------------------------|
|
|
3103
|
+
* | `42001f` | Baseline | 3.1 | No B-frames, low latency; legacy devices |
|
|
3104
|
+
* | `42e01f` | Constrained Baseline | 3.1 | Widest HW decode support; **recommended** |
|
|
3105
|
+
* | `4d001f` | Main | 3.1 | B-frames + CABAC; better compression |
|
|
3106
|
+
* | `4d4028` | Main | 4.0 | Up to 1080p60; common desktop target |
|
|
3107
|
+
* | `640028` | High | 4.0 | Best quality/compression; most GPUs support |
|
|
3108
|
+
* | `64002a` | High | 4.2 | Up to 1080p120 or 4K30 |
|
|
3109
|
+
* | `640033` | High | 5.1 | Up to 4K60; requires modern HW |
|
|
3110
|
+
* | `640034` | High | 5.2 | Up to 4K120; high-end HW only |
|
|
3111
|
+
* | `6e001f` | High 10 | 3.1 | 10-bit color; limited browser support |
|
|
3112
|
+
* | `f4001f` | High 4:4:4 Predictive | 3.1 | Lossless capable; rare in browsers |
|
|
3113
|
+
*
|
|
3114
|
+
* UE Pixel Streaming defaults to Constrained Baseline (`42e01f`) or High (`6400xx`)
|
|
3115
|
+
* depending on engine version and `WebRTC.H264Profile` CVar.
|
|
3116
|
+
*
|
|
3117
|
+
* We prioritize `42e01f` because it guarantees hardware decoding on
|
|
3118
|
+
* virtually all client devices (desktop, mobile, embedded), avoiding
|
|
3119
|
+
* fallback to software decoding or negotiation of a higher profile
|
|
3120
|
+
* that the client GPU may not accelerate.
|
|
3121
|
+
*/
|
|
3122
|
+
prioritizeH264Profile(sdp, profileLevelId) {
|
|
3123
|
+
const lines = sdp.split('\r\n');
|
|
3124
|
+
const targetPts = [];
|
|
3125
|
+
for (const line of lines) {
|
|
3126
|
+
if (line.startsWith('a=fmtp:') &&
|
|
3127
|
+
line.toLowerCase().includes(`profile-level-id=${profileLevelId}`)) {
|
|
3128
|
+
const pt = line.split(':')[1]?.split(' ')[0];
|
|
3129
|
+
if (pt)
|
|
3130
|
+
targetPts.push(pt);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
if (targetPts.length === 0)
|
|
3134
|
+
return sdp;
|
|
3135
|
+
return lines
|
|
3136
|
+
.map((line) => {
|
|
3137
|
+
if (!line.startsWith('m=video'))
|
|
3138
|
+
return line;
|
|
3139
|
+
const parts = line.split(' ');
|
|
3140
|
+
// parts: m=video <port> <proto> <pt1> <pt2> ...
|
|
3141
|
+
const header = parts.slice(0, 3);
|
|
3142
|
+
const pts = parts.slice(3);
|
|
3143
|
+
const prioritized = [
|
|
3144
|
+
...targetPts.filter((pt) => pts.includes(pt)),
|
|
3145
|
+
...pts.filter((pt) => !targetPts.includes(pt)),
|
|
3146
|
+
];
|
|
3147
|
+
return [...header, ...prioritized].join(' ');
|
|
3148
|
+
})
|
|
3149
|
+
.join('\r\n');
|
|
3150
|
+
}
|
|
3151
|
+
mungeSDP(sdp) {
|
|
3152
|
+
let mungedSDP = this.prioritizeH264Profile(sdp, '42e01f');
|
|
3153
|
+
mungedSDP = mungedSDP.replace(/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm, '$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r\n');
|
|
3154
|
+
// Set max bitrate to highest bitrate Opus supports
|
|
3155
|
+
let audioSDP = 'maxaveragebitrate=510000;';
|
|
2963
3156
|
if (this.useMic) {
|
|
2964
|
-
//
|
|
3157
|
+
// Set the max capture rate to 48khz (so we can send high-quality audio from mic)
|
|
2965
3158
|
audioSDP += 'sprop-maxcapturerate=48000;';
|
|
2966
3159
|
}
|
|
2967
3160
|
// Force mono or stereo based on whether ?forceMono was passed or not
|
|
2968
|
-
audioSDP += this.forceMonoAudio
|
|
2969
|
-
|
|
2970
|
-
: 'sprop-stereo=1;stereo=1;';
|
|
2971
|
-
// enable in-band forward error correction for opus audio
|
|
3161
|
+
audioSDP += this.forceMonoAudio ? 'stereo=0;' : 'stereo=1;';
|
|
3162
|
+
// Enable in-band forward error correction for opus audio
|
|
2972
3163
|
audioSDP += 'useinbandfec=1';
|
|
2973
3164
|
// We use the line 'useinbandfec=1' (which Opus uses) to set our Opus specific audio parameters.
|
|
2974
|
-
|
|
3165
|
+
mungedSDP = mungedSDP?.replace('useinbandfec=1', audioSDP);
|
|
3166
|
+
return mungedSDP;
|
|
2975
3167
|
}
|
|
2976
3168
|
/**
|
|
2977
|
-
* Closes existing PeerConnection
|
|
3169
|
+
* Closes existing PeerConnection and DataChannel, resets negotiation state.
|
|
2978
3170
|
*/
|
|
2979
3171
|
closePC() {
|
|
2980
3172
|
if (this.pcClient) {
|
|
@@ -2987,6 +3179,8 @@ class WebRtcPlayerService extends SubService {
|
|
|
2987
3179
|
}
|
|
2988
3180
|
this.dcClient?.close();
|
|
2989
3181
|
this.dcClient = null;
|
|
3182
|
+
this.remoteDescriptionSet = false;
|
|
3183
|
+
this.iceCandidateBuffer.length = 0;
|
|
2990
3184
|
}
|
|
2991
3185
|
canSend() {
|
|
2992
3186
|
return this.dcClient && this.dcClient.readyState === 'open';
|
|
@@ -3023,9 +3217,9 @@ class WebRtcPlayerService extends SubService {
|
|
|
3023
3217
|
}
|
|
3024
3218
|
}
|
|
3025
3219
|
/**
|
|
3026
|
-
*
|
|
3027
|
-
*
|
|
3028
|
-
* the existing WebSocket connection.
|
|
3220
|
+
* Reconnects only the WebRTC peer connection without re-establishing signaling.
|
|
3221
|
+
* Closes the current PeerConnection and creates a new client-initiated offer
|
|
3222
|
+
* through the existing WebSocket connection.
|
|
3029
3223
|
* @returns true if reconnection was initiated, false if signaling is not connected
|
|
3030
3224
|
*/
|
|
3031
3225
|
reconnectPeer() {
|
|
@@ -3058,11 +3252,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3058
3252
|
this.cfg.bundlePolicy = urlParams.has('ForceMaxBundle')
|
|
3059
3253
|
? 'max-bundle'
|
|
3060
3254
|
: 'balanced';
|
|
3061
|
-
|
|
3062
|
-
//Variables
|
|
3063
|
-
//**********************
|
|
3064
|
-
this.pcClient = null;
|
|
3065
|
-
this.dcClient = null;
|
|
3255
|
+
this.cfg.iceCandidatePoolSize = 1;
|
|
3066
3256
|
// When ?useMic check for SSL or localhost
|
|
3067
3257
|
const isLocalhostConnection = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
|
|
3068
3258
|
const isHttpsConnection = location.protocol === 'https:';
|
|
@@ -3071,23 +3261,7 @@ class WebRtcPlayerService extends SubService {
|
|
|
3071
3261
|
Logger.error('Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.');
|
|
3072
3262
|
Logger.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
|
|
3073
3263
|
}
|
|
3074
|
-
|
|
3075
|
-
async createPeerConnection(config) {
|
|
3076
|
-
this.closePC();
|
|
3077
|
-
this.pcClient = new RTCPeerConnection(config);
|
|
3078
|
-
this.commandTelemetryService.trackStartCommand('EXT-Browser ICE gathering');
|
|
3079
|
-
this.commandTelemetryService.trackStartCommand('EXT-Browser ICE connection');
|
|
3080
|
-
this.commandTelemetryService.trackStartCommand('EXT-Signaling state change');
|
|
3081
|
-
this.handlePeerConnectionEvents(this.pcClient);
|
|
3082
|
-
await this.setupTransceiversAsync(this.pcClient);
|
|
3083
|
-
this.dcClient = this.createDataChannel(this.pcClient, 'UnrealDcConnection', {
|
|
3084
|
-
// See https://www.w3.org/TR/webrtc/#dom-rtcdatachannelinit for values (this is needed for Firefox to be consistent with Chrome.)
|
|
3085
|
-
ordered: true,
|
|
3086
|
-
});
|
|
3087
|
-
this.disconnect$.pipe(take(1)).subscribe(() => {
|
|
3088
|
-
this.closePC();
|
|
3089
|
-
});
|
|
3090
|
-
await this.createOffer(this.pcClient);
|
|
3264
|
+
this.setupPeerConnection(this.cfg);
|
|
3091
3265
|
}
|
|
3092
3266
|
handlePeerConnectionEvents(pcClient) {
|
|
3093
3267
|
fromEvent(pcClient, 'signalingstatechange')
|
|
@@ -3116,8 +3290,10 @@ class WebRtcPlayerService extends SubService {
|
|
|
3116
3290
|
fromEvent(pcClient, 'icecandidate')
|
|
3117
3291
|
.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(this.disconnect$))
|
|
3118
3292
|
.subscribe((candidate) => this.signallingSrv.send({ type: 'iceCandidate', candidate }));
|
|
3293
|
+
// When the server creates the DataChannel (server-offer flow),
|
|
3294
|
+
// we receive it here via the 'datachannel' event
|
|
3119
3295
|
fromEvent(pcClient, 'datachannel')
|
|
3120
|
-
.pipe(tap$1(() => Logger.info(
|
|
3296
|
+
.pipe(tap$1((e) => Logger.info(`[DATACHANNEL] Received remote DC: label="${e.channel.label}" id=${e.channel.id} protocol="${e.channel.protocol}" ordered=${e.channel.ordered}`)), map$1((e) => e.channel), takeUntil(this.disconnect$))
|
|
3121
3297
|
.subscribe((datachannel) => {
|
|
3122
3298
|
this.dcClient = this.setupDataChannelCallbacks(datachannel);
|
|
3123
3299
|
});
|
|
@@ -3225,7 +3401,14 @@ class WebRtcPlayerService extends SubService {
|
|
|
3225
3401
|
this.onDataChannelConnected();
|
|
3226
3402
|
});
|
|
3227
3403
|
fromEvent(datachannel, 'message')
|
|
3228
|
-
.pipe(
|
|
3404
|
+
.pipe(
|
|
3405
|
+
/* tap((e: MessageEvent) => {
|
|
3406
|
+
const view = new Uint8Array(e.data);
|
|
3407
|
+
Logger.info(
|
|
3408
|
+
`[DATACHANNEL] Message received on "${datachannel.label}" id=${datachannel.id}, byteCode=${view[0]}, size=${view.byteLength}`,
|
|
3409
|
+
);
|
|
3410
|
+
}),*/
|
|
3411
|
+
map$1((e) => e.data), takeUntil(this.disconnect$))
|
|
3229
3412
|
.subscribe((data) => this.onDataChannelMessage$.next(data));
|
|
3230
3413
|
return datachannel;
|
|
3231
3414
|
}
|
|
@@ -3242,34 +3425,55 @@ class WebRtcPlayerService extends SubService {
|
|
|
3242
3425
|
}
|
|
3243
3426
|
this.store.dispatch(dataChannelConnected({ statusMessage }));
|
|
3244
3427
|
}
|
|
3428
|
+
/**
|
|
3429
|
+
* Creates an SDP offer, applies SDP munging, sets it as a local description,
|
|
3430
|
+
* and sends it to the server via signaling.
|
|
3431
|
+
* Transceivers already configure media directions, so deprecated
|
|
3432
|
+
* offerToReceive* options are not used.
|
|
3433
|
+
*/
|
|
3245
3434
|
async createOffer(pcClient) {
|
|
3246
3435
|
try {
|
|
3247
|
-
const offer = await pcClient.createOffer(
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
});
|
|
3251
|
-
this.mungeSDP(offer);
|
|
3252
|
-
pcClient?.setLocalDescription(offer);
|
|
3436
|
+
const offer = await pcClient.createOffer();
|
|
3437
|
+
offer.sdp = this.mungeSDP(offer.sdp ?? '');
|
|
3438
|
+
await pcClient.setLocalDescription(offer);
|
|
3253
3439
|
this.signallingSrv.send(offer);
|
|
3254
3440
|
}
|
|
3255
3441
|
catch {
|
|
3256
3442
|
Logger.error("Couldn't create offer");
|
|
3257
3443
|
}
|
|
3258
3444
|
}
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3445
|
+
/**
|
|
3446
|
+
* Creates an SDP answer, applies SDP munging, sets it as a local description,
|
|
3447
|
+
* and sends it to the server via signaling.
|
|
3448
|
+
*/
|
|
3449
|
+
async createAnswer(pcClient) {
|
|
3450
|
+
try {
|
|
3451
|
+
const answer = await pcClient.createAnswer();
|
|
3452
|
+
answer.sdp = this.mungeSDP(answer.sdp ?? '');
|
|
3453
|
+
await pcClient.setLocalDescription(answer);
|
|
3454
|
+
this.signallingSrv.send(answer);
|
|
3455
|
+
// Observability signal for the signaling waterfall — the handshake
|
|
3456
|
+
// went "answer sent" between `offer received` and `datachannel_ready`.
|
|
3457
|
+
this.store.dispatch(answerSent());
|
|
3458
|
+
}
|
|
3459
|
+
catch (e) {
|
|
3460
|
+
Logger.error("Couldn't create answer", e);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WebRtcPlayerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3464
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WebRtcPlayerService }); }
|
|
3465
|
+
}
|
|
3466
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WebRtcPlayerService, decorators: [{
|
|
3467
|
+
type: Injectable
|
|
3468
|
+
}], ctorParameters: () => [] });
|
|
3469
|
+
|
|
3470
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3471
|
+
const deepEqual = (a, b) => {
|
|
3472
|
+
if (a === b)
|
|
3473
|
+
return true;
|
|
3474
|
+
// must both be objects (and not null)?
|
|
3475
|
+
if (typeof a === 'object' &&
|
|
3476
|
+
a !== null &&
|
|
3273
3477
|
typeof b === 'object' &&
|
|
3274
3478
|
b !== null) {
|
|
3275
3479
|
// arrays?
|
|
@@ -3603,10 +3807,10 @@ class AggregatorService extends SubService {
|
|
|
3603
3807
|
Logger.warn(`unrecognized data received, packet ID ${view[0]}`);
|
|
3604
3808
|
}
|
|
3605
3809
|
}
|
|
3606
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3607
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
3810
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AggregatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3811
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AggregatorService }); }
|
|
3608
3812
|
}
|
|
3609
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3813
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AggregatorService, decorators: [{
|
|
3610
3814
|
type: Injectable
|
|
3611
3815
|
}], ctorParameters: () => [] });
|
|
3612
3816
|
|
|
@@ -3694,10 +3898,10 @@ class ConsoleExtensionsService extends SubService {
|
|
|
3694
3898
|
Logger.info('setWarnTime() => set time to appear the AFK window.');
|
|
3695
3899
|
Logger.info('emitCommand(command) => send command to Unreal Engine.');
|
|
3696
3900
|
}
|
|
3697
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3698
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
3901
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConsoleExtensionsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3902
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConsoleExtensionsService }); }
|
|
3699
3903
|
}
|
|
3700
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3904
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ConsoleExtensionsService, decorators: [{
|
|
3701
3905
|
type: Injectable
|
|
3702
3906
|
}], ctorParameters: () => [] });
|
|
3703
3907
|
|
|
@@ -3713,10 +3917,10 @@ class DevModeService {
|
|
|
3713
3917
|
localStorage.removeItem('devMode');
|
|
3714
3918
|
}
|
|
3715
3919
|
}
|
|
3716
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3717
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
3920
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DevModeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3921
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DevModeService, providedIn: 'root' }); }
|
|
3718
3922
|
}
|
|
3719
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
3923
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DevModeService, decorators: [{
|
|
3720
3924
|
type: Injectable,
|
|
3721
3925
|
args: [{
|
|
3722
3926
|
providedIn: 'root',
|
|
@@ -3819,10 +4023,10 @@ class UnrealCommunicatorService {
|
|
|
3819
4023
|
requestQualityControl() {
|
|
3820
4024
|
this.sendInputData(new Uint8Array([MessageType.RequestQualityControl]).buffer);
|
|
3821
4025
|
}
|
|
3822
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
3823
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4026
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCommunicatorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4027
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCommunicatorService }); }
|
|
3824
4028
|
}
|
|
3825
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4029
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCommunicatorService, decorators: [{
|
|
3826
4030
|
type: Injectable
|
|
3827
4031
|
}], ctorParameters: () => [] });
|
|
3828
4032
|
|
|
@@ -4315,6 +4519,7 @@ class InputService extends SubService {
|
|
|
4315
4519
|
class RegionsPingService {
|
|
4316
4520
|
constructor() {
|
|
4317
4521
|
this.httpClient = inject(HttpClient);
|
|
4522
|
+
this.store = inject(Store);
|
|
4318
4523
|
this.unrealInitialConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4319
4524
|
this.config = {
|
|
4320
4525
|
ping_count: 2,
|
|
@@ -4324,7 +4529,15 @@ class RegionsPingService {
|
|
|
4324
4529
|
};
|
|
4325
4530
|
}
|
|
4326
4531
|
getFastest(regionListUrl = this.unrealInitialConfig?.regionsPingUrl || '') {
|
|
4327
|
-
|
|
4532
|
+
// Self-announce every lifecycle waypoint of the region-discovery
|
|
4533
|
+
// flow (start / providers-fetched / resolved). The final
|
|
4534
|
+
// `regionResolved` forwards the winner's full ping-sample array
|
|
4535
|
+
// straight from `PingResult.result` — no aggregation.
|
|
4536
|
+
this.store.dispatch(regionPingFetchStart());
|
|
4537
|
+
return this.getProviders(regionListUrl).pipe(tap(() => this.store.dispatch(regionPingFetchEnd())), switchMap((providers) => this.getPingResult(providers)), catchError(() => of(null)), tapLog('PingResult'), tap((data) => this.store.dispatch(regionResolved({
|
|
4538
|
+
region: data?.region_code ?? '',
|
|
4539
|
+
pingsMs: data?.result ?? [],
|
|
4540
|
+
}))), map$1((data) => data?.region_code));
|
|
4328
4541
|
}
|
|
4329
4542
|
getPingResult(providers) {
|
|
4330
4543
|
const regions = providers.regions;
|
|
@@ -4405,19 +4618,18 @@ class RegionsPingService {
|
|
|
4405
4618
|
throw error;
|
|
4406
4619
|
}
|
|
4407
4620
|
}
|
|
4408
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4409
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4621
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RegionsPingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4622
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RegionsPingService }); }
|
|
4410
4623
|
}
|
|
4411
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4624
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RegionsPingService, decorators: [{
|
|
4412
4625
|
type: Injectable
|
|
4413
4626
|
}] });
|
|
4414
4627
|
|
|
4415
4628
|
class StreamStatusTelemetryService {
|
|
4416
|
-
#store;
|
|
4417
|
-
#videoService;
|
|
4418
4629
|
constructor() {
|
|
4419
|
-
this
|
|
4420
|
-
this
|
|
4630
|
+
this.store = inject(Store);
|
|
4631
|
+
this.actions$ = inject(Actions);
|
|
4632
|
+
this.videoService = inject(VideoService);
|
|
4421
4633
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4422
4634
|
this.init();
|
|
4423
4635
|
}
|
|
@@ -4425,13 +4637,47 @@ class StreamStatusTelemetryService {
|
|
|
4425
4637
|
if (this.unrealConfig?.playwright) {
|
|
4426
4638
|
return;
|
|
4427
4639
|
}
|
|
4428
|
-
|
|
4429
|
-
|
|
4640
|
+
// Start a telemetry session on every `initSignalling` dispatch.
|
|
4641
|
+
// switchMap cancels the previous session's inner pipeline on re-init.
|
|
4642
|
+
// throttleTime leading+trailing emits the first event immediately and
|
|
4643
|
+
// at most one event every 5s afterwards.
|
|
4644
|
+
this.actions$
|
|
4645
|
+
.pipe(ofType(initSignalling), switchMap$1(() => {
|
|
4646
|
+
// Captured once per session — marks the UTC moment signalling started.
|
|
4647
|
+
const startedAt = Date.now();
|
|
4648
|
+
const startTimeUtc = new Date(startedAt).toISOString();
|
|
4649
|
+
const signalingReadyTime$ = this.readyTimeSince(this.store.select(unrealFeature.selectCirrusConnected), startedAt);
|
|
4650
|
+
const viewPortReadyTime$ = this.readyTimeSince(this.store.select(unrealFeature.selectViewportReady), startedAt);
|
|
4651
|
+
return this.videoService.videoStats$.pipe(throttleTime(5000, asyncScheduler, {
|
|
4652
|
+
leading: true,
|
|
4653
|
+
trailing: true,
|
|
4654
|
+
}), withLatestFrom(this.store.select(unrealFeature.selectAwsInstance), this.store.select(unrealFeature.selectLowBandwidth), this.store.select(unrealFeature.selectLowBandwidthStats), signalingReadyTime$, viewPortReadyTime$), map$1(([data, signalingServer, isLowBandwidth, lbmStats, signalingReadyTime, viewPortReadyTime,]) => this.mapEventData({
|
|
4655
|
+
data,
|
|
4656
|
+
signalingServer,
|
|
4657
|
+
isLowBandwidth,
|
|
4658
|
+
lbmStats,
|
|
4659
|
+
startTimeUtc,
|
|
4660
|
+
signalingReadyTime,
|
|
4661
|
+
viewPortReadyTime,
|
|
4662
|
+
})));
|
|
4663
|
+
}))
|
|
4430
4664
|
.subscribe((data) => this.trackEventToMixPanel(data));
|
|
4431
4665
|
}
|
|
4432
|
-
|
|
4666
|
+
/**
|
|
4667
|
+
* Emits `undefined` until `readyState$` first emits a truthy value,
|
|
4668
|
+
* then emits the ms elapsed since `startedAt` and completes.
|
|
4669
|
+
* Used with `withLatestFrom` so events carry a stable duration for the whole session.
|
|
4670
|
+
*/
|
|
4671
|
+
readyTimeSince(readyState$, startedAt) {
|
|
4672
|
+
return readyState$.pipe(filter(Boolean), take$1(1), map$1(() => Date.now() - startedAt), startWith$1(undefined));
|
|
4673
|
+
}
|
|
4674
|
+
mapEventData(params) {
|
|
4675
|
+
const { data, signalingServer, isLowBandwidth, lbmStats, startTimeUtc, signalingReadyTime, viewPortReadyTime, } = params;
|
|
4433
4676
|
const { bitrate, VideoEncoderQP, framesPerSecond, frameWidth, frameHeight, jitter, packetsLost, currentRoundTripTime, runTime, } = data.aggregatedStats;
|
|
4434
4677
|
return {
|
|
4678
|
+
startTimeUtc,
|
|
4679
|
+
signalingReadyTime,
|
|
4680
|
+
viewPortReadyTime,
|
|
4435
4681
|
runTime,
|
|
4436
4682
|
frameWidth,
|
|
4437
4683
|
frameHeight,
|
|
@@ -4449,559 +4695,715 @@ class StreamStatusTelemetryService {
|
|
|
4449
4695
|
};
|
|
4450
4696
|
}
|
|
4451
4697
|
trackEventToMixPanel(data) {
|
|
4452
|
-
this
|
|
4698
|
+
this.store.dispatch(trackMixpanelEvent({ event: 'streamStatus', data }));
|
|
4453
4699
|
}
|
|
4454
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
4455
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
4700
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
4701
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService }); }
|
|
4456
4702
|
}
|
|
4457
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
4703
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService, decorators: [{
|
|
4458
4704
|
type: Injectable
|
|
4459
4705
|
}], ctorParameters: () => [] });
|
|
4460
4706
|
|
|
4461
|
-
|
|
4707
|
+
const QP_RED_THRESHOLD = 35;
|
|
4708
|
+
const FPS_LOW_RATIO = 0.7; // p50 fps below 70% of target
|
|
4709
|
+
const UNLIMITED_REFERENCE_FPS = 60;
|
|
4710
|
+
const SAMPLE_WINDOW_MS = 5000;
|
|
4711
|
+
// Persistent per-browser identifier — survives page reloads. Used to group
|
|
4712
|
+
// multiple sessions of the same anonymous user for funnel / retention analysis.
|
|
4713
|
+
const USER_ID_STORAGE_KEY = 'sourceui.streamTelemetry.userId';
|
|
4714
|
+
class StreamStatusTelemetryService2 {
|
|
4462
4715
|
constructor() {
|
|
4463
|
-
|
|
4464
|
-
this.
|
|
4465
|
-
this.signallingSrv = inject(SignallingService);
|
|
4716
|
+
this.store = inject(Store);
|
|
4717
|
+
this.actions$ = inject(Actions);
|
|
4466
4718
|
this.videoService = inject(VideoService);
|
|
4467
|
-
this.
|
|
4468
|
-
this.
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
this.
|
|
4476
|
-
this.
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
}
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
}
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
// We use the line 'useinbandfec=1' (which Opus uses) to set our Opus specific audio parameters.
|
|
4704
|
-
mungedSDP = mungedSDP?.replace('useinbandfec=1', audioSDP);
|
|
4705
|
-
return mungedSDP;
|
|
4706
|
-
}
|
|
4707
|
-
/**
|
|
4708
|
-
* Closes existing PeerConnection and DataChannel, resets negotiation state.
|
|
4709
|
-
*/
|
|
4710
|
-
closePC() {
|
|
4711
|
-
if (this.pcClient) {
|
|
4712
|
-
Logger.warn('Closing existing PeerConnection');
|
|
4713
|
-
}
|
|
4714
|
-
this.pcClient?.close();
|
|
4715
|
-
this.pcClient = null;
|
|
4716
|
-
if (this.dcClient) {
|
|
4717
|
-
Logger.warn('Closing existing DataChannel');
|
|
4718
|
-
}
|
|
4719
|
-
this.dcClient?.close();
|
|
4720
|
-
this.dcClient = null;
|
|
4721
|
-
this.remoteDescriptionSet = false;
|
|
4722
|
-
this.iceCandidateBuffer.length = 0;
|
|
4723
|
-
}
|
|
4724
|
-
canSend() {
|
|
4725
|
-
return this.dcClient && this.dcClient.readyState === 'open';
|
|
4726
|
-
}
|
|
4727
|
-
send(data) {
|
|
4728
|
-
if (this.canSend()) {
|
|
4729
|
-
this.dcClient?.send(data);
|
|
4730
|
-
}
|
|
4731
|
-
else {
|
|
4732
|
-
Logger.colored('black', 'yellow', `DataChannel connection not prepared or in state: ${this.dcClient?.readyState}`);
|
|
4733
|
-
// Trigger peer reconnection if DC is not open and we're not yet reconnecting
|
|
4734
|
-
this.tryReconnectOnSendFailure();
|
|
4735
|
-
}
|
|
4736
|
-
}
|
|
4737
|
-
/**
|
|
4738
|
-
* Attempts to reconnect the peer when send fails due to DataChannel not being open.
|
|
4739
|
-
* This handles cases where the DC connection is broken, but no error events are fired.
|
|
4740
|
-
*/
|
|
4741
|
-
tryReconnectOnSendFailure() {
|
|
4742
|
-
// Skip if already reconnecting or signaling is not connected
|
|
4743
|
-
if (this.isReconnecting()) {
|
|
4744
|
-
Logger.info('[Reconnect] Already reconnecting, skipping duplicate attempt');
|
|
4745
|
-
return;
|
|
4746
|
-
}
|
|
4747
|
-
if (!this.signallingSrv.isConnected()) {
|
|
4748
|
-
Logger.warn('[Reconnect] Cannot reconnect: Signaling not connected');
|
|
4749
|
-
return;
|
|
4750
|
-
}
|
|
4751
|
-
// Check if DC exists but is in a non-open state (closing, closed, or connecting stuck)
|
|
4752
|
-
const dcState = this.dcClient?.readyState;
|
|
4753
|
-
if (dcState && dcState !== 'open') {
|
|
4754
|
-
Logger.warn(`[Reconnect] DataChannel in "${dcState}" state during send, initiating peer reconnection`);
|
|
4755
|
-
this.store.dispatch(reconnectPeer({ attempt: 1, maxAttempts: 3, delay: 0 }));
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
/**
|
|
4759
|
-
* Reconnects only the WebRTC peer connection without re-establishing signaling.
|
|
4760
|
-
* Closes the current PeerConnection and creates a new client-initiated offer
|
|
4761
|
-
* through the existing WebSocket connection.
|
|
4762
|
-
* @returns true if reconnection was initiated, false if signaling is not connected
|
|
4763
|
-
*/
|
|
4764
|
-
reconnectPeer() {
|
|
4765
|
-
if (!this.signallingSrv.isConnected()) {
|
|
4766
|
-
Logger.error('[Reconnect] Cannot reconnect peer: Signaling not connected');
|
|
4767
|
-
return false;
|
|
4768
|
-
}
|
|
4769
|
-
if (!this.cfg || Object.keys(this.cfg).length === 0) {
|
|
4770
|
-
Logger.error('[Reconnect] Cannot reconnect peer: No config available');
|
|
4771
|
-
return false;
|
|
4772
|
-
}
|
|
4773
|
-
Logger.info('[Reconnect] Initiating peer reconnection');
|
|
4774
|
-
// Close only the PeerConnection, keep WebSocket alive
|
|
4775
|
-
this.closePC();
|
|
4776
|
-
// Create a new WebRTC offer using the stored config
|
|
4777
|
-
void this.createWebRtcOffer();
|
|
4778
|
-
return true;
|
|
4779
|
-
}
|
|
4780
|
-
setConfig(parOptions = {}) {
|
|
4781
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
4782
|
-
//**********************
|
|
4783
|
-
//Config setup
|
|
4784
|
-
//**********************
|
|
4785
|
-
this.cfg = { ...parOptions.peerConnectionOptions };
|
|
4786
|
-
this.forceTURN = urlParams.has('ForceTURN');
|
|
4787
|
-
if (this.forceTURN) {
|
|
4788
|
-
Logger.info('Forcing TURN usage by setting ICE Transport Policy in peer connection config.');
|
|
4789
|
-
this.cfg.iceTransportPolicy = 'relay';
|
|
4790
|
-
}
|
|
4791
|
-
this.cfg.bundlePolicy = urlParams.has('ForceMaxBundle')
|
|
4792
|
-
? 'max-bundle'
|
|
4793
|
-
: 'balanced';
|
|
4794
|
-
this.cfg.iceCandidatePoolSize = 1;
|
|
4795
|
-
// When ?useMic check for SSL or localhost
|
|
4796
|
-
const isLocalhostConnection = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
|
|
4797
|
-
const isHttpsConnection = location.protocol === 'https:';
|
|
4798
|
-
if (this.useMic && !isLocalhostConnection && !isHttpsConnection) {
|
|
4799
|
-
this.useMic = false;
|
|
4800
|
-
Logger.error('Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.');
|
|
4801
|
-
Logger.error("For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'");
|
|
4719
|
+
this.signallingService = inject(SignallingService);
|
|
4720
|
+
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
4721
|
+
/**
|
|
4722
|
+
* Per-browser anonymous user id. Read from `localStorage` on first access;
|
|
4723
|
+
* if missing, a new UUID is generated and persisted. Included as
|
|
4724
|
+
* `data.userId` on every emitted event — the server uses it to bucket
|
|
4725
|
+
* per-session files under `data/<userId>/<sessionId>.jsonl`.
|
|
4726
|
+
*/
|
|
4727
|
+
this.userId = this.getOrCreateUserId();
|
|
4728
|
+
this.session = null;
|
|
4729
|
+
/**
|
|
4730
|
+
* Whether a session has already started during the current page lifetime.
|
|
4731
|
+
* Used to distinguish a fresh page load (bootstrap measurement is
|
|
4732
|
+
* meaningful) from a Resume/restart (the document is already loaded,
|
|
4733
|
+
* so "bootstrap" is a no-op). Only the first session gets the bootstrap
|
|
4734
|
+
* phase anchored at navigation start (perf.now() === 0).
|
|
4735
|
+
*/
|
|
4736
|
+
this.hasStartedBefore = false;
|
|
4737
|
+
/**
|
|
4738
|
+
* Rolling snapshot of the currently selected WebRTC ICE candidate pair
|
|
4739
|
+
* (local/remote type + url). Scanned over `videoStats$` so the stream
|
|
4740
|
+
* *keeps* the first non-null pair even if later stats emit null — WebRTC
|
|
4741
|
+
* doesn't un-select a pair mid-session, but the source is nullable. The
|
|
4742
|
+
* `startWith(null)` seed makes `withLatestFrom` on this stream safe even
|
|
4743
|
+
* before the first video stat arrives (common at data-channel-open).
|
|
4744
|
+
*/
|
|
4745
|
+
this.selectedPair$ = this.videoService.videoStats$.pipe(map((s) => s.aggregatedStats.selectedPair ?? null), scan((acc, curr) => curr ?? acc, null), startWith(null));
|
|
4746
|
+
/**
|
|
4747
|
+
* Correlation-matched Observable: emits once when the first `loopBack`
|
|
4748
|
+
* command's corresponding `command completed` action fires. Built from
|
|
4749
|
+
* the two related actions — the command-started captures the correlation
|
|
4750
|
+
* id, the command-completed with that id emits.
|
|
4751
|
+
*/
|
|
4752
|
+
this.loopbackReceived$ = this.actions$.pipe(ofType(commandStarted), filter$1(({ command }) => command === 'loopBack'), take(1), switchMap(({ id }) => this.actions$.pipe(ofType(commandCompleted), filter$1((b) => b.id === id), take(1))));
|
|
4753
|
+
// Overlay-referenced milestones — hoisted as fields so both
|
|
4754
|
+
// `milestones: [...]` and `overlays: [...]` share the same object
|
|
4755
|
+
// identity (the in-session Map keys milestones by that identity).
|
|
4756
|
+
this.pollingStartedEntry = {
|
|
4757
|
+
label: 'Polling started (HTTP orchestration)',
|
|
4758
|
+
event: this.actions$.pipe(ofType(pollingStarted)),
|
|
4759
|
+
};
|
|
4760
|
+
this.awsInstanceResolvedEntry = {
|
|
4761
|
+
label: 'Instance WS URL resolved',
|
|
4762
|
+
event: this.actions$.pipe(ofType(setAwsInstance)),
|
|
4763
|
+
};
|
|
4764
|
+
this.loopbackReceivedEntry = {
|
|
4765
|
+
label: 'LoopBack received',
|
|
4766
|
+
event: this.loopbackReceived$,
|
|
4767
|
+
};
|
|
4768
|
+
this.signalingServerConnectedEntry = {
|
|
4769
|
+
label: 'Websocket connected',
|
|
4770
|
+
event: this.store
|
|
4771
|
+
.select(unrealFeature.selectCirrusConnected)
|
|
4772
|
+
.pipe(filter$1(Boolean), take(1)),
|
|
4773
|
+
};
|
|
4774
|
+
this.configReceivedEntry = {
|
|
4775
|
+
label: 'Config received',
|
|
4776
|
+
event: this.signallingService.onConfig$.pipe(filter$1(Boolean), take(1)),
|
|
4777
|
+
};
|
|
4778
|
+
/**
|
|
4779
|
+
* Unified phase catalog — the single source of truth for everything
|
|
4780
|
+
* this service tracks after `session_started`. Each entry is a phase;
|
|
4781
|
+
* its `milestones` array is ordered chronologically and the *last*
|
|
4782
|
+
* entry is the phase closer. When that closer's observable fires, the
|
|
4783
|
+
* service emits one generic `stream.phase_ended` wire event with every
|
|
4784
|
+
* display field the viewer needs.
|
|
4785
|
+
*
|
|
4786
|
+
* `phaseNo` is the array index; `startPhase` chains from the previous
|
|
4787
|
+
* phase's `endPhase` — neither needs to be specified here.
|
|
4788
|
+
*/
|
|
4789
|
+
this.phaseCatalog = [
|
|
4790
|
+
{
|
|
4791
|
+
label: 'Region resolving',
|
|
4792
|
+
color: '#61afef',
|
|
4793
|
+
milestones: [
|
|
4794
|
+
{
|
|
4795
|
+
label: 'Region ping fetch start',
|
|
4796
|
+
event: this.actions$.pipe(ofType(regionPingFetchStart)),
|
|
4797
|
+
},
|
|
4798
|
+
{
|
|
4799
|
+
label: 'Region ping fetch end',
|
|
4800
|
+
event: this.actions$.pipe(ofType(regionPingFetchEnd)),
|
|
4801
|
+
},
|
|
4802
|
+
// CLOSER — the `regionResolved` action carries the raw ping
|
|
4803
|
+
// samples (one per probe) from `RegionsPingService`; the
|
|
4804
|
+
// catalog flattens them to a comma-separated string since
|
|
4805
|
+
// `toPhaseParams` only accepts primitives.
|
|
4806
|
+
{
|
|
4807
|
+
label: 'Region resolved',
|
|
4808
|
+
event: this.actions$.pipe(ofType(regionResolved), take(1), map(({ region, pingsMs }) => ({
|
|
4809
|
+
region,
|
|
4810
|
+
pingsMs: pingsMs.length ? `${pingsMs.join(', ')} ms` : null,
|
|
4811
|
+
}))),
|
|
4812
|
+
},
|
|
4813
|
+
],
|
|
4814
|
+
},
|
|
4815
|
+
{
|
|
4816
|
+
label: 'Signaling connection',
|
|
4817
|
+
color: '#c678dd',
|
|
4818
|
+
overlays: [
|
|
4819
|
+
{
|
|
4820
|
+
from: this.pollingStartedEntry,
|
|
4821
|
+
to: this.awsInstanceResolvedEntry,
|
|
4822
|
+
title: 'Polling → instance resolved (HTTP polling + AWS instance handle)',
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
from: this.signalingServerConnectedEntry,
|
|
4826
|
+
to: this.configReceivedEntry,
|
|
4827
|
+
title: 'Signaling handshake (WS open → Cirrus config received)',
|
|
4828
|
+
},
|
|
4829
|
+
],
|
|
4830
|
+
milestones: [
|
|
4831
|
+
this.pollingStartedEntry,
|
|
4832
|
+
{
|
|
4833
|
+
label: 'Polling ended (HTTP orchestration)',
|
|
4834
|
+
event: this.actions$.pipe(ofType(pollingEnded)),
|
|
4835
|
+
},
|
|
4836
|
+
this.awsInstanceResolvedEntry,
|
|
4837
|
+
this.signalingServerConnectedEntry,
|
|
4838
|
+
{
|
|
4839
|
+
label: 'Instance reserved (WS orchestration)',
|
|
4840
|
+
event: this.actions$.pipe(ofType(setOrchestrationParameters)),
|
|
4841
|
+
},
|
|
4842
|
+
{
|
|
4843
|
+
label: 'Instance ready (WS orchestration)',
|
|
4844
|
+
event: this.actions$.pipe(ofType(instanceReady)),
|
|
4845
|
+
},
|
|
4846
|
+
this.configReceivedEntry,
|
|
4847
|
+
{
|
|
4848
|
+
label: 'SSInfo received',
|
|
4849
|
+
event: this.store.select(unrealFeature.selectSsData).pipe(filter$1(Boolean), take(1), withLatestFrom$1(this.store.select(unrealFeature.selectAwsInstance)), map(([ssData, aws]) => ({
|
|
4850
|
+
client: aws.wsUrl,
|
|
4851
|
+
instanceName: aws.instanceName,
|
|
4852
|
+
...ssData,
|
|
4853
|
+
}))),
|
|
4854
|
+
},
|
|
4855
|
+
],
|
|
4856
|
+
},
|
|
4857
|
+
{
|
|
4858
|
+
label: 'WebRTC negotiation',
|
|
4859
|
+
color: '#e5c07b',
|
|
4860
|
+
milestones: [
|
|
4861
|
+
{
|
|
4862
|
+
label: 'Offer received',
|
|
4863
|
+
event: this.signallingService.onOffer$.pipe(filter$1(Boolean)),
|
|
4864
|
+
},
|
|
4865
|
+
{
|
|
4866
|
+
label: 'Answer sent (server-offer flow)',
|
|
4867
|
+
event: this.actions$.pipe(ofType(answerSent)),
|
|
4868
|
+
},
|
|
4869
|
+
{
|
|
4870
|
+
label: 'Answer received (client-offer flow)',
|
|
4871
|
+
event: this.signallingService.onWebRtcAnswer$.pipe(filter$1(Boolean)),
|
|
4872
|
+
},
|
|
4873
|
+
{
|
|
4874
|
+
label: 'First ICE candidate from server',
|
|
4875
|
+
event: this.signallingService.onWebRtcIce$.pipe(filter$1(Boolean)),
|
|
4876
|
+
},
|
|
4877
|
+
{
|
|
4878
|
+
label: 'Last ICE candidate from server',
|
|
4879
|
+
event: this.signallingService.onWebRtcIce$.pipe(filter$1(Boolean)),
|
|
4880
|
+
mode: 'last',
|
|
4881
|
+
},
|
|
4882
|
+
// CLOSER — snaps negotiation diagnostics and the Cirrus
|
|
4883
|
+
// streamer identity (`ssData`, reliably populated by DC-open
|
|
4884
|
+
// time) into phase-3 params:
|
|
4885
|
+
// - `iceCandidateCount` (total ICE candidates from server — too
|
|
4886
|
+
// few hints at a restrictive network)
|
|
4887
|
+
// - `handshakeFlow` (server-offer vs client-offer; we only use
|
|
4888
|
+
// server-offer today, but the detector stays in place)
|
|
4889
|
+
// - `instanceType` / `branchInfo` / version / path / app /
|
|
4890
|
+
// `connectionId` (streamer identity, from `ssInfo` message)
|
|
4891
|
+
{
|
|
4892
|
+
label: 'Data channel connected',
|
|
4893
|
+
event: this.store
|
|
4894
|
+
.select(unrealFeature.selectDataChannelConnected)
|
|
4895
|
+
.pipe(filter$1(Boolean), take(1), map(() => ({
|
|
4896
|
+
iceCandidateCount: this.session?.iceCandidateCount ?? 0,
|
|
4897
|
+
handshakeFlow: this.session?.handshakeFlow ?? null,
|
|
4898
|
+
}))),
|
|
4899
|
+
},
|
|
4900
|
+
],
|
|
4901
|
+
},
|
|
4902
|
+
{
|
|
4903
|
+
label: 'Unreal communication',
|
|
4904
|
+
color: '#98c379',
|
|
4905
|
+
overlays: [
|
|
4906
|
+
{
|
|
4907
|
+
from: this.loopbackReceivedEntry,
|
|
4908
|
+
title: 'Stabilization (warm up)',
|
|
4909
|
+
},
|
|
4910
|
+
],
|
|
4911
|
+
milestones: [
|
|
4912
|
+
{
|
|
4913
|
+
label: 'First command started',
|
|
4914
|
+
event: this.actions$.pipe(ofType(commandStarted)),
|
|
4915
|
+
},
|
|
4916
|
+
{
|
|
4917
|
+
label: 'LoopBack sent',
|
|
4918
|
+
event: this.actions$.pipe(ofType(commandStarted), filter$1(({ command }) => command === 'loopBack')),
|
|
4919
|
+
},
|
|
4920
|
+
this.loopbackReceivedEntry,
|
|
4921
|
+
// CLOSER — flattens the selected ICE candidate pair into two
|
|
4922
|
+
// string params (local / remote), and snapshots the running
|
|
4923
|
+
// command counter so phase 5 shows how many commands the client
|
|
4924
|
+
// fired at Unreal before the viewport was ready.
|
|
4925
|
+
{
|
|
4926
|
+
label: 'Viewport ready',
|
|
4927
|
+
event: this.store.select(unrealFeature.selectViewportReady).pipe(filter$1(Boolean), take(1), withLatestFrom$1(this.selectedPair$), map(([, selectedPair]) => ({
|
|
4928
|
+
localCandidate: fmtCandidate(selectedPair?.local),
|
|
4929
|
+
remoteCandidate: fmtCandidate(selectedPair?.remote),
|
|
4930
|
+
commandCount: this.session?.commandCount ?? 0,
|
|
4931
|
+
}))),
|
|
4932
|
+
},
|
|
4933
|
+
],
|
|
4934
|
+
},
|
|
4935
|
+
];
|
|
4936
|
+
if (this.unrealConfig?.playwright)
|
|
4937
|
+
return;
|
|
4938
|
+
if (!this.unrealConfig?.streamTelemetryV2)
|
|
4939
|
+
return;
|
|
4940
|
+
this.init();
|
|
4941
|
+
}
|
|
4942
|
+
getOrCreateUserId() {
|
|
4943
|
+
try {
|
|
4944
|
+
const existing = localStorage.getItem(USER_ID_STORAGE_KEY);
|
|
4945
|
+
if (existing && existing.length > 0)
|
|
4946
|
+
return existing;
|
|
4947
|
+
const fresh = v4();
|
|
4948
|
+
localStorage.setItem(USER_ID_STORAGE_KEY, fresh);
|
|
4949
|
+
return fresh;
|
|
4950
|
+
}
|
|
4951
|
+
catch {
|
|
4952
|
+
// localStorage disabled (private-mode restrictions, quota, etc.)
|
|
4953
|
+
// Fall back to a per-tab ephemeral id so telemetry keeps working.
|
|
4954
|
+
return v4();
|
|
4802
4955
|
}
|
|
4803
|
-
this.setupPeerConnection(this.cfg);
|
|
4804
4956
|
}
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
.
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
.pipe(
|
|
4814
|
-
.subscribe((
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4957
|
+
init() {
|
|
4958
|
+
// Start a new session on every initSignalling dispatch.
|
|
4959
|
+
this.actions$
|
|
4960
|
+
.pipe(ofType(initSignalling))
|
|
4961
|
+
.subscribe(() => this.startSession());
|
|
4962
|
+
// End the current session when Cirrus drops (true → false transition).
|
|
4963
|
+
this.store
|
|
4964
|
+
.select(unrealFeature.selectCirrusConnected)
|
|
4965
|
+
.pipe(pairwise(), filter$1(([prev, curr]) => prev && !curr))
|
|
4966
|
+
.subscribe(() => this.endSession('disconnected'));
|
|
4967
|
+
// Close the session when the tab is hidden / being evicted from bfcache.
|
|
4968
|
+
// `pagehide` fires in more cases than `beforeunload` (covers iOS Safari, bfcache).
|
|
4969
|
+
fromEvent(window, 'pagehide').subscribe(() => this.endSession('pagehide'));
|
|
4970
|
+
}
|
|
4971
|
+
startSession() {
|
|
4972
|
+
// If a previous session is still active, close it first.
|
|
4973
|
+
this.endSession('restart');
|
|
4974
|
+
const id = v4();
|
|
4975
|
+
const startedAt = Date.now();
|
|
4976
|
+
const startedAtPerf = performance.now();
|
|
4977
|
+
const sub = new Subscription();
|
|
4978
|
+
// Anchor selection:
|
|
4979
|
+
// - First session on this page → anchor at navigation start (0).
|
|
4980
|
+
// `performance.now()` is already "ms since navigation start", so
|
|
4981
|
+
// `session_started.duration` becomes the full page-load + idle-time
|
|
4982
|
+
// leading up to the first stream start (i.e. the real bootstrap).
|
|
4983
|
+
// - Resume/restart sessions → anchor at the session's own start.
|
|
4984
|
+
// The document is already loaded — a "bootstrap" phase would either
|
|
4985
|
+
// balloon (to include all prior idle time) or be misleading, so we
|
|
4986
|
+
// suppress it by making the anchor == session_started.
|
|
4987
|
+
const isFirstSession = !this.hasStartedBefore;
|
|
4988
|
+
this.hasStartedBefore = true;
|
|
4989
|
+
const anchorPerf = isFirstSession ? 0 : startedAtPerf;
|
|
4990
|
+
const times = new Map();
|
|
4991
|
+
this.session = {
|
|
4992
|
+
id,
|
|
4993
|
+
startedAt,
|
|
4994
|
+
startedAtPerf,
|
|
4995
|
+
anchorPerf,
|
|
4996
|
+
// Initial milestone anchor = anchorPerf. For the first session this is
|
|
4997
|
+
// navigation start (0), so `session_started.duration` becomes the full
|
|
4998
|
+
// page-load elapsed. For Resume sessions anchor == startedAtPerf, so
|
|
4999
|
+
// `session_started.duration` is 0 (no bootstrap phase).
|
|
5000
|
+
lastMilestonePerf: anchorPerf,
|
|
5001
|
+
seq: 0,
|
|
5002
|
+
sub,
|
|
5003
|
+
qpBad: false,
|
|
5004
|
+
fpsLow: false,
|
|
5005
|
+
maxFpsTarget: UNLIMITED_REFERENCE_FPS,
|
|
5006
|
+
iceCandidateCount: 0,
|
|
5007
|
+
handshakeFlow: null,
|
|
5008
|
+
commandCount: 0,
|
|
5009
|
+
times,
|
|
5010
|
+
};
|
|
5011
|
+
// Side-channel counters for WebRTC negotiation diagnostics — shown
|
|
5012
|
+
// as phase-3 params on the `datachannel_connected` closer. Kept
|
|
5013
|
+
// imperative (session-scoped) so no RxJS hot-stream plumbing is
|
|
5014
|
+
// needed to survive the subscribe-on-close timing.
|
|
5015
|
+
sub.add(this.signallingService.onWebRtcIce$
|
|
5016
|
+
.pipe(filter$1(Boolean))
|
|
5017
|
+
.subscribe(() => {
|
|
5018
|
+
if (this.session)
|
|
5019
|
+
this.session.iceCandidateCount++;
|
|
5020
|
+
}));
|
|
5021
|
+
sub.add(this.actions$.pipe(ofType(answerSent), take(1)).subscribe(() => {
|
|
5022
|
+
if (this.session)
|
|
5023
|
+
this.session.handshakeFlow = 'server-offer';
|
|
5024
|
+
}));
|
|
5025
|
+
sub.add(this.signallingService.onWebRtcAnswer$
|
|
5026
|
+
.pipe(filter$1(Boolean), take(1))
|
|
5027
|
+
.subscribe(() => {
|
|
5028
|
+
if (this.session)
|
|
5029
|
+
this.session.handshakeFlow = 'client-offer';
|
|
5030
|
+
}));
|
|
5031
|
+
// Unreal-command counter — increments on every `commandStarted`
|
|
5032
|
+
// action. Snapshotted into the `viewport_ready` closer payload as
|
|
5033
|
+
// phase-5's `commandCount`.
|
|
5034
|
+
sub.add(this.actions$.pipe(ofType(commandStarted)).subscribe(() => {
|
|
5035
|
+
if (this.session)
|
|
5036
|
+
this.session.commandCount++;
|
|
5037
|
+
}));
|
|
5038
|
+
// Unified catalog loop — one subscription per entry. Non-closers
|
|
5039
|
+
// record their emission time into the session map. The *last* entry
|
|
5040
|
+
// of each phase is the closer: its emission fires a single generic
|
|
5041
|
+
// `stream.phase_ended` event carrying every display field the viewer
|
|
5042
|
+
// needs. The viewer never inspects milestone names or phase kinds;
|
|
5043
|
+
// adding a new phase to `phaseCatalog` is enough — zero viewer work.
|
|
5044
|
+
this.phaseCatalog.forEach((bucket, bucketIdx) => {
|
|
5045
|
+
bucket.milestones.forEach((entry, idx) => {
|
|
5046
|
+
const isCloser = idx === bucket.milestones.length - 1;
|
|
5047
|
+
sub.add(entry.event.subscribe((value) => {
|
|
5048
|
+
if (!this.session)
|
|
5049
|
+
return;
|
|
5050
|
+
if (isCloser) {
|
|
5051
|
+
this.emitPhaseEnded(bucket, bucketIdx, value);
|
|
5052
|
+
return;
|
|
5053
|
+
}
|
|
5054
|
+
const time = Math.round(performance.now() - this.session.anchorPerf);
|
|
5055
|
+
if (entry.mode !== 'last' && this.session.times.has(entry))
|
|
5056
|
+
return;
|
|
5057
|
+
this.session.times.set(entry, time);
|
|
5058
|
+
}));
|
|
5059
|
+
});
|
|
4823
5060
|
});
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
5061
|
+
const bootstrapInfo = this.collectBootstrapInfo();
|
|
5062
|
+
this.emitLifecycle('stream.session_started', {
|
|
5063
|
+
sessionId: id,
|
|
5064
|
+
startTimeUtc: new Date(startedAt).toISOString(),
|
|
5065
|
+
pixelRatio: window.devicePixelRatio,
|
|
5066
|
+
// Browser viewport size at session start (`window.innerWidth` /
|
|
5067
|
+
// `innerHeight`). Named `browserInnerSize` to distinguish from
|
|
5068
|
+
// the physical screen — what gates the rendered video resolution
|
|
5069
|
+
// is the container we started from, not the device hardware.
|
|
5070
|
+
browserInnerSize: {
|
|
5071
|
+
width: window.innerWidth,
|
|
5072
|
+
height: window.innerHeight,
|
|
5073
|
+
},
|
|
5074
|
+
userAgent: navigator.userAgent,
|
|
5075
|
+
sampleWindowMs: SAMPLE_WINDOW_MS,
|
|
5076
|
+
// Full page URL — `location.href` is the only reliable way to get this.
|
|
5077
|
+
// The server-observed `Referer` header is truncated to origin for
|
|
5078
|
+
// cross-origin requests by default Referrer-Policy.
|
|
5079
|
+
pageUrl: window.location.href,
|
|
5080
|
+
// Navigation / page-load context for the "app bootstrap → session_started"
|
|
5081
|
+
// waterfall phase. Only useful for the first session (`bootstrap` phase
|
|
5082
|
+
// is suppressed on Resume), but `bootstrapInfo` describes the document,
|
|
5083
|
+
// not the session — harmless and cheap to send either way.
|
|
5084
|
+
...(bootstrapInfo && { bootstrapInfo }),
|
|
5085
|
+
},
|
|
5086
|
+
// Pin the milestone to the captured `performance.now()` so `duration`
|
|
5087
|
+
// equals "ms since navigation start" precisely (for first sessions),
|
|
5088
|
+
// rather than being inflated by any synchronous setup done in between.
|
|
5089
|
+
startedAtPerf);
|
|
5090
|
+
// Track current maxFps target to evaluate FPS-ratio anomalies.
|
|
5091
|
+
sub.add(this.actions$
|
|
5092
|
+
.pipe(ofType(setMaxFps))
|
|
5093
|
+
.subscribe(({ maxFps }) => this.updateMaxFpsTarget(maxFps)));
|
|
5094
|
+
// Periodic aggregated sample — every 5s window.
|
|
5095
|
+
sub.add(this.videoService.videoStats$
|
|
5096
|
+
.pipe(bufferTime(SAMPLE_WINDOW_MS), filter$1((samples) => samples.length > 0))
|
|
5097
|
+
.subscribe((samples) => this.handleWindow(samples)));
|
|
5098
|
+
}
|
|
5099
|
+
endSession(reason) {
|
|
5100
|
+
if (!this.session)
|
|
5101
|
+
return;
|
|
5102
|
+
const { id, sub } = this.session;
|
|
5103
|
+
this.emitLifecycle('stream.session_ended', {
|
|
5104
|
+
sessionId: id,
|
|
5105
|
+
reason,
|
|
5106
|
+
timestamp: new Date().toISOString(),
|
|
4828
5107
|
});
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
5108
|
+
sub.unsubscribe();
|
|
5109
|
+
this.session = null;
|
|
5110
|
+
}
|
|
5111
|
+
/**
|
|
5112
|
+
* Emit a lifecycle event with a `duration` field — ms between the
|
|
5113
|
+
* previous lifecycle event and now (phase-local, NOT anchor-relative).
|
|
5114
|
+
* For `session_started` this equals the full page-load elapsed on the
|
|
5115
|
+
* first session, or 0 on Resume. Advances `lastMilestonePerf` so the
|
|
5116
|
+
* next event's duration is correctly rooted.
|
|
5117
|
+
*/
|
|
5118
|
+
emitLifecycle(event, extra, atPerf) {
|
|
5119
|
+
if (!this.session)
|
|
5120
|
+
return;
|
|
5121
|
+
// `atPerf` lets callers pin the milestone to a specific `performance.now()`
|
|
5122
|
+
// value — useful for `session_started` where the wall-clock and perf-clock
|
|
5123
|
+
// anchors were already captured at the top of `startSession()`.
|
|
5124
|
+
const now = atPerf ?? performance.now();
|
|
5125
|
+
const duration = Math.round(now - this.session.lastMilestonePerf);
|
|
5126
|
+
this.track(event, { duration, ...extra });
|
|
5127
|
+
this.session.lastMilestonePerf = now;
|
|
5128
|
+
}
|
|
5129
|
+
/**
|
|
5130
|
+
* Emit the unified `stream.phase_ended` wire event for a closer. Packs
|
|
5131
|
+
* every display field the viewer consumes — phaseNo (derived from the
|
|
5132
|
+
* catalog index), label, color, milestones, params, overlays. Boundary
|
|
5133
|
+
* labels aren't on the wire — the viewer derives them from neighbour
|
|
5134
|
+
* phases' `label` fields.
|
|
5135
|
+
*/
|
|
5136
|
+
emitPhaseEnded(bucket, bucketIdx, extrasValue) {
|
|
5137
|
+
if (!this.session)
|
|
5138
|
+
return;
|
|
5139
|
+
const now = performance.now();
|
|
5140
|
+
const phaseStartMs = Math.round(this.session.lastMilestonePerf - this.session.anchorPerf);
|
|
5141
|
+
const phaseEndMs = Math.round(now - this.session.anchorPerf);
|
|
5142
|
+
const milestones = this.milestonesOfBucket(bucket);
|
|
5143
|
+
const params = toPhaseParams(extrasValue);
|
|
5144
|
+
const overlays = resolveOverlays(bucket.overlays, this.session.times, phaseStartMs, phaseEndMs);
|
|
5145
|
+
this.emitLifecycle('stream.phase_ended', {
|
|
5146
|
+
sessionId: this.session.id,
|
|
5147
|
+
phaseNo: bucketIdx + 1,
|
|
5148
|
+
label: bucket.label,
|
|
5149
|
+
color: bucket.color,
|
|
5150
|
+
milestones,
|
|
5151
|
+
params,
|
|
5152
|
+
overlays,
|
|
4838
5153
|
});
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
5154
|
+
}
|
|
5155
|
+
handleWindow(samples) {
|
|
5156
|
+
if (!this.session)
|
|
5157
|
+
return;
|
|
5158
|
+
const s = this.session;
|
|
5159
|
+
const bitrate = this.aggregate(samples.map((x) => x.aggregatedStats.bitrate ?? 0));
|
|
5160
|
+
const fps = this.aggregate(samples.map((x) => x.aggregatedStats.framesPerSecond ?? 0));
|
|
5161
|
+
const qp = this.aggregate(samples.map((x) => x.aggregatedStats.VideoEncoderQP ?? 0));
|
|
5162
|
+
const jitter = this.aggregate(samples.map((x) => x.aggregatedStats.jitter ?? 0));
|
|
5163
|
+
const rtt = this.aggregate(samples.map((x) => x.aggregatedStats.currentRoundTripTime ?? 0));
|
|
5164
|
+
const latest = samples[samples.length - 1]?.aggregatedStats;
|
|
5165
|
+
// Periodic sample event.
|
|
5166
|
+
this.track('stream.sample', {
|
|
5167
|
+
sessionId: s.id,
|
|
5168
|
+
seq: s.seq++,
|
|
5169
|
+
timestamp: new Date().toISOString(),
|
|
5170
|
+
sampleCount: samples.length,
|
|
5171
|
+
// Tab visibility state at emission time. Chrome does NOT throttle timers
|
|
5172
|
+
// for WebRTC-active tabs, so the handler runs on time even when hidden —
|
|
5173
|
+
// the point-in-time read is reliable in this specific context.
|
|
5174
|
+
visibilityState: document.visibilityState,
|
|
5175
|
+
bitrate,
|
|
5176
|
+
fps,
|
|
5177
|
+
qp,
|
|
5178
|
+
jitter,
|
|
5179
|
+
rtt,
|
|
5180
|
+
packetsLost: latest?.packetsLost,
|
|
5181
|
+
viewPort: {
|
|
5182
|
+
width: latest?.frameWidth,
|
|
5183
|
+
height: latest?.frameHeight,
|
|
5184
|
+
},
|
|
4854
5185
|
});
|
|
5186
|
+
this.evaluateAnomalies(fps, qp);
|
|
4855
5187
|
}
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
//
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
latency: 0,
|
|
4871
|
-
noiseSuppression: false,
|
|
4872
|
-
sampleRate: 48000,
|
|
4873
|
-
sampleSize: 16,
|
|
4874
|
-
volume: 1.0,
|
|
4875
|
-
}
|
|
4876
|
-
: false;
|
|
4877
|
-
// Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
|
|
4878
|
-
const stream = await navigator.mediaDevices.getUserMedia({
|
|
4879
|
-
video: false,
|
|
4880
|
-
audio: audioSendOptions,
|
|
5188
|
+
evaluateAnomalies(fps, qp) {
|
|
5189
|
+
if (!this.session)
|
|
5190
|
+
return;
|
|
5191
|
+
const s = this.session;
|
|
5192
|
+
// Quality (QP red) entry/exit.
|
|
5193
|
+
const qpBadNow = qp.p50 > QP_RED_THRESHOLD;
|
|
5194
|
+
if (qpBadNow !== s.qpBad) {
|
|
5195
|
+
s.qpBad = qpBadNow;
|
|
5196
|
+
this.track(qpBadNow ? 'stream.quality_degraded' : 'stream.quality_recovered', {
|
|
5197
|
+
sessionId: s.id,
|
|
5198
|
+
seq: s.seq,
|
|
5199
|
+
timestamp: new Date().toISOString(),
|
|
5200
|
+
qpP50: qp.p50,
|
|
5201
|
+
qpP95: qp.p95,
|
|
4881
5202
|
});
|
|
4882
|
-
if (stream) {
|
|
4883
|
-
if (hasTransceivers) {
|
|
4884
|
-
for (const transceiver of pc.getTransceivers()) {
|
|
4885
|
-
if (transceiver &&
|
|
4886
|
-
transceiver.receiver &&
|
|
4887
|
-
transceiver.receiver.track &&
|
|
4888
|
-
transceiver.receiver.track.kind === 'audio') {
|
|
4889
|
-
for (const track of stream.getTracks()) {
|
|
4890
|
-
if (track.kind && track.kind === 'audio') {
|
|
4891
|
-
await transceiver.sender.replaceTrack(track);
|
|
4892
|
-
transceiver.direction = 'sendrecv';
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
}
|
|
4896
|
-
}
|
|
4897
|
-
}
|
|
4898
|
-
else {
|
|
4899
|
-
for (const track of stream.getTracks()) {
|
|
4900
|
-
if (track.kind && track.kind === 'audio') {
|
|
4901
|
-
pc.addTransceiver(track, { direction: 'sendrecv' });
|
|
4902
|
-
}
|
|
4903
|
-
}
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
else {
|
|
4907
|
-
pc.addTransceiver('audio', { direction: 'recvonly' });
|
|
4908
|
-
}
|
|
4909
5203
|
}
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
this.
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
.
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
.
|
|
4922
|
-
Logger.warn(`[DATACHANNEL] Data channel disconnected: ${datachannel.label}(${datachannel.id})`, e);
|
|
4923
|
-
this.store.dispatch(disconnectStream({
|
|
4924
|
-
reason: DisconnectReason.DataChannelClosed,
|
|
4925
|
-
message: 'DataChannelClosed',
|
|
4926
|
-
}));
|
|
4927
|
-
});
|
|
4928
|
-
fromEvent(datachannel, 'error')
|
|
4929
|
-
.pipe(takeUntil(this.disconnect$))
|
|
4930
|
-
.subscribe((e) => {
|
|
4931
|
-
const message = `[DATACHANNEL] Data channel "${datachannel.label}" error. id:${datachannel.id}`;
|
|
4932
|
-
Logger.error(message, e);
|
|
4933
|
-
});
|
|
4934
|
-
fromEvent(datachannel, 'open')
|
|
4935
|
-
.pipe(tap$1(() => {
|
|
4936
|
-
Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
|
|
4937
|
-
}), takeUntil(this.disconnect$))
|
|
4938
|
-
.subscribe(() => {
|
|
4939
|
-
this.commandTelemetryService.trackStopCommand('EXT-DataChannel Connection');
|
|
4940
|
-
this.onDataChannelConnected();
|
|
5204
|
+
// FPS low vs current target.
|
|
5205
|
+
const ref = s.maxFpsTarget > 0 ? s.maxFpsTarget : UNLIMITED_REFERENCE_FPS;
|
|
5206
|
+
const fpsLowNow = fps.p50 < ref * FPS_LOW_RATIO;
|
|
5207
|
+
if (fpsLowNow && !s.fpsLow) {
|
|
5208
|
+
this.track('stream.fps_low', {
|
|
5209
|
+
sessionId: s.id,
|
|
5210
|
+
seq: s.seq,
|
|
5211
|
+
timestamp: new Date().toISOString(),
|
|
5212
|
+
target: s.maxFpsTarget,
|
|
5213
|
+
referenceTarget: ref,
|
|
5214
|
+
fpsP50: fps.p50,
|
|
5215
|
+
fpsP95: fps.p95,
|
|
4941
5216
|
});
|
|
4942
|
-
fromEvent(datachannel, 'message')
|
|
4943
|
-
.pipe(
|
|
4944
|
-
/* tap((e: MessageEvent) => {
|
|
4945
|
-
const view = new Uint8Array(e.data);
|
|
4946
|
-
Logger.info(
|
|
4947
|
-
`[DATACHANNEL] Message received on "${datachannel.label}" id=${datachannel.id}, byteCode=${view[0]}, size=${view.byteLength}`,
|
|
4948
|
-
);
|
|
4949
|
-
}),*/
|
|
4950
|
-
map$1((e) => e.data), takeUntil(this.disconnect$))
|
|
4951
|
-
.subscribe((data) => this.onDataChannelMessage$.next(data));
|
|
4952
|
-
return datachannel;
|
|
4953
|
-
}
|
|
4954
|
-
catch (e) {
|
|
4955
|
-
Logger.warn('[DATACHANNEL] Datachannel setup caused an exception: ', e);
|
|
4956
|
-
return null;
|
|
4957
5217
|
}
|
|
5218
|
+
s.fpsLow = fpsLowNow;
|
|
4958
5219
|
}
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
statusMessage +=
|
|
4963
|
-
'\n If you are experiencing connection problems please try Google Chrome';
|
|
4964
|
-
}
|
|
4965
|
-
this.store.dispatch(dataChannelConnected({ statusMessage }));
|
|
5220
|
+
updateMaxFpsTarget(maxFps) {
|
|
5221
|
+
if (this.session)
|
|
5222
|
+
this.session.maxFpsTarget = maxFps;
|
|
4966
5223
|
}
|
|
4967
5224
|
/**
|
|
4968
|
-
*
|
|
4969
|
-
*
|
|
4970
|
-
*
|
|
4971
|
-
* offerToReceive* options are not used.
|
|
5225
|
+
* Snapshot every milestone recorded in this session that belongs to
|
|
5226
|
+
* {@link phase}. Used to attach a phase's milestone array to the
|
|
5227
|
+
* lifecycle event that closes that phase.
|
|
4972
5228
|
*/
|
|
4973
|
-
|
|
5229
|
+
/**
|
|
5230
|
+
* Collect wire-shape milestones that fired within a bucket. Walks the
|
|
5231
|
+
* catalog's own milestone list (minus the closer) and pulls each
|
|
5232
|
+
* entry's time from the in-session map via object identity.
|
|
5233
|
+
*/
|
|
5234
|
+
milestonesOfBucket(bucket) {
|
|
5235
|
+
if (!this.session)
|
|
5236
|
+
return [];
|
|
5237
|
+
const out = [];
|
|
5238
|
+
const closerIdx = bucket.milestones.length - 1;
|
|
5239
|
+
for (let i = 0; i < closerIdx; i++) {
|
|
5240
|
+
const entry = bucket.milestones[i];
|
|
5241
|
+
const time = this.session.times.get(entry);
|
|
5242
|
+
if (time !== undefined)
|
|
5243
|
+
out.push({ label: entry.label, time });
|
|
5244
|
+
}
|
|
5245
|
+
return out;
|
|
5246
|
+
}
|
|
5247
|
+
/**
|
|
5248
|
+
* Capture browser navigation timings — useful to explain the
|
|
5249
|
+
* "application bootstrap → session_started" waterfall phase.
|
|
5250
|
+
* Values are ms from navigation start; null if the entry is unavailable
|
|
5251
|
+
* (old browsers, cross-origin iframes with limited perf access, etc.).
|
|
5252
|
+
*/
|
|
5253
|
+
collectBootstrapInfo() {
|
|
4974
5254
|
try {
|
|
4975
|
-
const
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
5255
|
+
const nav = performance.getEntriesByType('navigation')[0];
|
|
5256
|
+
if (!nav)
|
|
5257
|
+
return null;
|
|
5258
|
+
return {
|
|
5259
|
+
type: nav.type, // 'navigate' | 'reload' | 'back_forward' | 'prerender'
|
|
5260
|
+
protocol: nav.nextHopProtocol || null, // 'h2' / 'h3' / 'http/1.1'
|
|
5261
|
+
firstByteMs: Math.round(nav.responseStart),
|
|
5262
|
+
domInteractiveMs: Math.round(nav.domInteractive),
|
|
5263
|
+
domContentLoadedMs: Math.round(nav.domContentLoadedEventEnd),
|
|
5264
|
+
loadEventMs: Math.round(nav.loadEventEnd),
|
|
5265
|
+
// Navigation-only size — the HTML document itself. Usually small;
|
|
5266
|
+
// the interesting number is `resources.transferSize` below.
|
|
5267
|
+
navigationTransferSize: nav.transferSize || null,
|
|
5268
|
+
navigationEncodedBodySize: nav.encodedBodySize || null,
|
|
5269
|
+
navigationDecodedBodySize: nav.decodedBodySize || null,
|
|
5270
|
+
resources: this.collectResourceTotals(),
|
|
5271
|
+
};
|
|
4979
5272
|
}
|
|
4980
5273
|
catch {
|
|
4981
|
-
|
|
5274
|
+
return null;
|
|
4982
5275
|
}
|
|
4983
5276
|
}
|
|
4984
5277
|
/**
|
|
4985
|
-
*
|
|
4986
|
-
*
|
|
5278
|
+
* Aggregate all resources the browser has loaded so far
|
|
5279
|
+
* (`performance.getEntriesByType('resource')`), grouped by `initiatorType`.
|
|
5280
|
+
* Gives a real "bytes downloaded before session start" figure — unlike the
|
|
5281
|
+
* navigation entry which only covers the HTML document.
|
|
5282
|
+
*
|
|
5283
|
+
* `transferSize === 0 && decodedBodySize > 0` means the resource came from
|
|
5284
|
+
* the HTTP cache — counted separately so cached hits don't inflate
|
|
5285
|
+
* the "bytes on the wire" metric.
|
|
4987
5286
|
*/
|
|
4988
|
-
|
|
5287
|
+
collectResourceTotals() {
|
|
4989
5288
|
try {
|
|
4990
|
-
const
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5289
|
+
const list = performance.getEntriesByType('resource');
|
|
5290
|
+
if (!list.length)
|
|
5291
|
+
return null;
|
|
5292
|
+
const empty = () => ({
|
|
5293
|
+
count: 0,
|
|
5294
|
+
cachedCount: 0,
|
|
5295
|
+
transferSize: 0,
|
|
5296
|
+
encodedBodySize: 0,
|
|
5297
|
+
decodedBodySize: 0,
|
|
5298
|
+
});
|
|
5299
|
+
const totals = empty();
|
|
5300
|
+
const byType = {};
|
|
5301
|
+
for (const r of list) {
|
|
5302
|
+
const type = r.initiatorType || 'other';
|
|
5303
|
+
const b = (byType[type] ||= empty());
|
|
5304
|
+
const fromCache = r.transferSize === 0 && r.decodedBodySize > 0;
|
|
5305
|
+
const t = r.transferSize || 0;
|
|
5306
|
+
const enc = r.encodedBodySize || 0;
|
|
5307
|
+
const dec = r.decodedBodySize || 0;
|
|
5308
|
+
b.count++;
|
|
5309
|
+
if (fromCache)
|
|
5310
|
+
b.cachedCount++;
|
|
5311
|
+
b.transferSize += t;
|
|
5312
|
+
b.encodedBodySize += enc;
|
|
5313
|
+
b.decodedBodySize += dec;
|
|
5314
|
+
totals.count++;
|
|
5315
|
+
if (fromCache)
|
|
5316
|
+
totals.cachedCount++;
|
|
5317
|
+
totals.transferSize += t;
|
|
5318
|
+
totals.encodedBodySize += enc;
|
|
5319
|
+
totals.decodedBodySize += dec;
|
|
5320
|
+
}
|
|
5321
|
+
return { ...totals, byType };
|
|
4994
5322
|
}
|
|
4995
|
-
catch
|
|
4996
|
-
|
|
5323
|
+
catch {
|
|
5324
|
+
return null;
|
|
4997
5325
|
}
|
|
4998
5326
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5327
|
+
aggregate(values) {
|
|
5328
|
+
if (values.length === 0)
|
|
5329
|
+
return { p50: 0, p95: 0, min: 0, max: 0 };
|
|
5330
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
5331
|
+
// Nearest-rank method: p-th percentile ≡ sorted[ceil(p/100 * n) - 1].
|
|
5332
|
+
// Using `floor` would make p95 collapse to `max` for n=20
|
|
5333
|
+
// (floor(0.95*20) = 19 = max index), hiding the max line behind p95
|
|
5334
|
+
// in charts. `ceil - 1` keeps them distinct: p95 = sorted[18], max = sorted[19].
|
|
5335
|
+
const pick = (p) => {
|
|
5336
|
+
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
5337
|
+
return sorted[Math.max(0, Math.min(sorted.length - 1, idx))];
|
|
5338
|
+
};
|
|
5339
|
+
return {
|
|
5340
|
+
p50: pick(50),
|
|
5341
|
+
p95: pick(95),
|
|
5342
|
+
min: sorted[0],
|
|
5343
|
+
max: sorted[sorted.length - 1],
|
|
5344
|
+
};
|
|
5345
|
+
}
|
|
5346
|
+
track(event, data) {
|
|
5347
|
+
// `userId` is attached to every event so the server can bucket files
|
|
5348
|
+
// under `data/<userId>/<sessionId>.jsonl` without maintaining a
|
|
5349
|
+
// session→user index.
|
|
5350
|
+
this.store.dispatch(trackTelemetryData({
|
|
5351
|
+
event,
|
|
5352
|
+
data: { userId: this.userId, ...data },
|
|
5353
|
+
}));
|
|
5354
|
+
}
|
|
5355
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService2, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5356
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService2 }); }
|
|
5001
5357
|
}
|
|
5002
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5358
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StreamStatusTelemetryService2, decorators: [{
|
|
5003
5359
|
type: Injectable
|
|
5004
5360
|
}], ctorParameters: () => [] });
|
|
5361
|
+
// ──────────────────────────────────────────────────────────────────
|
|
5362
|
+
// Module-scope helpers — keep the class body focused on RxJS wiring.
|
|
5363
|
+
// ──────────────────────────────────────────────────────────────────
|
|
5364
|
+
/** Compact one side of an ICE candidate pair to `"type (url)"` or just
|
|
5365
|
+
* `type` (host candidates have no URL). */
|
|
5366
|
+
function fmtCandidate(c) {
|
|
5367
|
+
if (!c)
|
|
5368
|
+
return null;
|
|
5369
|
+
return c.url ? `${c.type} (${c.url})` : c.type;
|
|
5370
|
+
}
|
|
5371
|
+
/**
|
|
5372
|
+
* Flatten the closer's emitted extras object into display key/value
|
|
5373
|
+
* pairs. Null/undefined/empty are dropped; objects and arrays are
|
|
5374
|
+
* skipped — they should be pre-formatted by the closer's `map(...)`.
|
|
5375
|
+
*/
|
|
5376
|
+
function toPhaseParams(value) {
|
|
5377
|
+
if (!value || typeof value !== 'object')
|
|
5378
|
+
return [];
|
|
5379
|
+
const out = [];
|
|
5380
|
+
for (const [key, v] of Object.entries(value)) {
|
|
5381
|
+
if (v === null || v === undefined || v === '')
|
|
5382
|
+
continue;
|
|
5383
|
+
if (typeof v === 'object')
|
|
5384
|
+
continue;
|
|
5385
|
+
out.push({ key, value: String(v) });
|
|
5386
|
+
}
|
|
5387
|
+
return out;
|
|
5388
|
+
}
|
|
5389
|
+
/**
|
|
5390
|
+
* Resolve declarative overlay recipes to absolute ms spans. Endpoints
|
|
5391
|
+
* fall back to the phase's own start/end when unspecified; recipes
|
|
5392
|
+
* whose referenced entry never fired are quietly skipped.
|
|
5393
|
+
*/
|
|
5394
|
+
function resolveOverlays(recipes, times, phaseStartMs, phaseEndMs) {
|
|
5395
|
+
if (!recipes?.length)
|
|
5396
|
+
return [];
|
|
5397
|
+
const out = [];
|
|
5398
|
+
for (const r of recipes) {
|
|
5399
|
+
const fromMs = r.from ? (times.get(r.from) ?? null) : phaseStartMs;
|
|
5400
|
+
const toMs = r.to ? (times.get(r.to) ?? null) : phaseEndMs;
|
|
5401
|
+
if (fromMs === null || toMs === null || toMs <= fromMs)
|
|
5402
|
+
continue;
|
|
5403
|
+
out.push({ fromMs, toMs, title: r.title });
|
|
5404
|
+
}
|
|
5405
|
+
return out;
|
|
5406
|
+
}
|
|
5005
5407
|
|
|
5006
5408
|
const ReceivedMimeTypes = {
|
|
5007
5409
|
ApplicationJson: 'application/json',
|
|
@@ -5035,10 +5437,10 @@ class FileHandlerService {
|
|
|
5035
5437
|
setTimeout(() => sender(out), 0);
|
|
5036
5438
|
return observable;
|
|
5037
5439
|
}
|
|
5038
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5039
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5440
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileHandlerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5441
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileHandlerService }); }
|
|
5040
5442
|
}
|
|
5041
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5443
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FileHandlerService, decorators: [{
|
|
5042
5444
|
type: Injectable
|
|
5043
5445
|
}] });
|
|
5044
5446
|
|
|
@@ -5127,10 +5529,10 @@ class AnalyticsService extends SubService {
|
|
|
5127
5529
|
});
|
|
5128
5530
|
});
|
|
5129
5531
|
}
|
|
5130
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5131
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5532
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AnalyticsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5533
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AnalyticsService }); }
|
|
5132
5534
|
}
|
|
5133
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5535
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AnalyticsService, decorators: [{
|
|
5134
5536
|
type: Injectable
|
|
5135
5537
|
}], ctorParameters: () => [] });
|
|
5136
5538
|
|
|
@@ -5497,10 +5899,10 @@ class FpsMonitorService {
|
|
|
5497
5899
|
}
|
|
5498
5900
|
return { fps: fps / len, bitrate: bitrate / len, qp: qp / len };
|
|
5499
5901
|
}
|
|
5500
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5501
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5902
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FpsMonitorService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5903
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FpsMonitorService }); }
|
|
5502
5904
|
}
|
|
5503
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5905
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FpsMonitorService, decorators: [{
|
|
5504
5906
|
type: Injectable
|
|
5505
5907
|
}], ctorParameters: () => [] });
|
|
5506
5908
|
|
|
@@ -5571,10 +5973,10 @@ class UnrealCallbackService {
|
|
|
5571
5973
|
setTimeout(() => sender({ ...data, correlationId }), 0);
|
|
5572
5974
|
return observable;
|
|
5573
5975
|
}
|
|
5574
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5575
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.
|
|
5976
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCallbackService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5977
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCallbackService }); }
|
|
5576
5978
|
}
|
|
5577
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5979
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealCallbackService, decorators: [{
|
|
5578
5980
|
type: Injectable
|
|
5579
5981
|
}] });
|
|
5580
5982
|
|
|
@@ -5590,10 +5992,10 @@ class AfkTimeoutModalComponent {
|
|
|
5590
5992
|
resetAfk() {
|
|
5591
5993
|
this.#afkService.reset();
|
|
5592
5994
|
}
|
|
5593
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5594
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
5995
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AfkTimeoutModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
5996
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: AfkTimeoutModalComponent, isStandalone: true, selector: "app-afk-timeout-modal", ngImport: i0, template: "@if (isAfkTimerVisible()) {\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 }); }
|
|
5595
5997
|
}
|
|
5596
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
5998
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AfkTimeoutModalComponent, decorators: [{
|
|
5597
5999
|
type: Component,
|
|
5598
6000
|
args: [{ selector: 'app-afk-timeout-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceButtonComponent], template: "@if (isAfkTimerVisible()) {\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"] }]
|
|
5599
6001
|
}] });
|
|
@@ -5605,10 +6007,10 @@ class FreezeFrameComponent {
|
|
|
5605
6007
|
this.combinedFreeze = this.#store.selectSignal(selectFreezeFrameCombinedDataUrl);
|
|
5606
6008
|
}
|
|
5607
6009
|
#store;
|
|
5608
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5609
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6010
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FreezeFrameComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6011
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", 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 }); }
|
|
5610
6012
|
}
|
|
5611
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6013
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FreezeFrameComponent, decorators: [{
|
|
5612
6014
|
type: Component,
|
|
5613
6015
|
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"] }]
|
|
5614
6016
|
}] });
|
|
@@ -5636,10 +6038,10 @@ class SafePipe {
|
|
|
5636
6038
|
throw new Error(`Invalid safe type specified: ${type}`);
|
|
5637
6039
|
}
|
|
5638
6040
|
}
|
|
5639
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5640
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.
|
|
6041
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SafePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
6042
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: SafePipe, isStandalone: true, name: "safe" }); }
|
|
5641
6043
|
}
|
|
5642
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6044
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: SafePipe, decorators: [{
|
|
5643
6045
|
type: Pipe,
|
|
5644
6046
|
args: [{
|
|
5645
6047
|
name: 'safe',
|
|
@@ -5651,10 +6053,10 @@ class LowBandwidthModalComponent {
|
|
|
5651
6053
|
close(value) {
|
|
5652
6054
|
this.#dialogRef.close(value || false);
|
|
5653
6055
|
}
|
|
5654
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5655
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.
|
|
6056
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LowBandwidthModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6057
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", type: LowBandwidthModalComponent, isStandalone: true, selector: "app-low-bandwidth-modal", ngImport: i0, template: "<div [attr.data-testid]=\"'low-bandwidth'\" class=\"src-modal src-modal--lbm\">\r\n <header class=\"src-modal__header\">\r\n <h6 [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\r\n Unstable Connection\r\n </h6>\r\n </header>\r\n <section class=\"src-modal__body\">\r\n <div\r\n [innerHtml]=\"\r\n 'Fluid Interactivity Modes were disabled due to an unstable connection. Showcase Gallery Mode is enabled. To regain full functionality, switch to Interactive Mode.'\r\n | safe: 'html'\r\n \"\r\n class=\"src-modal__scroll-box\"\r\n ></div>\r\n </section>\r\n <footer class=\"src-modal__footer\">\r\n <div class=\"src-modal__buttons\">\r\n <src-button\r\n (onClick)=\"close(true)\"\r\n [data-testid]=\"'switch-to-interactive-mode'\"\r\n >\r\n Switch\r\n </src-button>\r\n\r\n <src-button\r\n (onClick)=\"close()\"\r\n [data-testid]=\"'close-lbm-modal'\"\r\n weight=\"primary\"\r\n >\r\n Ok\r\n </src-button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [".src-modal--lbm{width:360px}.src-modal--lbm .src-modal__body{font-size:14px;line-height:24px}.src-modal--lbm .src-modal__buttons{display:flex;gap: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"] }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5656
6058
|
}
|
|
5657
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6059
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LowBandwidthModalComponent, decorators: [{
|
|
5658
6060
|
type: Component,
|
|
5659
6061
|
args: [{ selector: 'app-low-bandwidth-modal', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SafePipe, SourceButtonComponent], template: "<div [attr.data-testid]=\"'low-bandwidth'\" class=\"src-modal src-modal--lbm\">\r\n <header class=\"src-modal__header\">\r\n <h6 [attr.data-testid]=\"'title'\" class=\"src-modal__title\">\r\n Unstable Connection\r\n </h6>\r\n </header>\r\n <section class=\"src-modal__body\">\r\n <div\r\n [innerHtml]=\"\r\n 'Fluid Interactivity Modes were disabled due to an unstable connection. Showcase Gallery Mode is enabled. To regain full functionality, switch to Interactive Mode.'\r\n | safe: 'html'\r\n \"\r\n class=\"src-modal__scroll-box\"\r\n ></div>\r\n </section>\r\n <footer class=\"src-modal__footer\">\r\n <div class=\"src-modal__buttons\">\r\n <src-button\r\n (onClick)=\"close(true)\"\r\n [data-testid]=\"'switch-to-interactive-mode'\"\r\n >\r\n Switch\r\n </src-button>\r\n\r\n <src-button\r\n (onClick)=\"close()\"\r\n [data-testid]=\"'close-lbm-modal'\"\r\n weight=\"primary\"\r\n >\r\n Ok\r\n </src-button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [".src-modal--lbm{width:360px}.src-modal--lbm .src-modal__body{font-size:14px;line-height:24px}.src-modal--lbm .src-modal__buttons{display:flex;gap:8px}\n"] }]
|
|
5660
6062
|
}] });
|
|
@@ -5666,10 +6068,10 @@ class ImageLoadingSrcComponent {
|
|
|
5666
6068
|
this.imageLoadingSrc = this.#store.selectSignal(unrealFeature.selectImageLoadingSrc);
|
|
5667
6069
|
}
|
|
5668
6070
|
#store;
|
|
5669
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5670
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6071
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ImageLoadingSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6072
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", 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 [ngSrc]=\"image\" fill loading=\"lazy\" alt=\"image loading src\" />\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 }); }
|
|
5671
6073
|
}
|
|
5672
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6074
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ImageLoadingSrcComponent, decorators: [{
|
|
5673
6075
|
type: Component,
|
|
5674
6076
|
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 [ngSrc]=\"image\" fill loading=\"lazy\" alt=\"image loading src\" />\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"] }]
|
|
5675
6077
|
}] });
|
|
@@ -5745,10 +6147,10 @@ class StatGraphComponent {
|
|
|
5745
6147
|
}
|
|
5746
6148
|
this.draw();
|
|
5747
6149
|
}
|
|
5748
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5749
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.
|
|
6150
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StatGraphComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6151
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.9", 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 }); }
|
|
5750
6152
|
}
|
|
5751
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6153
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: StatGraphComponent, decorators: [{
|
|
5752
6154
|
type: Component,
|
|
5753
6155
|
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"] }]
|
|
5754
6156
|
}], 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: [{
|
|
@@ -5829,10 +6231,10 @@ class UnrealStatusComponent {
|
|
|
5829
6231
|
// Fails silently if the iframe's content window cannot be accessed
|
|
5830
6232
|
}
|
|
5831
6233
|
}
|
|
5832
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5833
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6234
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealStatusComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6235
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: UnrealStatusComponent, isStandalone: true, selector: "app-unreal-status", ngImport: i0, template: "@if (showLoader()) {\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 {{ messagePercents() }}%\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 }); }
|
|
5834
6236
|
}
|
|
5835
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6237
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealStatusComponent, decorators: [{
|
|
5836
6238
|
type: Component,
|
|
5837
6239
|
args: [{ selector: 'app-unreal-status', changeDetection: ChangeDetectionStrategy.OnPush, imports: [SourceLoadingComponent], template: "@if (showLoader()) {\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 {{ messagePercents() }}%\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"] }]
|
|
5838
6240
|
}] });
|
|
@@ -5961,10 +6363,10 @@ class VideoStatsComponent {
|
|
|
5961
6363
|
elements: this.elementsToShow,
|
|
5962
6364
|
}));
|
|
5963
6365
|
}
|
|
5964
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
5965
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6366
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VideoStatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6367
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", 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$ | async; track graph) {\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 | async)!\"\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]=\"ssInfo() | 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: AsyncPipe, name: "async" }, { kind: "pipe", type: JsonPipe, name: "json" }, { kind: "pipe", type: SafePipe, name: "safe" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
5966
6368
|
}
|
|
5967
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6369
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: VideoStatsComponent, decorators: [{
|
|
5968
6370
|
type: Component,
|
|
5969
6371
|
args: [{ selector: 'app-video-stats', changeDetection: ChangeDetectionStrategy.OnPush, imports: [StatGraphComponent, AsyncPipe, 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$ | async; track graph) {\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 | async)!\"\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]=\"ssInfo() | 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"] }]
|
|
5970
6372
|
}], ctorParameters: () => [] });
|
|
@@ -6008,10 +6410,10 @@ class ResumeStreamButtonComponent {
|
|
|
6008
6410
|
onDisconnect() {
|
|
6009
6411
|
this.isSecondStart.set(true);
|
|
6010
6412
|
}
|
|
6011
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6012
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6413
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResumeStreamButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6414
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", 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 has been paused due to 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 }); }
|
|
6013
6415
|
}
|
|
6014
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6416
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ResumeStreamButtonComponent, decorators: [{
|
|
6015
6417
|
type: Component,
|
|
6016
6418
|
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 has been paused due to 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"] }]
|
|
6017
6419
|
}] });
|
|
@@ -6070,10 +6472,10 @@ class IntroSrcComponent {
|
|
|
6070
6472
|
videoElement.innerHTML += 'Browser not supported';
|
|
6071
6473
|
videoContainer.appendChild(videoElement);
|
|
6072
6474
|
}
|
|
6073
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6074
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6475
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IntroSrcComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6476
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", 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 [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 }); }
|
|
6075
6477
|
}
|
|
6076
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6478
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: IntroSrcComponent, decorators: [{
|
|
6077
6479
|
type: Component,
|
|
6078
6480
|
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 [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"] }]
|
|
6079
6481
|
}], ctorParameters: () => [], propDecorators: { videoElement: [{ type: i0.ViewChild, args: ['videoPlayerCover', { isSignal: true }] }] } });
|
|
@@ -6096,7 +6498,6 @@ class UnrealSceneComponent {
|
|
|
6096
6498
|
this.changeMouseOverScene.emit(false);
|
|
6097
6499
|
}
|
|
6098
6500
|
constructor() {
|
|
6099
|
-
this.unrealConfig = inject(UNREAL_CONFIG);
|
|
6100
6501
|
this.isStudio = input(false, ...(ngDevMode ? [{ debugName: "isStudio" }] : /* istanbul ignore next */ []));
|
|
6101
6502
|
this.useContainerAsSizeProvider = input(true, ...(ngDevMode ? [{ debugName: "useContainerAsSizeProvider" }] : /* istanbul ignore next */ []));
|
|
6102
6503
|
this.studioResolutionSize = input({
|
|
@@ -6199,16 +6600,7 @@ class UnrealSceneComponent {
|
|
|
6199
6600
|
containerHeight = this.videoElement()?.nativeElement.offsetHeight || 2;
|
|
6200
6601
|
}
|
|
6201
6602
|
const pixelRatio = this.pixelRatio;
|
|
6202
|
-
|
|
6203
|
-
//New Approach
|
|
6204
|
-
return getResizeValuesUniversal({
|
|
6205
|
-
pixelRatio,
|
|
6206
|
-
containerWidth,
|
|
6207
|
-
containerHeight,
|
|
6208
|
-
maxResolution: this.maxResolution,
|
|
6209
|
-
});
|
|
6210
|
-
}
|
|
6211
|
-
return getResizeValuesUniversalOld({
|
|
6603
|
+
return getResizeValuesUniversal({
|
|
6212
6604
|
pixelRatio,
|
|
6213
6605
|
containerWidth,
|
|
6214
6606
|
containerHeight,
|
|
@@ -6216,20 +6608,11 @@ class UnrealSceneComponent {
|
|
|
6216
6608
|
});
|
|
6217
6609
|
}
|
|
6218
6610
|
getResizeStudioValues() {
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
pixelRatio: this.pixelRatio,
|
|
6222
|
-
containerWidth: this.videoElement()?.nativeElement.offsetWidth || 2,
|
|
6223
|
-
containerHeight: this.videoElement()?.nativeElement.offsetHeight || 2,
|
|
6224
|
-
studioWidth: this.width,
|
|
6225
|
-
studioHeight: this.height,
|
|
6226
|
-
maxResolution: this.maxResolution,
|
|
6227
|
-
});
|
|
6228
|
-
}
|
|
6229
|
-
return getResizeStudioValuesOld({
|
|
6611
|
+
const videoElement = this.videoElement()?.nativeElement;
|
|
6612
|
+
return getResizeStudioValues({
|
|
6230
6613
|
pixelRatio: this.pixelRatio,
|
|
6231
|
-
containerWidth:
|
|
6232
|
-
containerHeight:
|
|
6614
|
+
containerWidth: videoElement?.offsetWidth || 2,
|
|
6615
|
+
containerHeight: videoElement?.offsetHeight || 2,
|
|
6233
6616
|
studioWidth: this.width,
|
|
6234
6617
|
studioHeight: this.height,
|
|
6235
6618
|
maxResolution: this.maxResolution,
|
|
@@ -6238,13 +6621,18 @@ class UnrealSceneComponent {
|
|
|
6238
6621
|
sendResize(size) {
|
|
6239
6622
|
this.commandsSender.sendCommandToUnreal({
|
|
6240
6623
|
command: MetaBoxCommand.FChangeResolutionCommand,
|
|
6241
|
-
payload: {
|
|
6624
|
+
payload: {
|
|
6625
|
+
resolution: { x: size.w, y: size.h },
|
|
6626
|
+
payloadVersion: this.isStudio()
|
|
6627
|
+
? EPayloadVersion.disableFovRecalculation
|
|
6628
|
+
: undefined,
|
|
6629
|
+
},
|
|
6242
6630
|
});
|
|
6243
6631
|
}
|
|
6244
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6245
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, studioResolutionSize: { classPropertyName: "studioResolutionSize", publicName: "studioResolutionSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\r\n <div #videoContainer [class.lightModeCursor]=\"lightMode()\" 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 <ng-content select=\"app-low-bandwidth-detector\"></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 }); }
|
|
6632
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealSceneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6633
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: UnrealSceneComponent, isStandalone: true, selector: "app-unreal-scene", inputs: { isStudio: { classPropertyName: "isStudio", publicName: "isStudio", isSignal: true, isRequired: false, transformFunction: null }, useContainerAsSizeProvider: { classPropertyName: "useContainerAsSizeProvider", publicName: "useContainerAsSizeProvider", isSignal: true, isRequired: false, transformFunction: null }, studioResolutionSize: { classPropertyName: "studioResolutionSize", publicName: "studioResolutionSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changeMouseOverScene: "changeMouseOverScene" }, host: { listeners: { "mouseover": "onMouseOver()", "mouseout": "onMouseOut()" } }, viewQueries: [{ propertyName: "videoElement", first: true, predicate: ["videoContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"frame unreal-container\">\r\n <div #videoContainer [class.lightModeCursor]=\"lightMode()\" 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 <ng-content select=\"app-low-bandwidth-detector\"></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 }); }
|
|
6246
6634
|
}
|
|
6247
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6635
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: UnrealSceneComponent, decorators: [{
|
|
6248
6636
|
type: Component,
|
|
6249
6637
|
args: [{ selector: 'app-unreal-scene', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
6250
6638
|
AfkTimeoutModalComponent,
|
|
@@ -6283,10 +6671,10 @@ class WebrtcErrorModalComponent {
|
|
|
6283
6671
|
}
|
|
6284
6672
|
this.close();
|
|
6285
6673
|
}
|
|
6286
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6287
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.
|
|
6674
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WebrtcErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6675
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.9", 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 }); }
|
|
6288
6676
|
}
|
|
6289
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6677
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: WebrtcErrorModalComponent, decorators: [{
|
|
6290
6678
|
type: Component,
|
|
6291
6679
|
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"] }]
|
|
6292
6680
|
}] });
|
|
@@ -6332,10 +6720,10 @@ class FilterSettingsComponent {
|
|
|
6332
6720
|
panelOpen: this.settings.panelOpen,
|
|
6333
6721
|
});
|
|
6334
6722
|
}
|
|
6335
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6336
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6723
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FilterSettingsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6724
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: FilterSettingsComponent, isStandalone: true, selector: "app-filter-settings", ngImport: i0, template: "<div class=\"settings-container\">\r\n <button (click)=\"togglePanel()\" class=\"gear-button\">LBM</button>\r\n @if (settings.panelOpen) {\r\n <div class=\"settings-panel\">\r\n <h3>LBM Trigger Settings</h3>\r\n <h5>Data Flow Monitor</h5>\r\n <form>\r\n <label>\r\n Monitoring delay:\r\n <input\r\n [(ngModel)]=\"settings.monitoringDelayTime\"\r\n [placeholder]=\"defaultFilterModel.monitoringDelayTime\"\r\n type=\"number\"\r\n name=\"monitoringDelayTime\"\r\n />\r\n </label>\r\n\r\n <label>\r\n Minimum Bitrate (kbit/s):\r\n <input\r\n [(ngModel)]=\"settings.minimumBitrate\"\r\n [placeholder]=\"defaultFilterModel.minimumBitrate\"\r\n type=\"number\"\r\n name=\"minimumBitrate\"\r\n />\r\n </label>\r\n <label>\r\n Yellow Flag (%):\r\n <input\r\n [(ngModel)]=\"settings.yellowFlag\"\r\n [placeholder]=\"defaultFilterModel.yellowFlag\"\r\n type=\"number\"\r\n name=\"yellowFlag\"\r\n />\r\n </label>\r\n <label>\r\n Red Flag (%):\r\n <input\r\n [(ngModel)]=\"settings.redFlag\"\r\n [placeholder]=\"defaultFilterModel.redFlag\"\r\n type=\"number\"\r\n name=\"redFlag\"\r\n />\r\n </label>\r\n <label>\r\n Minimum FPS:\r\n <input\r\n [(ngModel)]=\"settings.minimumFps\"\r\n [placeholder]=\"defaultFilterModel.minimumFps\"\r\n type=\"number\"\r\n name=\"minimumFps\"\r\n />\r\n </label>\r\n <hr />\r\n <h5>Kalman Filter</h5>\r\n <label>\r\n Initial Bitrate Estimate (kbit/s):\r\n <input\r\n [(ngModel)]=\"settings.initialBitrateEstimate\"\r\n [placeholder]=\"defaultFilterModel.initialBitrateEstimate\"\r\n type=\"number\"\r\n name=\"initialBitrateEstimate\"\r\n />\r\n </label>\r\n <label>\r\n Initial Error Covariance:\r\n <input\r\n [(ngModel)]=\"settings.initialErrorCovariance\"\r\n [placeholder]=\"defaultFilterModel.initialErrorCovariance\"\r\n type=\"number\"\r\n name=\"initialErrorCovariance\"\r\n />\r\n </label>\r\n <label>\r\n Process Noise (Q):\r\n <input\r\n [(ngModel)]=\"settings.processNoise\"\r\n [placeholder]=\"defaultFilterModel.processNoise\"\r\n type=\"number\"\r\n name=\"processNoise\"\r\n />\r\n </label>\r\n <label>\r\n Measurement Noise (R):\r\n <input\r\n [(ngModel)]=\"settings.measurementNoise\"\r\n [placeholder]=\"defaultFilterModel.measurementNoise\"\r\n type=\"number\"\r\n name=\"measurementNoise\"\r\n />\r\n </label>\r\n </form>\r\n <br />\r\n <br />\r\n <div class=\"apply-button-container\">\r\n <button (click)=\"reset()\">Reset</button>\r\n <button (click)=\"saveSettings()\">Apply</button>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".settings-container{position:absolute;top:65px;right:10px;z-index:1000}.gear-button{right:0;width:50px;position:absolute;cursor:pointer;font-size:10px;background:none;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#e6f419;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}.settings-panel{font-size:13px;background:#fff;border:1px solid #ccc;padding:15px;margin-top:25px;border-radius:4px;box-shadow:0 2px 8px #0003}.settings-panel h3{margin-top:0}.settings-panel form{display:flex;flex-direction:column}.settings-panel label{margin-bottom:10px;display:flex;flex-direction:column}.settings-panel input{padding:4px;margin-top:4px;border:1px solid #ccc;border-radius:2px}.apply-button-container{position:absolute;bottom:15px;right:15px}.apply-button-container button{padding:6px 12px;border:none;background-color:#1976d2;color:#fff;border-radius:4px;cursor:pointer;margin:2px}.apply-button-container button:hover{background-color:#1565c0}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6337
6725
|
}
|
|
6338
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6726
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: FilterSettingsComponent, decorators: [{
|
|
6339
6727
|
type: Component,
|
|
6340
6728
|
args: [{ selector: 'app-filter-settings', imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"settings-container\">\r\n <button (click)=\"togglePanel()\" class=\"gear-button\">LBM</button>\r\n @if (settings.panelOpen) {\r\n <div class=\"settings-panel\">\r\n <h3>LBM Trigger Settings</h3>\r\n <h5>Data Flow Monitor</h5>\r\n <form>\r\n <label>\r\n Monitoring delay:\r\n <input\r\n [(ngModel)]=\"settings.monitoringDelayTime\"\r\n [placeholder]=\"defaultFilterModel.monitoringDelayTime\"\r\n type=\"number\"\r\n name=\"monitoringDelayTime\"\r\n />\r\n </label>\r\n\r\n <label>\r\n Minimum Bitrate (kbit/s):\r\n <input\r\n [(ngModel)]=\"settings.minimumBitrate\"\r\n [placeholder]=\"defaultFilterModel.minimumBitrate\"\r\n type=\"number\"\r\n name=\"minimumBitrate\"\r\n />\r\n </label>\r\n <label>\r\n Yellow Flag (%):\r\n <input\r\n [(ngModel)]=\"settings.yellowFlag\"\r\n [placeholder]=\"defaultFilterModel.yellowFlag\"\r\n type=\"number\"\r\n name=\"yellowFlag\"\r\n />\r\n </label>\r\n <label>\r\n Red Flag (%):\r\n <input\r\n [(ngModel)]=\"settings.redFlag\"\r\n [placeholder]=\"defaultFilterModel.redFlag\"\r\n type=\"number\"\r\n name=\"redFlag\"\r\n />\r\n </label>\r\n <label>\r\n Minimum FPS:\r\n <input\r\n [(ngModel)]=\"settings.minimumFps\"\r\n [placeholder]=\"defaultFilterModel.minimumFps\"\r\n type=\"number\"\r\n name=\"minimumFps\"\r\n />\r\n </label>\r\n <hr />\r\n <h5>Kalman Filter</h5>\r\n <label>\r\n Initial Bitrate Estimate (kbit/s):\r\n <input\r\n [(ngModel)]=\"settings.initialBitrateEstimate\"\r\n [placeholder]=\"defaultFilterModel.initialBitrateEstimate\"\r\n type=\"number\"\r\n name=\"initialBitrateEstimate\"\r\n />\r\n </label>\r\n <label>\r\n Initial Error Covariance:\r\n <input\r\n [(ngModel)]=\"settings.initialErrorCovariance\"\r\n [placeholder]=\"defaultFilterModel.initialErrorCovariance\"\r\n type=\"number\"\r\n name=\"initialErrorCovariance\"\r\n />\r\n </label>\r\n <label>\r\n Process Noise (Q):\r\n <input\r\n [(ngModel)]=\"settings.processNoise\"\r\n [placeholder]=\"defaultFilterModel.processNoise\"\r\n type=\"number\"\r\n name=\"processNoise\"\r\n />\r\n </label>\r\n <label>\r\n Measurement Noise (R):\r\n <input\r\n [(ngModel)]=\"settings.measurementNoise\"\r\n [placeholder]=\"defaultFilterModel.measurementNoise\"\r\n type=\"number\"\r\n name=\"measurementNoise\"\r\n />\r\n </label>\r\n </form>\r\n <br />\r\n <br />\r\n <div class=\"apply-button-container\">\r\n <button (click)=\"reset()\">Reset</button>\r\n <button (click)=\"saveSettings()\">Apply</button>\r\n </div>\r\n </div>\r\n }\r\n</div>\r\n", styles: [".settings-container{position:absolute;top:65px;right:10px;z-index:1000}.gear-button{right:0;width:50px;position:absolute;cursor:pointer;font-size:10px;background:none;font-weight:500;border:1px solid;border-radius:9px;background:#0162cc;color:#fff;padding:4px}.gear-button>*{color:#e6f419;filter:drop-shadow(1px 1px 1px rgb(0,0,0))}.settings-panel{font-size:13px;background:#fff;border:1px solid #ccc;padding:15px;margin-top:25px;border-radius:4px;box-shadow:0 2px 8px #0003}.settings-panel h3{margin-top:0}.settings-panel form{display:flex;flex-direction:column}.settings-panel label{margin-bottom:10px;display:flex;flex-direction:column}.settings-panel input{padding:4px;margin-top:4px;border:1px solid #ccc;border-radius:2px}.apply-button-container{position:absolute;bottom:15px;right:15px}.apply-button-container button{padding:6px 12px;border:none;background-color:#1976d2;color:#fff;border-radius:4px;cursor:pointer;margin:2px}.apply-button-container button:hover{background-color:#1565c0}\n"] }]
|
|
6341
6729
|
}] });
|
|
@@ -6440,10 +6828,10 @@ class LowBandwidthDetectorComponent {
|
|
|
6440
6828
|
this.store.dispatch(changeLowBandwidth({ lowBandwidth: false }));
|
|
6441
6829
|
});
|
|
6442
6830
|
}
|
|
6443
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.
|
|
6444
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.
|
|
6831
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LowBandwidthDetectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
6832
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: LowBandwidthDetectorComponent, isStandalone: true, selector: "app-low-bandwidth-detector", ngImport: i0, template: "@if (isReducedQuality() || isLowBandwidth()) {\r\n <div\r\n [class.expanded]=\"isIndicatorExpanded() && isLowBandwidth()\"\r\n class=\"lbm-indicator freeze-loader\"\r\n >\r\n <div (click)=\"toggleIndicator(true)\" class=\"lbm-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill=\"#85888E\"\r\n d=\"M7.189 3.605c-.73.145-1.438.35-2.126.614A14.412 14.412 0 0 0 .896 6.666a1.08 1.08 0 0 0-.375.844c0 .34.118.629.354.865s.524.36.865.375c.34.014.65-.09.927-.313a11.89 11.89 0 0 1 3.385-1.916 10.94 10.94 0 0 1 1.235-.375l-.098-2.541ZM7.385 8.708a9.107 9.107 0 0 0-2.906 1.48c-.264.194-.402.464-.416.812-.014.347.104.646.354.896.236.236.524.364.864.385.34.02.664-.073.97-.281.422-.29.878-.53 1.368-.721a2.545 2.545 0 0 1-.166-.814l-.068-1.757ZM12.386 11.267c.094-.25.15-.52.161-.802l.068-1.755a9.019 9.019 0 0 1 2.927 1.52c.264.193.4.46.406.801.007.34-.114.635-.364.885a1.213 1.213 0 0 1-.865.365A1.614 1.614 0 0 1 13.77 12a6.574 6.574 0 0 0-1.385-.733ZM12.713 6.146l.098-2.542c.73.146 1.438.351 2.127.615 1.541.59 2.93 1.406 4.166 2.447.25.223.379.5.386.834.007.333-.115.625-.365.875a1.253 1.253 0 0 1-.864.375c-.34.014-.65-.09-.927-.313a11.892 11.892 0 0 0-3.386-1.916 10.94 10.94 0 0 0-1.235-.375ZM8.813 16.187c.32.32.715.48 1.187.48.472 0 .868-.16 1.188-.48.32-.32.479-.715.479-1.187 0-.473-.16-.868-.48-1.188-.319-.32-.715-.479-1.187-.479-.472 0-.868.16-1.187.48-.32.319-.48.714-.48 1.187 0 .472.16.868.48 1.187Z\"\r\n />\r\n <path\r\n fill=\"#fff\"\r\n fill-rule=\"evenodd\"\r\n d=\"M10 .833c-.91 0-1.637.756-1.602 1.665l.304 7.92a1.299 1.299 0 0 0 2.596 0l.305-7.92A1.604 1.604 0 0 0 10 .833ZM8.813 16.187c.32.32.715.48 1.187.48.472 0 .868-.16 1.188-.48.32-.32.479-.715.479-1.187 0-.473-.16-.868-.48-1.188-.319-.32-.715-.479-1.187-.479-.472 0-.868.16-1.187.48-.32.319-.48.714-.48 1.187 0 .472.16.868.48 1.187Z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n </div>\r\n\r\n <div\r\n [class.lbm-message--open]=\"isLowBandwidth() && isIndicatorExpanded()\"\r\n class=\"lbm-message\"\r\n >\r\n <p class=\"lbm-description\">\r\n Fluid Interactivity Modes were disabled due to an unstable connection.\r\n\r\n <button\r\n (click)=\"openLBMDialog()\"\r\n [attr.data-testid]=\"'learn-more-lbm'\"\r\n type=\"button\"\r\n class=\"lbm-learn-more\"\r\n >\r\n Learn more\r\n </button>\r\n </p>\r\n <button\r\n (click)=\"toggleIndicator(false)\"\r\n [attr.data-testid]=\"'close-lbm-indicator'\"\r\n type=\"button\"\r\n class=\"lbm-close\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"20\"\r\n height=\"20\"\r\n viewBox=\"0 0 20 20\"\r\n fill=\"none\"\r\n >\r\n <path\r\n d=\"M10.0001 11.2289L6.68618 14.5428C6.51951 14.7095 6.31818 14.7895 6.08218 14.7828C5.84618 14.7755 5.64485 14.6885 5.47818 14.5218C5.31151 14.3552 5.22818 14.1505 5.22818 13.9078C5.22818 13.6645 5.31151 13.4595 5.47818 13.2928L8.77107 9.99992L5.45718 6.68603C5.29051 6.51936 5.21051 6.3147 5.21718 6.07203C5.22451 5.8287 5.31151 5.6237 5.47818 5.45703C5.64485 5.29036 5.84951 5.20703 6.09218 5.20703C6.33551 5.20703 6.54051 5.29036 6.70718 5.45703L10.0001 8.77092L13.314 5.45703C13.4806 5.29036 13.6853 5.20703 13.928 5.20703C14.1713 5.20703 14.3763 5.29036 14.543 5.45703C14.7096 5.6237 14.793 5.8287 14.793 6.07203C14.793 6.3147 14.7096 6.51936 14.543 6.68603L11.2291 9.99992L14.543 13.3138C14.7096 13.4805 14.793 13.6818 14.793 13.9178C14.793 14.1538 14.7096 14.3552 14.543 14.5218C14.3763 14.6885 14.1713 14.7718 13.928 14.7718C13.6853 14.7718 13.4806 14.6885 13.314 14.5218L10.0001 11.2289Z\"\r\n fill=\"white\"\r\n />\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n}\r\n\r\n@if (isDevMode) {\r\n <app-filter-settings />\r\n}\r\n", styles: [".freeze-loader,.lbm-indicator{--lbmPositionTop: 18px;--lbmPositionLeft: 18px;--lbmPositionRight: 18px;--lbmIndicatorSize: 36px;--lbmIndicatorBorderRadius: 8px;--lbmIndicatorCloseSize: 20px;--lbmPadding: 10px;--lbmTextSize: 13px;--lbmLineHeight: 20px;--lbmTextColor: #ffffff;--lbmBackgroundColor: rgba(17, 24, 39, .7);position:absolute;top:var(--lbmPositionTop);left:var(--lbmPositionLeft);z-index:10;display:flex;justify-content:center;align-items:flex-start;min-width:var(--lbmIndicatorSize);min-height:var(--lbmIndicatorSize);overflow:hidden}.freeze-loader.expanded,.lbm-indicator.expanded{--lbmPositionTop: 8px;--lbmPositionLeft: 8px;--lbmPositionRight: 8px;width:auto;max-width:calc(100% - var(--lbmPositionLeft) - var(--lbmPositionRight));padding:var(--lbmPadding);background:var(--lbmBackgroundColor);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:var(--lbmIndicatorBorderRadius);gap:12px}.freeze-loader .lbm-icon,.lbm-indicator .lbm-icon{display:flex;width:var(--lbmIndicatorSize);height:var(--lbmIndicatorSize);padding:8px;color:#fff;background-color:var(--lbmBackgroundColor);border-radius:50%;flex-shrink:0;cursor:pointer}.freeze-loader .lbm-icon svg,.freeze-loader .lbm-icon img,.lbm-indicator .lbm-icon svg,.lbm-indicator .lbm-icon img{width:100%;height:100%}.freeze-loader .lbm-close,.lbm-indicator .lbm-close{position:absolute;top:0;right:0;width:var(--lbmIndicatorCloseSize);height:var(--lbmIndicatorCloseSize);background-color:transparent;border:none;padding:0;cursor:pointer}.freeze-loader .lbm-message,.lbm-indicator .lbm-message{position:relative;opacity:0;height:0;max-width:0;padding-right:24px;pointer-events:none;visibility:hidden;animation:closeMessage .3s forwards}.freeze-loader .lbm-message--open,.lbm-indicator .lbm-message--open{position:relative;visibility:visible;display:flex;align-items:center;align-self:center;pointer-events:all;gap:12px;max-width:fit-content;height:auto;opacity:1;animation:openMessage .3s forwards}.freeze-loader .lbm-description,.lbm-indicator .lbm-description{margin:0;font-size:var(--lbmTextSize);font-weight:400;line-height:var(--lbmLineHeight);color:var(--lbmTextColor)}.freeze-loader .lbm-learn-more,.lbm-indicator .lbm-learn-more{display:inline-flex;padding:0;font-size:var(--lbmTextSize);font-weight:400;line-height:var(--lbmLineHeight);color:var(--lbmTextColor);text-decoration-line:underline;background-color:transparent;border:none;cursor:pointer}.freeze-loader .lbm-learn-more:hover,.lbm-indicator .lbm-learn-more:hover{text-decoration:none}@keyframes openMessage{0%{opacity:0;max-width:0;height:0}10%{height:auto;max-width:fit-content}to{opacity:1;height:auto;max-width:fit-content}}@keyframes closeMessage{0%{opacity:1;max-width:fit-content}to{opacity:0;max-width:0}}\n"], dependencies: [{ kind: "component", type: FilterSettingsComponent, selector: "app-filter-settings" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
6445
6833
|
}
|
|
6446
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.
|
|
6834
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: LowBandwidthDetectorComponent, decorators: [{
|
|
6447
6835
|
type: Component,
|
|
6448
6836
|
args: [{ selector: 'app-low-bandwidth-detector', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FilterSettingsComponent], template: "@if (isReducedQuality() || isLowBandwidth()) {\r\n <div\r\n [class.expanded]=\"isIndicatorExpanded() && isLowBandwidth()\"\r\n class=\"lbm-indicator freeze-loader\"\r\n >\r\n <div (click)=\"toggleIndicator(true)\" class=\"lbm-icon\">\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 20 20\">\r\n <path\r\n fill=\"#85888E\"\r\n d=\"M7.189 3.605c-.73.145-1.438.35-2.126.614A14.412 14.412 0 0 0 .896 6.666a1.08 1.08 0 0 0-.375.844c0 .34.118.629.354.865s.524.36.865.375c.34.014.65-.09.927-.313a11.89 11.89 0 0 1 3.385-1.916 10.94 10.94 0 0 1 1.235-.375l-.098-2.541ZM7.385 8.708a9.107 9.107 0 0 0-2.906 1.48c-.264.194-.402.464-.416.812-.014.347.104.646.354.896.236.236.524.364.864.385.34.02.664-.073.97-.281.422-.29.878-.53 1.368-.721a2.545 2.545 0 0 1-.166-.814l-.068-1.757ZM12.386 11.267c.094-.25.15-.52.161-.802l.068-1.755a9.019 9.019 0 0 1 2.927 1.52c.264.193.4.46.406.801.007.34-.114.635-.364.885a1.213 1.213 0 0 1-.865.365A1.614 1.614 0 0 1 13.77 12a6.574 6.574 0 0 0-1.385-.733ZM12.713 6.146l.098-2.542c.73.146 1.438.351 2.127.615 1.541.59 2.93 1.406 4.166 2.447.25.223.379.5.386.834.007.333-.115.625-.365.875a1.253 1.253 0 0 1-.864.375c-.34.014-.65-.09-.927-.313a11.892 11.892 0 0 0-3.386-1.916 10.94 10.94 0 0 0-1.235-.375ZM8.813 16.187c.32.32.715.48 1.187.48.472 0 .868-.16 1.188-.48.32-.32.479-.715.479-1.187 0-.473-.16-.868-.48-1.188-.319-.32-.715-.479-1.187-.479-.472 0-.868.16-1.187.48-.32.319-.48.714-.48 1.187 0 .472.16.868.48 1.187Z\"\r\n />\r\n <path\r\n fill=\"#fff\"\r\n fill-rule=\"evenodd\"\r\n d=\"M10 .833c-.91 0-1.637.756-1.602 1.665l.304 7.92a1.299 1.299 0 0 0 2.596 0l.305-7.92A1.604 1.604 0 0 0 10 .833ZM8.813 16.187c.32.32.715.48 1.187.48.472 0 .868-.16 1.188-.48.32-.32.479-.715.479-1.187 0-.473-.16-.868-.48-1.188-.319-.32-.715-.479-1.187-.479-.472 0-.868.16-1.187.48-.32.319-.48.714-.48 1.187 0 .472.16.868.48 1.187Z\"\r\n clip-rule=\"evenodd\"\r\n />\r\n </svg>\r\n </div>\r\n\r\n <div\r\n [class.lbm-message--open]=\"isLowBandwidth() && isIndicatorExpanded()\"\r\n class=\"lbm-message\"\r\n >\r\n <p class=\"lbm-description\">\r\n Fluid Interactivity Modes were disabled due to an unstable connection.\r\n\r\n <button\r\n (click)=\"openLBMDialog()\"\r\n [attr.data-testid]=\"'learn-more-lbm'\"\r\n type=\"button\"\r\n class=\"lbm-learn-more\"\r\n >\r\n Learn more\r\n </button>\r\n </p>\r\n <button\r\n (click)=\"toggleIndicator(false)\"\r\n [attr.data-testid]=\"'close-lbm-indicator'\"\r\n type=\"button\"\r\n class=\"lbm-close\"\r\n >\r\n <svg\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n width=\"20\"\r\n height=\"20\"\r\n viewBox=\"0 0 20 20\"\r\n fill=\"none\"\r\n >\r\n <path\r\n d=\"M10.0001 11.2289L6.68618 14.5428C6.51951 14.7095 6.31818 14.7895 6.08218 14.7828C5.84618 14.7755 5.64485 14.6885 5.47818 14.5218C5.31151 14.3552 5.22818 14.1505 5.22818 13.9078C5.22818 13.6645 5.31151 13.4595 5.47818 13.2928L8.77107 9.99992L5.45718 6.68603C5.29051 6.51936 5.21051 6.3147 5.21718 6.07203C5.22451 5.8287 5.31151 5.6237 5.47818 5.45703C5.64485 5.29036 5.84951 5.20703 6.09218 5.20703C6.33551 5.20703 6.54051 5.29036 6.70718 5.45703L10.0001 8.77092L13.314 5.45703C13.4806 5.29036 13.6853 5.20703 13.928 5.20703C14.1713 5.20703 14.3763 5.29036 14.543 5.45703C14.7096 5.6237 14.793 5.8287 14.793 6.07203C14.793 6.3147 14.7096 6.51936 14.543 6.68603L11.2291 9.99992L14.543 13.3138C14.7096 13.4805 14.793 13.6818 14.793 13.9178C14.793 14.1538 14.7096 14.3552 14.543 14.5218C14.3763 14.6885 14.1713 14.7718 13.928 14.7718C13.6853 14.7718 13.4806 14.6885 13.314 14.5218L10.0001 11.2289Z\"\r\n fill=\"white\"\r\n />\r\n </svg>\r\n </button>\r\n </div>\r\n </div>\r\n}\r\n\r\n@if (isDevMode) {\r\n <app-filter-settings />\r\n}\r\n", styles: [".freeze-loader,.lbm-indicator{--lbmPositionTop: 18px;--lbmPositionLeft: 18px;--lbmPositionRight: 18px;--lbmIndicatorSize: 36px;--lbmIndicatorBorderRadius: 8px;--lbmIndicatorCloseSize: 20px;--lbmPadding: 10px;--lbmTextSize: 13px;--lbmLineHeight: 20px;--lbmTextColor: #ffffff;--lbmBackgroundColor: rgba(17, 24, 39, .7);position:absolute;top:var(--lbmPositionTop);left:var(--lbmPositionLeft);z-index:10;display:flex;justify-content:center;align-items:flex-start;min-width:var(--lbmIndicatorSize);min-height:var(--lbmIndicatorSize);overflow:hidden}.freeze-loader.expanded,.lbm-indicator.expanded{--lbmPositionTop: 8px;--lbmPositionLeft: 8px;--lbmPositionRight: 8px;width:auto;max-width:calc(100% - var(--lbmPositionLeft) - var(--lbmPositionRight));padding:var(--lbmPadding);background:var(--lbmBackgroundColor);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-radius:var(--lbmIndicatorBorderRadius);gap:12px}.freeze-loader .lbm-icon,.lbm-indicator .lbm-icon{display:flex;width:var(--lbmIndicatorSize);height:var(--lbmIndicatorSize);padding:8px;color:#fff;background-color:var(--lbmBackgroundColor);border-radius:50%;flex-shrink:0;cursor:pointer}.freeze-loader .lbm-icon svg,.freeze-loader .lbm-icon img,.lbm-indicator .lbm-icon svg,.lbm-indicator .lbm-icon img{width:100%;height:100%}.freeze-loader .lbm-close,.lbm-indicator .lbm-close{position:absolute;top:0;right:0;width:var(--lbmIndicatorCloseSize);height:var(--lbmIndicatorCloseSize);background-color:transparent;border:none;padding:0;cursor:pointer}.freeze-loader .lbm-message,.lbm-indicator .lbm-message{position:relative;opacity:0;height:0;max-width:0;padding-right:24px;pointer-events:none;visibility:hidden;animation:closeMessage .3s forwards}.freeze-loader .lbm-message--open,.lbm-indicator .lbm-message--open{position:relative;visibility:visible;display:flex;align-items:center;align-self:center;pointer-events:all;gap:12px;max-width:fit-content;height:auto;opacity:1;animation:openMessage .3s forwards}.freeze-loader .lbm-description,.lbm-indicator .lbm-description{margin:0;font-size:var(--lbmTextSize);font-weight:400;line-height:var(--lbmLineHeight);color:var(--lbmTextColor)}.freeze-loader .lbm-learn-more,.lbm-indicator .lbm-learn-more{display:inline-flex;padding:0;font-size:var(--lbmTextSize);font-weight:400;line-height:var(--lbmLineHeight);color:var(--lbmTextColor);text-decoration-line:underline;background-color:transparent;border:none;cursor:pointer}.freeze-loader .lbm-learn-more:hover,.lbm-indicator .lbm-learn-more:hover{text-decoration:none}@keyframes openMessage{0%{opacity:0;max-width:0;height:0}10%{height:auto;max-width:fit-content}to{opacity:1;height:auto;max-width:fit-content}}@keyframes closeMessage{0%{opacity:1;max-width:fit-content}to{opacity:0;max-width:0}}\n"] }]
|
|
6449
6837
|
}] });
|
|
@@ -6468,20 +6856,14 @@ function provideUnrealModule() {
|
|
|
6468
6856
|
AFKService,
|
|
6469
6857
|
SignallingService,
|
|
6470
6858
|
StreamStatusTelemetryService,
|
|
6859
|
+
StreamStatusTelemetryService2,
|
|
6471
6860
|
FpsMonitorService,
|
|
6472
|
-
|
|
6473
|
-
provide: WebRtcPlayerService,
|
|
6474
|
-
useFactory: () => {
|
|
6475
|
-
const config = inject(UNREAL_CONFIG, { optional: true });
|
|
6476
|
-
return config?.newWebRtcConnector
|
|
6477
|
-
? new WebRtcPlayerService2()
|
|
6478
|
-
: new WebRtcPlayerService();
|
|
6479
|
-
},
|
|
6480
|
-
},
|
|
6861
|
+
WebRtcPlayerService,
|
|
6481
6862
|
provideEnvironmentInitializer(() => {
|
|
6482
6863
|
inject(AggregatorService);
|
|
6483
6864
|
inject(InputService);
|
|
6484
6865
|
inject(StreamStatusTelemetryService);
|
|
6866
|
+
inject(StreamStatusTelemetryService2);
|
|
6485
6867
|
inject(ConsoleExtensionsService);
|
|
6486
6868
|
inject(AFKService);
|
|
6487
6869
|
inject(FreezeFrameService);
|
|
@@ -6495,5 +6877,5 @@ function provideUnrealModule() {
|
|
|
6495
6877
|
* Generated bundle index. Do not edit.
|
|
6496
6878
|
*/
|
|
6497
6879
|
|
|
6498
|
-
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService,
|
|
6880
|
+
export { AFKService, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, CONSOLE_COMMAND_DISABLE_MESSAGES, CONSOLE_COMMAND_ENABLE_MESSAGES, CONSOLE_COMMAND_PIXEL_QUALITY, CommandTelemetryService, CommandsEffects, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DEFAULT_RECONNECT_DELAY_MS, DEFAULT_RECONNECT_ENABLED, DEFAULT_RECONNECT_MAX_ATTEMPTS, DEFAULT_RECONNECT_ON_DATACHANNEL_CLOSE, DEFAULT_RECONNECT_ON_ICE_FAILURE, DataFlowMonitor, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, FileHandlerService, FileReceiverService, FilterSettingsComponent, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, KalmanFilter1D, LatencyTimings, LowBandwidthDetectorComponent, LowBandwidthModalComponent, MINIMAL_FPS, MessageType, MouseButton, MouseButtonsMask, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PingHandler, PlayerCountHandler, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_VIDEO_ID, SafePipe, SignallingService, SpecialKeyCodes, StatGraphComponent, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, UNREAL_CONFIG, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealSceneComponent, UnrealStatusMessage, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeLowBandwidth, changeStatusMainVideoOnScene, changeStreamResolutionAction, changeStreamResolutionSuccessAction, commandCompleted, commandStarted, commandsFeature, commandsReducer, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, floatToSmoothPercents, forceResizeViewport, fromResizeObserver, getActiveUrl, getImageFromVideoStream, getResizeStudioValues, getResizeStudioValuesOld, getResizeValuesUniversal, getResizeValuesUniversalOld, getRtcErrorMessage, iceConnectionFailed, initSignalling, instanceReady, keepMaxUntilReset, makeEven, pollingEnded, pollingStarted, provideUnrealModule, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetWarnTimeout, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectCommandsInProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setDataChannelConnected, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setKalmanSettings, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setViewportNotReady, setViewportReady, showUnrealErrorMessage, smoothTransition, startStream, trackMixpanelEvent, trackTelemetryData, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
6499
6881
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|