@3dsource/angular-unreal-module 0.0.187 → 0.0.189
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.
package/LICENSE
CHANGED
|
File without changes
|
|
@@ -3,10 +3,10 @@ import { input, output, ChangeDetectionStrategy, Component, inject, DestroyRef,
|
|
|
3
3
|
import { NgTemplateOutlet, NgOptimizedImage, JsonPipe } from '@angular/common';
|
|
4
4
|
import { createAction, props, createReducer, on, createFeature, createSelector, Store, provideState } from '@ngrx/store';
|
|
5
5
|
import { Actions, ofType, createEffect, provideEffects } from '@ngrx/effects';
|
|
6
|
-
import { Subject, skip, share, timer, of, take, defer, switchMap, from, throwError, Observable, EMPTY, retry, merge, timeout as timeout$1, repeat, BehaviorSubject, interval,
|
|
6
|
+
import { Subject, skip, share, timer, of, take, defer, switchMap, from, throwError, Observable, EMPTY, retry, merge, timeout as timeout$1, repeat, BehaviorSubject, interval, takeUntil as takeUntil$1, fromEvent, tap as tap$1, throttleTime, map as map$1, delay, concat, debounceTime, mergeMap, startWith, takeWhile, NEVER, ignoreElements, scan, timestamp, pairwise, concatMap, animationFrameScheduler, shareReplay, firstValueFrom, asyncScheduler, filter as filter$1, withLatestFrom as withLatestFrom$1, Subscription, bufferTime, combineLatestWith } from 'rxjs';
|
|
7
7
|
import { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
|
|
8
8
|
export { UNREAL_CONFIG } from '@3dsource/angular-unreal-module/config';
|
|
9
|
-
import { tap, filter, takeUntil, catchError, map, timeout, finalize, first, switchMap as switchMap$1, distinctUntilChanged, withLatestFrom, exhaustMap, takeWhile, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
9
|
+
import { tap, filter, takeUntil, catchError, map, timeout, finalize, first, switchMap as switchMap$1, distinctUntilChanged, withLatestFrom, exhaustMap, takeWhile as takeWhile$1, debounceTime as debounceTime$1, take as take$1, startWith as startWith$1 } from 'rxjs/operators';
|
|
10
10
|
import { v4 } from 'uuid';
|
|
11
11
|
import { DialogRef, DIALOG_DATA, Dialog } from '@angular/cdk/dialog';
|
|
12
12
|
import { HttpClient } from '@angular/common/http';
|
|
@@ -673,6 +673,13 @@ const RECONNECT_CONFIG = {
|
|
|
673
673
|
maxAttempts: 3,
|
|
674
674
|
delayMs: 1000,
|
|
675
675
|
};
|
|
676
|
+
// Full-signalling restarts after consecutive DataChannelTimeouts. The limit is
|
|
677
|
+
// read at every timeout: a hidden tab gives up sooner, since nobody watches it
|
|
678
|
+
// and each attempt occupies a streaming instance.
|
|
679
|
+
const DATA_CHANNEL_TIMEOUT_RESTARTS = {
|
|
680
|
+
hidden: 3,
|
|
681
|
+
visible: 10,
|
|
682
|
+
};
|
|
676
683
|
const UnrealStatusMessage = {
|
|
677
684
|
CONNECTING_TO_SESSION: 'Connecting to session.',
|
|
678
685
|
STARTING_YOUR_SESSION: 'Starting your session',
|
|
@@ -2984,6 +2991,9 @@ class VideoService extends SubService {
|
|
|
2984
2991
|
// would drop that emission, so the stats interval (and `setViewportReady`,
|
|
2985
2992
|
// gated on `framesPerSecond`) would never start.
|
|
2986
2993
|
this.videoTrack$ = new BehaviorSubject(null);
|
|
2994
|
+
/** Bumped per attached peer connection: candidate-pair ids are only unique
|
|
2995
|
+
* inside one connection, so the epoch scopes the telemetry route ref. */
|
|
2996
|
+
this.pcEpoch = 0;
|
|
2987
2997
|
this.VideoEncoderQP = 0;
|
|
2988
2998
|
this.aggregatedStats = {};
|
|
2989
2999
|
/**
|
|
@@ -2992,7 +3002,7 @@ class VideoService extends SubService {
|
|
|
2992
3002
|
this.videoStats$ = this.videoTrack$.pipe(
|
|
2993
3003
|
// If videoTrack$ emits null, switch to EMPTY → the current interval is canceled.
|
|
2994
3004
|
switchMap$1((track) => track
|
|
2995
|
-
? interval(250).pipe(
|
|
3005
|
+
? interval(250).pipe(switchMap$1(() => from(this.getStats(track.pcClient, track.pcEpoch))), filter(Truthy))
|
|
2996
3006
|
: of(null)), filter(Truthy), share());
|
|
2997
3007
|
this.destroyAbort = new AbortController();
|
|
2998
3008
|
// Teardown is wired ONCE here instead of per `create()`. The per-create
|
|
@@ -3075,7 +3085,7 @@ class VideoService extends SubService {
|
|
|
3075
3085
|
this.audio.srcObject = new MediaStream(audioTracks);
|
|
3076
3086
|
this.playAudio(this.audio);
|
|
3077
3087
|
}
|
|
3078
|
-
this.videoTrack$.next({ stream, pcClient });
|
|
3088
|
+
this.videoTrack$.next({ stream, pcClient, pcEpoch: ++this.pcEpoch });
|
|
3079
3089
|
// Safari does not reliably honor the autoplay attribute when srcObject
|
|
3080
3090
|
// is bound post-creation for a WebRTC MediaStream; trigger play()
|
|
3081
3091
|
// explicitly and let safePlay() fall back if Safari refuses.
|
|
@@ -3158,7 +3168,7 @@ class VideoService extends SubService {
|
|
|
3158
3168
|
signal: this.destroyAbort.signal,
|
|
3159
3169
|
});
|
|
3160
3170
|
}
|
|
3161
|
-
async getStats(pcClient) {
|
|
3171
|
+
async getStats(pcClient, pcEpoch = 0) {
|
|
3162
3172
|
if (!pcClient || pcClient.connectionState === 'closed') {
|
|
3163
3173
|
return null;
|
|
3164
3174
|
}
|
|
@@ -3169,7 +3179,7 @@ class VideoService extends SubService {
|
|
|
3169
3179
|
if (!this.video) {
|
|
3170
3180
|
return null;
|
|
3171
3181
|
}
|
|
3172
|
-
return this.generateAggregatedStatsFunction(stats);
|
|
3182
|
+
return this.generateAggregatedStatsFunction(stats, pcEpoch);
|
|
3173
3183
|
}
|
|
3174
3184
|
catch (err) {
|
|
3175
3185
|
// Handle InvalidStateError or any transient WebRTC errors gracefully
|
|
@@ -3180,8 +3190,10 @@ class VideoService extends SubService {
|
|
|
3180
3190
|
throw err;
|
|
3181
3191
|
}
|
|
3182
3192
|
}
|
|
3183
|
-
generateAggregatedStatsFunction(stats) {
|
|
3193
|
+
generateAggregatedStatsFunction(stats, pcEpoch = 0) {
|
|
3184
3194
|
const newStat = {};
|
|
3195
|
+
let selectedPairId;
|
|
3196
|
+
let flaggedPairId;
|
|
3185
3197
|
// store each type of codec we can get stats on
|
|
3186
3198
|
newStat.codecs = {};
|
|
3187
3199
|
newStat.currentRoundTripTime = -1;
|
|
@@ -3190,7 +3202,9 @@ class VideoService extends SubService {
|
|
|
3190
3202
|
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
3191
3203
|
this.applyInboundVideoReport(newStat, report);
|
|
3192
3204
|
}
|
|
3193
|
-
//
|
|
3205
|
+
// Fallback RTT for when no selected pair resolves —
|
|
3206
|
+
// `applySelectedPair` overrides it with the selected pair's RTT.
|
|
3207
|
+
// Only read it from the *nominated* ICE candidate pair. The
|
|
3194
3208
|
// stats report contains multiple `candidate-pair` entries (one
|
|
3195
3209
|
// per host/srflx/relay combination tried during ICE); only the
|
|
3196
3210
|
// nominated pair carries a meaningful `currentRoundTripTime`,
|
|
@@ -3205,9 +3219,13 @@ class VideoService extends SubService {
|
|
|
3205
3219
|
Object.hasOwn(report, 'currentRoundTripTime')) {
|
|
3206
3220
|
newStat.currentRoundTripTime = report.currentRoundTripTime ?? 0;
|
|
3207
3221
|
}
|
|
3208
|
-
if (report.type === 'transport' &&
|
|
3209
|
-
|
|
3210
|
-
|
|
3222
|
+
if (report.type === 'transport' && report.selectedCandidatePairId) {
|
|
3223
|
+
selectedPairId = report.selectedCandidatePairId;
|
|
3224
|
+
}
|
|
3225
|
+
// Firefox reports no `transport` stats; its pre-standard `selected`
|
|
3226
|
+
// flag marks the same pair and is used only when the transport is absent.
|
|
3227
|
+
if (report.type === 'candidate-pair' && report.selected === true) {
|
|
3228
|
+
flaggedPairId = report.id;
|
|
3211
3229
|
}
|
|
3212
3230
|
// Store mimetype of each codec
|
|
3213
3231
|
if (Object.hasOwn(newStat, 'codecs') &&
|
|
@@ -3220,6 +3238,7 @@ class VideoService extends SubService {
|
|
|
3220
3238
|
.replace('audio/', '');
|
|
3221
3239
|
}
|
|
3222
3240
|
});
|
|
3241
|
+
this.applySelectedPair(newStat, stats, selectedPairId ?? flaggedPairId, pcEpoch);
|
|
3223
3242
|
this.applySessionTotals(newStat);
|
|
3224
3243
|
this.aggregatedStats = newStat;
|
|
3225
3244
|
return this.onAggregatedStats(newStat);
|
|
@@ -3261,24 +3280,37 @@ class VideoService extends SubService {
|
|
|
3261
3280
|
}
|
|
3262
3281
|
}
|
|
3263
3282
|
/**
|
|
3264
|
-
* Resolves the
|
|
3265
|
-
*
|
|
3266
|
-
*
|
|
3283
|
+
* Resolves the selected candidate pair into its route description, byte
|
|
3284
|
+
* counters and RTT. Anything missing from this report stays unknown, and a
|
|
3285
|
+
* malformed report never throws into the stats loop — the route is optional,
|
|
3286
|
+
* playback is not.
|
|
3267
3287
|
*/
|
|
3268
|
-
applySelectedPair(newStat, stats,
|
|
3269
|
-
|
|
3288
|
+
applySelectedPair(newStat, stats, pairId, pcEpoch) {
|
|
3289
|
+
newStat.selectedPair = null;
|
|
3290
|
+
if (!pairId) {
|
|
3270
3291
|
return;
|
|
3271
3292
|
}
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3293
|
+
try {
|
|
3294
|
+
const pair = stats.get(pairId);
|
|
3295
|
+
if (!pair) {
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
newStat.selectedPair = {
|
|
3299
|
+
ref: `${pcEpoch}:${pairId}`,
|
|
3300
|
+
timestamp: finite(pair.timestamp),
|
|
3301
|
+
bytesReceived: finite(pair.bytesReceived),
|
|
3302
|
+
bytesSent: finite(pair.bytesSent),
|
|
3303
|
+
local: candidateInfo(stats.get(pair.localCandidateId), true),
|
|
3304
|
+
remote: candidateInfo(stats.get(pair.remoteCandidateId), false),
|
|
3305
|
+
};
|
|
3306
|
+
const rtt = finite(pair.currentRoundTripTime);
|
|
3307
|
+
if (rtt !== undefined) {
|
|
3308
|
+
newStat.currentRoundTripTime = rtt;
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
catch {
|
|
3312
|
+
newStat.selectedPair = null;
|
|
3275
3313
|
}
|
|
3276
|
-
const local = stats.get(selectedPair.localCandidateId);
|
|
3277
|
-
const remote = stats.get(selectedPair.remoteCandidateId);
|
|
3278
|
-
newStat.selectedPair = {
|
|
3279
|
-
local: { type: local?.candidateType, url: local?.url },
|
|
3280
|
-
remote: { type: remote?.candidateType, url: remote?.url },
|
|
3281
|
-
};
|
|
3282
3314
|
}
|
|
3283
3315
|
/** Per-session values that do not come from a single report. */
|
|
3284
3316
|
applySessionTotals(newStat) {
|
|
@@ -3394,6 +3426,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
3394
3426
|
function pinBaseline(pinned, current) {
|
|
3395
3427
|
return (pinned || current);
|
|
3396
3428
|
}
|
|
3429
|
+
function finite(value) {
|
|
3430
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
3431
|
+
? value
|
|
3432
|
+
: undefined;
|
|
3433
|
+
}
|
|
3434
|
+
/**
|
|
3435
|
+
* The route-relevant fields of one candidate. The address is uploaded for a
|
|
3436
|
+
* relay candidate only — host / srflx addresses identify the user, a relay
|
|
3437
|
+
* address identifies the TURN provider's region.
|
|
3438
|
+
*/
|
|
3439
|
+
function candidateInfo(candidate, isLocal) {
|
|
3440
|
+
if (!candidate) {
|
|
3441
|
+
return {};
|
|
3442
|
+
}
|
|
3443
|
+
const isRelay = candidate.candidateType === 'relay';
|
|
3444
|
+
return {
|
|
3445
|
+
type: candidate.candidateType,
|
|
3446
|
+
protocol: candidate.protocol,
|
|
3447
|
+
...(isRelay
|
|
3448
|
+
? {
|
|
3449
|
+
address: candidate.address ?? undefined,
|
|
3450
|
+
port: finite(candidate.port),
|
|
3451
|
+
}
|
|
3452
|
+
: {}),
|
|
3453
|
+
...(isLocal
|
|
3454
|
+
? { url: candidate.url, relayProtocol: candidate.relayProtocol }
|
|
3455
|
+
: {}),
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3397
3458
|
/**
|
|
3398
3459
|
* Whether a rejected `play()` was blocked by the browser autoplay policy.
|
|
3399
3460
|
* Narrows an `unknown` rejection value without assuming it is an Error — a
|
|
@@ -3708,6 +3769,12 @@ class WebRtcPlayerService extends SubService {
|
|
|
3708
3769
|
canSend() {
|
|
3709
3770
|
return this.dcClient && this.dcClient.readyState === 'open';
|
|
3710
3771
|
}
|
|
3772
|
+
describeConnectionState() {
|
|
3773
|
+
const pc = this.pcClient;
|
|
3774
|
+
return pc
|
|
3775
|
+
? `signaling=${pc.signalingState}, ice=${pc.iceConnectionState}, connection=${pc.connectionState}`
|
|
3776
|
+
: 'no peer connection';
|
|
3777
|
+
}
|
|
3711
3778
|
send(data) {
|
|
3712
3779
|
if (this.canSend()) {
|
|
3713
3780
|
this.dcClient?.send(data);
|
|
@@ -4529,7 +4596,8 @@ class UnrealEffects {
|
|
|
4529
4596
|
mergeMap(() => EMPTY),
|
|
4530
4597
|
// ⏱️ Only actual timeout (or real upstream error) ends up here.
|
|
4531
4598
|
catchError(() => {
|
|
4532
|
-
const
|
|
4599
|
+
const details = `${document.visibilityState}; ${this.webRtcPlayerService.describeConnectionState()}`;
|
|
4600
|
+
const error = `⏱️ DataChannel connection timeout ${this.dataChannelConnectionTimeout}ms (${details}).`;
|
|
4533
4601
|
Logger.error(error);
|
|
4534
4602
|
const body = {
|
|
4535
4603
|
awsInstance,
|
|
@@ -4553,7 +4621,7 @@ class UnrealEffects {
|
|
|
4553
4621
|
// map response to the destroy action that ngrx will dispatch
|
|
4554
4622
|
map$1(() => disconnectStream({
|
|
4555
4623
|
reason: DisconnectReason.DataChannelTimeout,
|
|
4556
|
-
message:
|
|
4624
|
+
message: `Data Channel Timeout (${details})`,
|
|
4557
4625
|
})));
|
|
4558
4626
|
}));
|
|
4559
4627
|
}));
|
|
@@ -4585,8 +4653,28 @@ class UnrealEffects {
|
|
|
4585
4653
|
this.webRtcPlayerService.releaseRemoteConnections(reason);
|
|
4586
4654
|
}), filter(({ reason }) => reason === DisconnectReason.DropConnection), map$1(() => dropConnection()));
|
|
4587
4655
|
});
|
|
4588
|
-
|
|
4589
|
-
|
|
4656
|
+
/**
|
|
4657
|
+
* Restarts signalling after a DataChannelTimeout, a limited number of times.
|
|
4658
|
+
*
|
|
4659
|
+
* The counter is the index inside one restart window. A window opens on load,
|
|
4660
|
+
* on a successful data channel, on the user's `startStream` and on a scene
|
|
4661
|
+
* destroy, so only consecutive failures count. Once the limit for the current
|
|
4662
|
+
* tab visibility is reached, `establishingConnection` is cleared and the
|
|
4663
|
+
* resume popup shows.
|
|
4664
|
+
*/
|
|
4665
|
+
this.restartAfterDataChannelTimeout$ = createEffect(() => {
|
|
4666
|
+
const timeoutDrops$ = this.actions$.pipe(ofType(setCirrusDisconnected), withLatestFrom(this.store.select(unrealFeature.selectDisconnectReason)), filter(([, reason]) => reason === DisconnectReason.DataChannelTimeout));
|
|
4667
|
+
return this.actions$.pipe(ofType(dataChannelConnected, startStream, destroyUnrealScene), startWith(null), switchMap(() => timeoutDrops$.pipe(map$1((_, attempt) => {
|
|
4668
|
+
const limit = document.hidden
|
|
4669
|
+
? DATA_CHANNEL_TIMEOUT_RESTARTS.hidden
|
|
4670
|
+
: DATA_CHANNEL_TIMEOUT_RESTARTS.visible;
|
|
4671
|
+
if (attempt < limit) {
|
|
4672
|
+
Logger.warn(`[Restart] DataChannelTimeout, restart ${attempt + 1}/${limit}`);
|
|
4673
|
+
return initSignalling();
|
|
4674
|
+
}
|
|
4675
|
+
Logger.error(`[Restart] DataChannelTimeout, giving up after ${attempt} restarts`);
|
|
4676
|
+
return setEstablishingConnection({ value: false });
|
|
4677
|
+
}), takeWhile((action) => action.type === initSignalling.type, true))));
|
|
4590
4678
|
});
|
|
4591
4679
|
this.showUnrealError$ = createEffect(() => {
|
|
4592
4680
|
return this.actions$.pipe(ofType(showUnrealErrorMessage), map$1(({ code }) => getRtcErrorMessage(code)), distinctUntilChanged(), filter(Truthy), exhaustMap((content) => this.dialog.open(UnrealErrorModalComponent, {
|
|
@@ -4989,7 +5077,7 @@ const floatToSmoothPercents = () => (source) => source.pipe(timestamp(), startWi
|
|
|
4989
5077
|
*/
|
|
4990
5078
|
const smoothTransition = (start, end, duration) => {
|
|
4991
5079
|
const startTime = animationFrameScheduler.now();
|
|
4992
|
-
return interval(0, animationFrameScheduler).pipe(map(() => clampf(0, 1, (animationFrameScheduler.now() - startTime) / duration)), distinctUntilChanged(), takeWhile((fraction) => fraction < 1, true), map((fraction) => lerp(start, end, fraction)));
|
|
5080
|
+
return interval(0, animationFrameScheduler).pipe(map(() => clampf(0, 1, (animationFrameScheduler.now() - startTime) / duration)), distinctUntilChanged(), takeWhile$1((fraction) => fraction < 1, true), map((fraction) => lerp(start, end, fraction)));
|
|
4993
5081
|
};
|
|
4994
5082
|
|
|
4995
5083
|
const getImageFromVideoStream = (takeSizeFrom = 'video', imageOutput, sizes) => {
|
|
@@ -6765,6 +6853,56 @@ class CounterWindow {
|
|
|
6765
6853
|
}
|
|
6766
6854
|
}
|
|
6767
6855
|
|
|
6856
|
+
/**
|
|
6857
|
+
* Turns one `stream.sample` window of 250 ms stats reads into its route
|
|
6858
|
+
* fields: the runs of reads that shared a selected pair, and the description
|
|
6859
|
+
* of every pair this session has not described yet.
|
|
6860
|
+
*
|
|
6861
|
+
* One per session, because the described refs are that session's: the
|
|
6862
|
+
* collector files each session separately, so every file must describe the
|
|
6863
|
+
* routes it references.
|
|
6864
|
+
*
|
|
6865
|
+
* Only the reads that are there are reported. A read without a selected pair
|
|
6866
|
+
* closes the run, and nothing is interpolated across it — the server sees the
|
|
6867
|
+
* gap between two segments and keeps it unattributed.
|
|
6868
|
+
*/
|
|
6869
|
+
class RouteLog {
|
|
6870
|
+
constructor() {
|
|
6871
|
+
this.described = new Set();
|
|
6872
|
+
}
|
|
6873
|
+
take(samples) {
|
|
6874
|
+
const routeSegments = [];
|
|
6875
|
+
const routes = {};
|
|
6876
|
+
let open = null;
|
|
6877
|
+
for (const { aggregatedStats } of samples) {
|
|
6878
|
+
const pair = aggregatedStats.selectedPair;
|
|
6879
|
+
if (!pair) {
|
|
6880
|
+
open = null;
|
|
6881
|
+
continue;
|
|
6882
|
+
}
|
|
6883
|
+
const ts = pair.timestamp === undefined ? null : Math.round(pair.timestamp);
|
|
6884
|
+
const rx = pair.bytesReceived ?? null;
|
|
6885
|
+
const tx = pair.bytesSent ?? null;
|
|
6886
|
+
if (open?.ref === pair.ref) {
|
|
6887
|
+
open.ts[1] = ts;
|
|
6888
|
+
open.rx[1] = rx;
|
|
6889
|
+
open.tx[1] = tx;
|
|
6890
|
+
continue;
|
|
6891
|
+
}
|
|
6892
|
+
open = { ref: pair.ref, ts: [ts, ts], rx: [rx, rx], tx: [tx, tx] };
|
|
6893
|
+
routeSegments.push(open);
|
|
6894
|
+
if (!this.described.has(pair.ref)) {
|
|
6895
|
+
this.described.add(pair.ref);
|
|
6896
|
+
routes[pair.ref] = { local: pair.local, remote: pair.remote };
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
return {
|
|
6900
|
+
...(routeSegments.length ? { routeSegments } : {}),
|
|
6901
|
+
...(Object.keys(routes).length ? { routes } : {}),
|
|
6902
|
+
};
|
|
6903
|
+
}
|
|
6904
|
+
}
|
|
6905
|
+
|
|
6768
6906
|
/**
|
|
6769
6907
|
* Telemetry for the WebRTC stream lifecycle.
|
|
6770
6908
|
*
|
|
@@ -7183,6 +7321,7 @@ class StreamStatusTelemetryService2 {
|
|
|
7183
7321
|
seq: 0,
|
|
7184
7322
|
sub,
|
|
7185
7323
|
bytes: new CounterWindow(),
|
|
7324
|
+
routes: new RouteLog(),
|
|
7186
7325
|
qpBad: false,
|
|
7187
7326
|
fpsLow: false,
|
|
7188
7327
|
maxFpsTarget: UNLIMITED_REFERENCE_FPS,
|
|
@@ -7190,6 +7329,7 @@ class StreamStatusTelemetryService2 {
|
|
|
7190
7329
|
handshakeFlow: null,
|
|
7191
7330
|
commandCount: 0,
|
|
7192
7331
|
times,
|
|
7332
|
+
openPhaseIdx: 0,
|
|
7193
7333
|
};
|
|
7194
7334
|
// Resolve the wire session id from the stream's connection id, then flush
|
|
7195
7335
|
// everything queued so far. Subscribed FIRST so the id is in place before
|
|
@@ -7345,6 +7485,10 @@ class StreamStatusTelemetryService2 {
|
|
|
7345
7485
|
? errorMessage.message
|
|
7346
7486
|
: undefined;
|
|
7347
7487
|
const includeGranular = granularReason && granularReason !== DisconnectReason.None;
|
|
7488
|
+
const openPhase = this.phaseCatalog[session.openPhaseIdx];
|
|
7489
|
+
if (openPhase) {
|
|
7490
|
+
this.emitPhaseEnded(openPhase, session.openPhaseIdx, null, true);
|
|
7491
|
+
}
|
|
7348
7492
|
this.emitLifecycle('stream.session_ended', {
|
|
7349
7493
|
sessionId: id,
|
|
7350
7494
|
reason,
|
|
@@ -7408,9 +7552,10 @@ class StreamStatusTelemetryService2 {
|
|
|
7408
7552
|
* phaseNo (derived from the catalog index), id, color, milestones,
|
|
7409
7553
|
* params, overlays. No display text on the wire: the viewer's
|
|
7410
7554
|
* vocabulary maps every id back to a human label, and boundary labels
|
|
7411
|
-
* are derived from neighbor phases.
|
|
7555
|
+
* are derived from neighbor phases. `incomplete` marks the phase the
|
|
7556
|
+
* session ended in, sent from `endSession` instead of from its closer.
|
|
7412
7557
|
*/
|
|
7413
|
-
emitPhaseEnded(bucket, bucketIdx, extrasValue) {
|
|
7558
|
+
emitPhaseEnded(bucket, bucketIdx, extrasValue, incomplete = false) {
|
|
7414
7559
|
if (!this.session)
|
|
7415
7560
|
return;
|
|
7416
7561
|
const now = performance.now();
|
|
@@ -7427,7 +7572,15 @@ class StreamStatusTelemetryService2 {
|
|
|
7427
7572
|
milestones,
|
|
7428
7573
|
params,
|
|
7429
7574
|
overlays,
|
|
7575
|
+
...(incomplete && { incomplete: true }),
|
|
7430
7576
|
});
|
|
7577
|
+
this.closePhase(bucketIdx);
|
|
7578
|
+
}
|
|
7579
|
+
/** Move the open phase past `bucketIdx` — phases close in catalog order. */
|
|
7580
|
+
closePhase(bucketIdx) {
|
|
7581
|
+
if (!this.session)
|
|
7582
|
+
return;
|
|
7583
|
+
this.session.openPhaseIdx = Math.max(this.session.openPhaseIdx, bucketIdx + 1);
|
|
7431
7584
|
}
|
|
7432
7585
|
/**
|
|
7433
7586
|
* Emit the region / signaling / webrtc phases for an ADOPTED connection.
|
|
@@ -7518,6 +7671,7 @@ class StreamStatusTelemetryService2 {
|
|
|
7518
7671
|
params: toPhaseParams({ ...extras, prefetched: true }),
|
|
7519
7672
|
overlays: [],
|
|
7520
7673
|
});
|
|
7674
|
+
this.closePhase(bucketIdx);
|
|
7521
7675
|
}
|
|
7522
7676
|
/** Streamer identity params for the adopted signaling phase — mirrors the
|
|
7523
7677
|
* `ssinfo-received` closer's shape (client / instanceName / …ssData /
|
|
@@ -7612,6 +7766,7 @@ class StreamStatusTelemetryService2 {
|
|
|
7612
7766
|
width: latest?.frameWidth,
|
|
7613
7767
|
height: latest?.frameHeight,
|
|
7614
7768
|
},
|
|
7769
|
+
...s.routes.take(samples),
|
|
7615
7770
|
});
|
|
7616
7771
|
this.evaluateAnomalies(fps, qp);
|
|
7617
7772
|
}
|
|
@@ -7873,7 +8028,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImpor
|
|
|
7873
8028
|
/** Compact one side of an ICE candidate pair to `"type (url)"` or just
|
|
7874
8029
|
* `type` (host candidates have no URL). */
|
|
7875
8030
|
function fmtCandidate(c) {
|
|
7876
|
-
if (!c) {
|
|
8031
|
+
if (!c?.type) {
|
|
7877
8032
|
return null;
|
|
7878
8033
|
}
|
|
7879
8034
|
return c.url ? `${c.type} (${c.url})` : c.type;
|
|
@@ -8646,17 +8801,42 @@ class VideoStatsComponent {
|
|
|
8646
8801
|
value: this.stats()?.aggregatedStats.VideoEncoderQP ?? 0,
|
|
8647
8802
|
}), /* @ts-ignore */
|
|
8648
8803
|
...(ngDevMode ? [{ debugName: "videoQP" }] : /* istanbul ignore next */ []));
|
|
8649
|
-
|
|
8804
|
+
/** What the window shows: the raw stats plus the few display-only values
|
|
8805
|
+
* derived here. Shared by the text rows and the graphs. */
|
|
8806
|
+
this.view = computed(() => {
|
|
8650
8807
|
const event = this.stats();
|
|
8651
|
-
if (!
|
|
8808
|
+
if (!event) {
|
|
8652
8809
|
return undefined;
|
|
8653
8810
|
}
|
|
8654
|
-
const
|
|
8655
|
-
|
|
8811
|
+
const stats = event.aggregatedStats;
|
|
8812
|
+
const pair = stats.selectedPair;
|
|
8813
|
+
const view = {
|
|
8814
|
+
...stats,
|
|
8656
8815
|
quality: event.quality,
|
|
8816
|
+
MBytesReceived: typeof stats.bytesReceived === 'number'
|
|
8817
|
+
? +(stats.bytesReceived / 1e6).toFixed(2)
|
|
8818
|
+
: undefined,
|
|
8819
|
+
// Only the stable part of the route: counters, stats timestamp and ref
|
|
8820
|
+
// change on every tick and would make the row jump.
|
|
8821
|
+
selectedPair: pair && {
|
|
8822
|
+
local: {
|
|
8823
|
+
type: pair.local.type,
|
|
8824
|
+
protocol: pair.local.protocol,
|
|
8825
|
+
url: pair.local.url,
|
|
8826
|
+
},
|
|
8827
|
+
remote: { type: pair.remote.type, protocol: pair.remote.protocol },
|
|
8828
|
+
},
|
|
8657
8829
|
};
|
|
8830
|
+
return view;
|
|
8831
|
+
}, /* @ts-ignore */
|
|
8832
|
+
...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
|
|
8833
|
+
this.videoStatus = computed(() => {
|
|
8834
|
+
const view = this.view();
|
|
8835
|
+
if (!this.isDevMode || !view) {
|
|
8836
|
+
return undefined;
|
|
8837
|
+
}
|
|
8658
8838
|
return this.fields
|
|
8659
|
-
.map((key) => ({ key, value:
|
|
8839
|
+
.map((key) => ({ key, value: view[key] }))
|
|
8660
8840
|
.filter((r) => !this.elementsToShow().includes(r.key));
|
|
8661
8841
|
}, /* @ts-ignore */
|
|
8662
8842
|
...(ngDevMode ? [{ debugName: "videoStatus" }] : /* istanbul ignore next */ []));
|
|
@@ -8691,9 +8871,7 @@ class VideoStatsComponent {
|
|
|
8691
8871
|
Math.floor(Math.random() * 16777216)
|
|
8692
8872
|
.toString(16)
|
|
8693
8873
|
.padStart(6, '0'),
|
|
8694
|
-
stat: computed(() => ({
|
|
8695
|
-
value: this.stats()?.aggregatedStats[key],
|
|
8696
|
-
})),
|
|
8874
|
+
stat: computed(() => ({ value: this.view()?.[key] })),
|
|
8697
8875
|
}));
|
|
8698
8876
|
this.graphList = computed(() => this.graphListItems.filter((r) => this.elementsToShow().includes(r.key)), /* @ts-ignore */
|
|
8699
8877
|
...(ngDevMode ? [{ debugName: "graphList" }] : /* istanbul ignore next */ []));
|
|
@@ -9204,5 +9382,5 @@ function provideUnrealModule() {
|
|
|
9204
9382
|
* Generated bundle index. Do not edit.
|
|
9205
9383
|
*/
|
|
9206
9384
|
|
|
9207
|
-
export { AFKService, ALL_KEYBOARD_KEYS, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, COLOR_CODES, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, Falsy, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, HEXtoRGB, HSVtoRGB, IME_PROCESS_KEY, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, Logger, MINIMAL_FPS, MODIFIER_KEY_CODES, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RGBtoHEX, RGBtoHSV, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, STREAM_RESOLUTION_LIMITS, SafePipe, Semaphore, SignallingService, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, Truthy, UnrealAfkSlotDirective, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealKeyCode, UnrealResumeSlotDirective, UnrealSceneComponent, UnrealStatusMessage, UnrealStatusSlotDirective, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, clampStreamResolution, clampf, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, fitIntoRectangle, floatToPercents, floatToSmoothPercents, forceResizeViewport, fpIsASameAsB, fromResizeObserver, generateUuid, getActiveUrl, getCanvasCached, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, getSnapshot, iceConnectionFailed, initSignalling, instanceReady, inverseLerp, isAppOwned, isMetaboxMode, keepMaxUntilReset, lerp, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, resolveSkin, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, startStream, streamAdopted, tapLog, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
9385
|
+
export { AFKService, ALL_KEYBOARD_KEYS, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, COLOR_CODES, CommandTelemetryService, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DATA_CHANNEL_TIMEOUT_RESTARTS, DEBOUNCE_TO_MANY_RESIZE_CALLS, DEFAULT_AFK_TIMEOUT, DEFAULT_AFK_TIMEOUT_PERIOD, DESTROY_DISCONNECT_REASONS, DevModeService, DisconnectReason, FULL_HD_HEIGHT, FULL_HD_WIDTH, Falsy, FileHandlerService, FileReceiverService, FpsMonitorService, FreezeFrameComponent, FreezeFrameService, HEXtoRGB, HSVtoRGB, IME_PROCESS_KEY, INNER_RETRY_LIMIT, INSTANCE_READY_TIMEOUT, IceCandidateHandler, ImageLoadingSrcComponent, InputOptions, InputService, InstanceReadyHandler, InstanceReservedHandler, IntroSrcComponent, LatencyTimings, LoadingProgressService, Logger, MINIMAL_FPS, MODIFIER_KEY_CODES, MessageType, MouseButton, MouseButtonsMask, OUTER_RETRY_DELAY, OnCloseHandler, OnErrorHandler, OnMessageHandler, OnOpenHandler, OrchestrationMessageTypes, POLLING_TIME, PlayerCountHandler, RECONNECT_CONFIG, REGION_PING_ACCURATE_SAMPLES, REGION_PING_BACKGROUND_REFRESH_DELAY_MS, REGION_PING_CACHE_KEY, REGION_PING_CACHE_TTL_MS, REGION_PING_WARMUP_DEADLINE_MS, RGBtoHEX, RGBtoHSV, RegionsPingService, SAME_SIZE_THRESHOLD, SCREEN_LOCKER_CONTAINER_ID, SIGNALLING_PERCENT_VALUE, SSInfoHandler, STREAMING_AUDIO_ID, STREAMING_VIDEO_ID, STREAM_RESOLUTION_LIMITS, SafePipe, Semaphore, SignallingService, StatGraphComponent, StreamAdoptionService, StreamStatusTelemetryService, StreamStatusTelemetryService2, SubService, ToClientMessage, Truthy, UnrealAfkSlotDirective, UnrealCallbackService, UnrealCommunicatorService, UnrealEffects, UnrealKeyCode, UnrealResumeSlotDirective, UnrealSceneComponent, UnrealStatusMessage, UnrealStatusSlotDirective, VideoService, VideoStatsComponent, WSCloseCode_CIRRUS_ABNORMAL_CLOSURE, WSCloseCode_CIRRUS_MAX_PLAYERS_ERROR, WSCloseCode_CIRRUS_PLAYER_DISCONNECTED, WSCloseCode_CIRRUS_STREAMER_KIKED_PLAYER, WSCloseCode_FORCE_CIRRUS_CLOSE, WSCloseCode_NORMAL_AFK_TIMEOUT, WSCloseCode_NORMAL_CLOSURE, WSCloseCode_NORMAL_MANUAL_DISCONNECT, WSCloseCode_UNKNOWN, WSCloseCodes, WS_OPEN_STATE, WS_RETRY_PER_INSTANCE, WS_TIMEOUT, WebRtcPlayerService, WebrtcErrorModalComponent, abortEstablishingConnection, answerSent, changeStatusMainVideoOnScene, changeStreamResolutionAction, clampStreamResolution, clampf, commandCompleted, commandStarted, dataChannelConnected, dataChannelReady, decodeData, destroyRemoteConnections, destroyUnrealScene, disconnectStream, dispatchResize, dropConnection, dropDC, dropPC, fitIntoRectangle, floatToPercents, floatToSmoothPercents, forceResizeViewport, fpIsASameAsB, fromResizeObserver, generateUuid, getActiveUrl, getCanvasCached, getImageFromVideoStream, getResizeStudioValues, getResizeValuesUniversal, getRtcErrorMessage, getSnapshot, iceConnectionFailed, initSignalling, instanceReady, inverseLerp, isAppOwned, isMetaboxMode, keepMaxUntilReset, lerp, makeEven, pollingEnded, pollingStarted, provideUnrealModule, provideUnrealState, qpToQuality, reconnectPeer, reconnectPeerFailed, reconnectPeerSuccess, regionPingFetchEnd, regionPingFetchStart, regionResolved, resetAfk, resetConfig, resetDataChannelForReconnect, resetIntroSrc, resetUserInactivityTimeout, resetWarnTimeout, resolveSkin, saveAnalyticsEvent, selectCirrusConnectionId, selectClientAndViewIds, selectCommandProgress, selectFreezeFrameCombinedDataUrl, selectFreezeFrameDataUrl, selectFreezeFrameDataUrlFromVideo, selectFreezeFrameProgressMessageFromVideo, selectIsAutostart, selectIsExistMatchUrls, selectIsFreezeFrameLoading, selectIsFullyConnected, selectIsLoaderScreenVisible, selectIsVideoPlayingAndDataChannelConnected, selectShowLoader, selectShowReconnectPopup, selectSignalingParameters, selectStreamConfig, selectTotalProgress, selectUserInactivityDetected, selectWarnTimeout, setAfkTimerCountdown, setAfkTimerHide, setAfkTimerVisible, setAwsInstance, setCirrusConnected, setCirrusDisconnected, setConfig, setEstablishingConnection, setFreezeFrame, setFreezeFrameFromVideo, setIntroImageSrc, setIntroVideoSrc, setLoadingImageSrc, setLoopBackCommandIsCompleted, setMaxFps, setOrchestrationContext, setOrchestrationMessage, setOrchestrationParameters, setOrchestrationProgress, setSignalingName, setStatusMessage, setStatusPercentSignallingServer, setStreamClientCompanyId, setStreamViewId, setUnrealPlaywrightConfig, setUserInactivityTimeout, setViewportNotReady, setViewportReady, showLoadingProgress, showResumeStreamPopup, showUnrealErrorMessage, showUserInactivityTimeoutPopup, signalingRetryAttempted, startStream, streamAdopted, tapLog, trackMixpanelEvent, trackTelemetryData, uint8ToBase64, unrealCommandCallback, unrealFeature, unrealReducer, updateCirrusInfo, videoAdaptedToContainer, videoStreamInitialized };
|
|
9208
9386
|
//# sourceMappingURL=3dsource-angular-unreal-module.mjs.map
|