@3dsource/angular-unreal-module 0.0.197 → 0.0.200-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.
package/LICENSE
CHANGED
|
File without changes
|
|
@@ -3,7 +3,7 @@ import { ChangeDetectionStrategy, Component, DestroyRef, Directive, ElementRef,
|
|
|
3
3
|
import { JsonPipe, NgOptimizedImage, NgTemplateOutlet } from "@angular/common";
|
|
4
4
|
import { Store, createAction, createFeature, createReducer, createSelector, on, props, provideState } from "@ngrx/store";
|
|
5
5
|
import { Actions, createEffect, ofType, provideEffects } from "@ngrx/effects";
|
|
6
|
-
import { BehaviorSubject, EMPTY, NEVER, Observable, Subject, Subscription, animationFrameScheduler, asyncScheduler, bufferTime, combineLatestWith, concat, concatMap, debounceTime, defer, delay, filter, firstValueFrom, from, fromEvent, ignoreElements, interval, map, merge, mergeMap, of, pairwise, repeat, retry, scan, share, shareReplay, skip, startWith, switchMap, take, takeUntil, takeWhile, tap, throttleTime, throwError, timeout, timer, timestamp, withLatestFrom } from "rxjs";
|
|
6
|
+
import { BehaviorSubject, EMPTY, NEVER, Observable, Subject, Subscription, TimeoutError, animationFrameScheduler, asyncScheduler, bufferTime, combineLatestWith, concat, concatMap, debounceTime, defer, delay, filter, firstValueFrom, from, fromEvent, ignoreElements, interval, map, merge, mergeMap, of, pairwise, repeat, retry, scan, share, shareReplay, skip, startWith, switchMap, take, takeUntil, takeWhile, tap, throttleTime, throwError, timeout, timer, timestamp, withLatestFrom } from "rxjs";
|
|
7
7
|
import { UNREAL_CONFIG } from "@3dsource/angular-unreal-module/config";
|
|
8
8
|
import { catchError, debounceTime as debounceTime$1, distinctUntilChanged, exhaustMap, filter as filter$1, finalize, first, map as map$1, startWith as startWith$1, switchMap as switchMap$1, take as take$1, takeUntil as takeUntil$1, takeWhile as takeWhile$1, tap as tap$1, timeout as timeout$1, withLatestFrom as withLatestFrom$1 } from "rxjs/operators";
|
|
9
9
|
import { v4 } from "uuid";
|
|
@@ -1804,6 +1804,7 @@ const ORCHESTRATION_ERROR_CODES = {
|
|
|
1804
1804
|
};
|
|
1805
1805
|
const RETRY_CODES = /* @__PURE__ */ new Set([ORCHESTRATION_ERROR_CODES.NO_AVAILABLE_INSTANCES.code, ORCHESTRATION_ERROR_CODES.LIMIT_REACHED.code]);
|
|
1806
1806
|
function OrchestrationErrorHandler(msg) {
|
|
1807
|
+
this.onOrchestrationError$.next(msg.payload);
|
|
1807
1808
|
if (RETRY_CODES.has(msg.payload.code)) {
|
|
1808
1809
|
const errorInfo = Object.values(ORCHESTRATION_ERROR_CODES).find(({ code }) => code === msg.payload.code);
|
|
1809
1810
|
this.store.dispatch(setOrchestrationMessage({ message: errorInfo?.description }));
|
|
@@ -1831,6 +1832,10 @@ var SignallingService = class SignallingService extends SubService {
|
|
|
1831
1832
|
this.onWebRtcIce$ = new Subject();
|
|
1832
1833
|
this.onWebRtcAnswer$ = new Subject();
|
|
1833
1834
|
this.onInstanceReady$ = new Subject();
|
|
1835
|
+
this.onRequestStreamSent$ = new Subject();
|
|
1836
|
+
this.onOrchestrationError$ = new Subject();
|
|
1837
|
+
this.onCirrusClose$ = new Subject();
|
|
1838
|
+
this.onPollResponse$ = new Subject();
|
|
1834
1839
|
this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
|
|
1835
1840
|
this.ws = null;
|
|
1836
1841
|
this.controlWs = null;
|
|
@@ -1978,8 +1983,13 @@ var SignallingService = class SignallingService extends SubService {
|
|
|
1978
1983
|
}));
|
|
1979
1984
|
}
|
|
1980
1985
|
pollLoop(url) {
|
|
1981
|
-
|
|
1986
|
+
let sentAt = 0;
|
|
1987
|
+
return defer(() => {
|
|
1988
|
+
sentAt = performance.now();
|
|
1989
|
+
return this.httpClient.get(url);
|
|
1990
|
+
}).pipe(timeout(POLLING_TIME), tap$1((data) => this.onPollResponse$.next(pollResponse(200, data, sentAt))), catchError((err) => {
|
|
1982
1991
|
const status = err?.status;
|
|
1992
|
+
this.onPollResponse$.next(pollResponse(typeof status === "number" && status > 0 ? status : err instanceof TimeoutError ? "timeout" : "network", null, sentAt));
|
|
1983
1993
|
if (typeof status === "number" && status >= 400 && status < 500 && status !== 429) {
|
|
1984
1994
|
Logger.warn(`Polling HARD error on ${url} (HTTP ${status}); rotating URL`);
|
|
1985
1995
|
return throwError(() => err);
|
|
@@ -2122,6 +2132,7 @@ var SignallingService = class SignallingService extends SubService {
|
|
|
2122
2132
|
context
|
|
2123
2133
|
}
|
|
2124
2134
|
});
|
|
2135
|
+
this.onRequestStreamSent$.next(this.correlationId);
|
|
2125
2136
|
}
|
|
2126
2137
|
isConnected() {
|
|
2127
2138
|
return !!(this.ws && this.ws.readyState === 1);
|
|
@@ -2161,6 +2172,15 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
2161
2172
|
}],
|
|
2162
2173
|
ctorParameters: () => []
|
|
2163
2174
|
});
|
|
2175
|
+
function pollResponse(status, data, sentAt) {
|
|
2176
|
+
return {
|
|
2177
|
+
status,
|
|
2178
|
+
ms: Math.round(performance.now() - sentAt),
|
|
2179
|
+
ready: !!data?.signallingServer,
|
|
2180
|
+
percent: data?.info?.percent ?? null,
|
|
2181
|
+
message: data?.mm_message || data?.error || null
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2164
2184
|
function finite(value) {
|
|
2165
2185
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2166
2186
|
}
|
|
@@ -2214,10 +2234,16 @@ function iceSnapshot(stats) {
|
|
|
2214
2234
|
};
|
|
2215
2235
|
}
|
|
2216
2236
|
var VideoService = class VideoService extends SubService {
|
|
2237
|
+
get video() {
|
|
2238
|
+
return this.video$.value;
|
|
2239
|
+
}
|
|
2240
|
+
set video(video) {
|
|
2241
|
+
this.video$.next(video);
|
|
2242
|
+
}
|
|
2217
2243
|
constructor() {
|
|
2218
2244
|
super();
|
|
2219
2245
|
this.isVideoPlaying = this.store.selectSignal(unrealFeature.selectIsVideoPlaying);
|
|
2220
|
-
this.video = null;
|
|
2246
|
+
this.video$ = new BehaviorSubject(null);
|
|
2221
2247
|
this.audio = null;
|
|
2222
2248
|
this.container = null;
|
|
2223
2249
|
this.latencyTestTimings = new LatencyTimings();
|
|
@@ -2568,7 +2594,7 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2568
2594
|
const pc = this.pcClient;
|
|
2569
2595
|
if (!pc) return;
|
|
2570
2596
|
await this.setupTransceiversAsync(pc);
|
|
2571
|
-
this.
|
|
2597
|
+
this.createDataChannel(pc, "UnrealDcConnection", { ordered: true });
|
|
2572
2598
|
await this.createOffer(pc);
|
|
2573
2599
|
} catch (e) {
|
|
2574
2600
|
Logger.error("Failed to create WebRTC offer", e);
|
|
@@ -2581,15 +2607,17 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2581
2607
|
setupPeerConnection(config) {
|
|
2582
2608
|
this.closePC();
|
|
2583
2609
|
Logger.log("Creating new RTCPeerConnection with config");
|
|
2584
|
-
this.
|
|
2585
|
-
|
|
2610
|
+
this.ownPeerConnection(new RTCPeerConnection(config));
|
|
2611
|
+
}
|
|
2612
|
+
ownPeerConnection(pc) {
|
|
2613
|
+
this.pcClient = pc;
|
|
2614
|
+
this.handlePeerConnectionEvents(pc);
|
|
2586
2615
|
this.disconnect$.pipe(take(1), takeUntil$1(this.destroy$)).subscribe(() => {
|
|
2587
2616
|
this.closePC();
|
|
2588
2617
|
});
|
|
2589
2618
|
}
|
|
2590
2619
|
createDataChannel(pc, label, options) {
|
|
2591
|
-
|
|
2592
|
-
return this.setupDataChannelCallbacks(datachannel);
|
|
2620
|
+
this.ownDataChannel(pc.createDataChannel(label, options));
|
|
2593
2621
|
}
|
|
2594
2622
|
handleCandidateFromServer(iceCandidate) {
|
|
2595
2623
|
try {
|
|
@@ -2709,23 +2737,19 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2709
2737
|
return true;
|
|
2710
2738
|
}
|
|
2711
2739
|
adopt(pc, dc, stream, config, dcBuffer) {
|
|
2712
|
-
this.closePC();
|
|
2713
|
-
this.pcClient = pc;
|
|
2714
2740
|
this.cfg = config ?? {};
|
|
2715
|
-
this.
|
|
2741
|
+
this.videoService.create();
|
|
2716
2742
|
pc.onicecandidate = null;
|
|
2717
2743
|
pc.ondatachannel = null;
|
|
2718
2744
|
pc.ontrack = null;
|
|
2719
|
-
this.
|
|
2720
|
-
this.
|
|
2745
|
+
this.closePC();
|
|
2746
|
+
this.ownPeerConnection(pc);
|
|
2747
|
+
this.remoteDescriptionSet = true;
|
|
2748
|
+
this.videoService.attachVideoStream(stream, pc);
|
|
2721
2749
|
dc.onmessage = null;
|
|
2722
2750
|
dc.onopen = null;
|
|
2723
2751
|
dc.onclose = null;
|
|
2724
|
-
this.
|
|
2725
|
-
for (const buffered of dcBuffer) this.onDataChannelMessage$.next(buffered);
|
|
2726
|
-
this.onDataChannelConnected();
|
|
2727
|
-
this.videoService.create();
|
|
2728
|
-
this.videoService.attachVideoStream(stream, pc);
|
|
2752
|
+
this.ownDataChannel(dc, dcBuffer);
|
|
2729
2753
|
}
|
|
2730
2754
|
setConfig(parOptions = {}) {
|
|
2731
2755
|
const urlParams = new URLSearchParams(window.location.search);
|
|
@@ -2778,9 +2802,7 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2778
2802
|
type: "iceCandidate",
|
|
2779
2803
|
candidate
|
|
2780
2804
|
}));
|
|
2781
|
-
fromEvent(pcClient, "datachannel").pipe(tap((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$1(merge(this.disconnect$, this.destroy$))).subscribe((datachannel) =>
|
|
2782
|
-
this.dcClient = this.setupDataChannelCallbacks(datachannel);
|
|
2783
|
-
});
|
|
2805
|
+
fromEvent(pcClient, "datachannel").pipe(tap((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$1(merge(this.disconnect$, this.destroy$))).subscribe((datachannel) => this.ownDataChannel(datachannel));
|
|
2784
2806
|
const onTrack$ = fromEvent(pcClient, "track").pipe(takeUntil$1(merge(this.disconnect$, this.destroy$)), filter$1((e) => !!e.track), tap((e) => Logger.info("Got track - " + e.track.kind + " id=" + e.track.id + " readyState=" + e.track.readyState)), share());
|
|
2785
2807
|
onTrack$.pipe(filter$1((e) => e.track.kind === "audio"), map$1((e) => e.streams[0])).subscribe((stream) => this.videoService.attachAudioStream(stream));
|
|
2786
2808
|
onTrack$.pipe(filter$1((e) => e.track.kind === "video"), map$1((e) => e.streams[0]), tap(() => Logger.info("Set video source from video track ontrack."))).subscribe((stream) => {
|
|
@@ -2818,7 +2840,8 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2818
2840
|
} else pc.addTransceiver("audio", { direction: "recvonly" });
|
|
2819
2841
|
}
|
|
2820
2842
|
}
|
|
2821
|
-
|
|
2843
|
+
ownDataChannel(datachannel, replay = []) {
|
|
2844
|
+
this.dcClient = datachannel;
|
|
2822
2845
|
try {
|
|
2823
2846
|
Logger.log(`[DATACHANNEL] Setting up datachannel "${datachannel.label}" callbacks...`);
|
|
2824
2847
|
datachannel.binaryType = "arraybuffer";
|
|
@@ -2833,14 +2856,14 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
|
|
|
2833
2856
|
const message = `[DATACHANNEL] Data channel "${datachannel.label}" error. id:${datachannel.id}`;
|
|
2834
2857
|
Logger.error(message, e);
|
|
2835
2858
|
});
|
|
2836
|
-
fromEvent(datachannel, "open").pipe(tap(() => {
|
|
2837
|
-
Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
|
|
2838
|
-
}), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe(() => this.onDataChannelConnected());
|
|
2839
2859
|
fromEvent(datachannel, "message").pipe(map$1((e) => e.data), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((data) => this.onDataChannelMessage$.next(data));
|
|
2840
|
-
|
|
2860
|
+
for (const buffered of replay) this.onDataChannelMessage$.next(buffered);
|
|
2861
|
+
(datachannel.readyState === "open" ? of(null) : fromEvent(datachannel, "open").pipe(take(1))).pipe(tap(() => {
|
|
2862
|
+
Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
|
|
2863
|
+
}), switchMap$1(() => this.videoService.video$.pipe(filter$1(Truthy), take(1))), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe(() => this.onDataChannelConnected());
|
|
2841
2864
|
} catch (e) {
|
|
2842
2865
|
Logger.warn("[DATACHANNEL] Datachannel setup caused an exception: ", e);
|
|
2843
|
-
|
|
2866
|
+
this.dcClient = null;
|
|
2844
2867
|
}
|
|
2845
2868
|
}
|
|
2846
2869
|
onDataChannelConnected() {
|
|
@@ -2905,6 +2928,13 @@ var StreamAdoptionService = class StreamAdoptionService {
|
|
|
2905
2928
|
this.store = inject(Store);
|
|
2906
2929
|
this.signalling = inject(SignallingService);
|
|
2907
2930
|
this.webRtc = inject(WebRtcPlayerService);
|
|
2931
|
+
this.report$ = new Subject();
|
|
2932
|
+
this.waitStarted$ = new Subject();
|
|
2933
|
+
this.waiting = null;
|
|
2934
|
+
}
|
|
2935
|
+
waitingReport() {
|
|
2936
|
+
const w = this.waiting;
|
|
2937
|
+
return w ? reportOf(w.parked, "waiting", w.sincePerf) : null;
|
|
2908
2938
|
}
|
|
2909
2939
|
awaitAndAdopt$() {
|
|
2910
2940
|
return defer(() => {
|
|
@@ -2914,32 +2944,47 @@ var StreamAdoptionService = class StreamAdoptionService {
|
|
|
2914
2944
|
return of(false);
|
|
2915
2945
|
}
|
|
2916
2946
|
if (isAppOwned(slot)) return of(false);
|
|
2917
|
-
if (slot.state !== "connecting") return of(this.
|
|
2947
|
+
if (slot.state !== "connecting") return of(this.settle(slot, null));
|
|
2918
2948
|
Logger.info("[Adoption] Prefetch in progress — waiting before connecting");
|
|
2949
|
+
this.waitStarted$.next();
|
|
2919
2950
|
return this.awaitSettled$(slot);
|
|
2920
2951
|
});
|
|
2921
2952
|
}
|
|
2922
2953
|
awaitSettled$(parked) {
|
|
2923
2954
|
return new Observable((subscriber) => {
|
|
2924
2955
|
let done = false;
|
|
2956
|
+
const sincePerf = performance.now();
|
|
2957
|
+
this.waiting = {
|
|
2958
|
+
parked,
|
|
2959
|
+
sincePerf
|
|
2960
|
+
};
|
|
2925
2961
|
parked.onSettled(() => {
|
|
2926
2962
|
if (done) return;
|
|
2927
2963
|
done = true;
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
adopted = this.tryAdoptOrTakeSlot();
|
|
2931
|
-
} catch (e) {
|
|
2932
|
-
Logger.error("[Adoption] Adoption threw — falling back", e);
|
|
2933
|
-
this.takeSlot();
|
|
2934
|
-
}
|
|
2935
|
-
subscriber.next(adopted);
|
|
2964
|
+
this.waiting = null;
|
|
2965
|
+
subscriber.next(this.settle(parked, sincePerf));
|
|
2936
2966
|
subscriber.complete();
|
|
2937
2967
|
});
|
|
2938
2968
|
return () => {
|
|
2969
|
+
if (done) return;
|
|
2939
2970
|
done = true;
|
|
2971
|
+
this.waiting = null;
|
|
2972
|
+
this.report$.next(reportOf(parked, "abandoned", sincePerf));
|
|
2940
2973
|
};
|
|
2941
2974
|
});
|
|
2942
2975
|
}
|
|
2976
|
+
settle(parked, sincePerf) {
|
|
2977
|
+
let adopted = false;
|
|
2978
|
+
try {
|
|
2979
|
+
adopted = this.tryAdoptOrTakeSlot();
|
|
2980
|
+
} catch (e) {
|
|
2981
|
+
Logger.error("[Adoption] Adoption threw — falling back", e);
|
|
2982
|
+
this.takeSlot();
|
|
2983
|
+
}
|
|
2984
|
+
const outcome = adopted ? "adopted" : parked.state === "failed" ? "failed" : parked.state === "ready" ? "dead" : null;
|
|
2985
|
+
if (outcome) this.report$.next(reportOf(parked, outcome, sincePerf));
|
|
2986
|
+
return adopted;
|
|
2987
|
+
}
|
|
2943
2988
|
tryAdoptOrTakeSlot() {
|
|
2944
2989
|
const adopted = this.tryAdopt();
|
|
2945
2990
|
if (!adopted) this.takeSlot();
|
|
@@ -2966,7 +3011,7 @@ var StreamAdoptionService = class StreamAdoptionService {
|
|
|
2966
3011
|
if (!slot || isAppOwned(slot)) return false;
|
|
2967
3012
|
const parked = slot;
|
|
2968
3013
|
if (parked.state !== "ready") return false;
|
|
2969
|
-
if (!parked.ws || parked.ws.readyState !== 1 || !parked.pc || !parked.dc || !parked.stream) {
|
|
3014
|
+
if (parked.deadReason || !parked.ws || parked.ws.readyState !== 1 || !parked.pc || DEAD_PC_STATES.has(parked.pc.connectionState) || !parked.dc || DEAD_DC_STATES.has(parked.dc.readyState) || !parked.stream) {
|
|
2970
3015
|
Logger.warn("[Adoption] Parked stream is ready but transport is not live");
|
|
2971
3016
|
return false;
|
|
2972
3017
|
}
|
|
@@ -2977,6 +3022,7 @@ var StreamAdoptionService = class StreamAdoptionService {
|
|
|
2977
3022
|
pollingUrl: parked.pollingUrl ?? null,
|
|
2978
3023
|
ssData: parked.ssData ?? null,
|
|
2979
3024
|
streamRequestId: parked.ids?.streamRequestId || null,
|
|
3025
|
+
correlationId: parked.ids?.correlationId || null,
|
|
2980
3026
|
kind: parked.kind ?? null,
|
|
2981
3027
|
instanceName: parked.instanceName ?? null,
|
|
2982
3028
|
eta: parked.eta ?? null,
|
|
@@ -3017,6 +3063,20 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
3017
3063
|
args: [{ providedIn: "root" }]
|
|
3018
3064
|
}]
|
|
3019
3065
|
});
|
|
3066
|
+
const DEAD_PC_STATES = /* @__PURE__ */ new Set(["failed", "closed"]);
|
|
3067
|
+
const DEAD_DC_STATES = /* @__PURE__ */ new Set(["closing", "closed"]);
|
|
3068
|
+
function reportOf(parked, outcome, sincePerf) {
|
|
3069
|
+
return {
|
|
3070
|
+
outcome,
|
|
3071
|
+
failReason: outcome === "dead" ? parked.deadReason ?? "transport-not-live" : parked.failReason ?? null,
|
|
3072
|
+
failError: parked.failError ?? null,
|
|
3073
|
+
waitMs: sincePerf === null ? null : Math.round(performance.now() - sincePerf),
|
|
3074
|
+
kind: parked.kind ?? null,
|
|
3075
|
+
instanceName: parked.instanceName ?? null,
|
|
3076
|
+
streamRequestId: parked.ids?.streamRequestId || null,
|
|
3077
|
+
trail: [...parked.trail ?? []]
|
|
3078
|
+
};
|
|
3079
|
+
}
|
|
3020
3080
|
var UnrealCallbackService = class UnrealCallbackService {
|
|
3021
3081
|
constructor() {
|
|
3022
3082
|
this.actions$ = inject(Actions);
|
|
@@ -3539,6 +3599,10 @@ const dropCodes = [
|
|
|
3539
3599
|
function OnCloseHandler(e) {
|
|
3540
3600
|
const message = `WebSocket closed: ${JSON.stringify(e.code)} - ${e.reason}`;
|
|
3541
3601
|
Logger.warn(message);
|
|
3602
|
+
this.onCirrusClose$.next({
|
|
3603
|
+
code: e.code,
|
|
3604
|
+
reason: e.reason
|
|
3605
|
+
});
|
|
3542
3606
|
this.store.dispatch(setCirrusDisconnected());
|
|
3543
3607
|
if (dropCodes.includes(e.code)) this.store.dispatch(dropConnection());
|
|
3544
3608
|
}
|
|
@@ -5152,6 +5216,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5152
5216
|
this.videoService = inject(VideoService);
|
|
5153
5217
|
this.signallingService = inject(SignallingService);
|
|
5154
5218
|
this.webRtcPlayer = inject(WebRtcPlayerService);
|
|
5219
|
+
this.adoption = inject(StreamAdoptionService);
|
|
5155
5220
|
this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
|
|
5156
5221
|
this.destroyRef = inject(DestroyRef);
|
|
5157
5222
|
this.destroy$ = new Subject();
|
|
@@ -5200,6 +5265,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5200
5265
|
id: "region-resolving",
|
|
5201
5266
|
color: "#61afef",
|
|
5202
5267
|
milestones: [
|
|
5268
|
+
{
|
|
5269
|
+
id: "prefetch-wait-start",
|
|
5270
|
+
event: this.adoption.waitStarted$
|
|
5271
|
+
},
|
|
5203
5272
|
{
|
|
5204
5273
|
id: "region-ping-fetch-start",
|
|
5205
5274
|
event: this.actions$.pipe(ofType(regionPingFetchStart))
|
|
@@ -5232,6 +5301,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5232
5301
|
id: "polling-ended",
|
|
5233
5302
|
event: this.actions$.pipe(ofType(pollingEnded))
|
|
5234
5303
|
},
|
|
5304
|
+
{
|
|
5305
|
+
id: "request-stream-sent",
|
|
5306
|
+
event: this.signallingService.onRequestStreamSent$
|
|
5307
|
+
},
|
|
5235
5308
|
this.awsInstanceResolvedEntry,
|
|
5236
5309
|
this.signalingServerConnectedEntry,
|
|
5237
5310
|
{
|
|
@@ -5245,12 +5318,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5245
5318
|
this.configReceivedEntry,
|
|
5246
5319
|
{
|
|
5247
5320
|
id: "ssinfo-received",
|
|
5248
|
-
event: this.store.select(unrealFeature.selectSsData).pipe(filter(Boolean), take(1),
|
|
5249
|
-
client: aws.wsUrl,
|
|
5250
|
-
instanceName: aws.instanceName,
|
|
5251
|
-
...ssData,
|
|
5252
|
-
connectionId
|
|
5253
|
-
})))
|
|
5321
|
+
event: this.store.select(unrealFeature.selectSsData).pipe(filter(Boolean), take(1), map(() => this.signalingParams()))
|
|
5254
5322
|
}
|
|
5255
5323
|
]
|
|
5256
5324
|
},
|
|
@@ -5393,12 +5461,48 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5393
5461
|
handshakeFlow: null,
|
|
5394
5462
|
commandCount: 0,
|
|
5395
5463
|
times,
|
|
5396
|
-
openPhaseIdx: 0
|
|
5464
|
+
openPhaseIdx: 0,
|
|
5465
|
+
connectCorrelationId: null,
|
|
5466
|
+
orchestrationError: null,
|
|
5467
|
+
iceServers: null,
|
|
5468
|
+
cirrusClose: null,
|
|
5469
|
+
aws: null,
|
|
5470
|
+
prefetchReported: false,
|
|
5471
|
+
polls: {
|
|
5472
|
+
count: 0,
|
|
5473
|
+
errors: 0,
|
|
5474
|
+
last: null
|
|
5475
|
+
}
|
|
5397
5476
|
};
|
|
5398
5477
|
sub.add(this.store.select(selectCirrusConnectionId).pipe(filter(Boolean), take(1)).subscribe((connectionId) => this.resolveWireId(connectionId)));
|
|
5399
5478
|
sub.add(this.signallingService.onWebRtcIce$.pipe(filter(Boolean)).subscribe(() => {
|
|
5400
5479
|
if (this.session) this.session.iceCandidateCount++;
|
|
5401
5480
|
}));
|
|
5481
|
+
sub.add(this.signallingService.onRequestStreamSent$.subscribe((id) => {
|
|
5482
|
+
if (!this.session) return;
|
|
5483
|
+
this.session.connectCorrelationId = id;
|
|
5484
|
+
this.session.aws = null;
|
|
5485
|
+
}));
|
|
5486
|
+
sub.add(this.signallingService.onOrchestrationError$.subscribe((e) => {
|
|
5487
|
+
if (this.session) this.session.orchestrationError = `${e?.code}: ${e?.message ?? ""}`;
|
|
5488
|
+
}));
|
|
5489
|
+
sub.add(this.signallingService.onConfig$.pipe(take(1)).subscribe((msg) => {
|
|
5490
|
+
if (this.session) this.session.iceServers = iceServerHosts(msg?.peerConnectionOptions?.iceServers);
|
|
5491
|
+
}));
|
|
5492
|
+
sub.add(this.actions$.pipe(ofType(setAwsInstance)).subscribe(() => {
|
|
5493
|
+
if (this.session) this.session.aws = this.awsInstance$();
|
|
5494
|
+
}));
|
|
5495
|
+
sub.add(this.signallingService.onCirrusClose$.subscribe((close) => {
|
|
5496
|
+
if (this.session) this.session.cirrusClose = close;
|
|
5497
|
+
}));
|
|
5498
|
+
sub.add(this.signallingService.onPollResponse$.subscribe((poll) => {
|
|
5499
|
+
const polls = this.session?.polls;
|
|
5500
|
+
if (!polls) return;
|
|
5501
|
+
polls.count++;
|
|
5502
|
+
if (poll.status !== 200) polls.errors++;
|
|
5503
|
+
polls.last = poll;
|
|
5504
|
+
}));
|
|
5505
|
+
sub.add(this.adoption.report$.subscribe((report) => this.trackPrefetch(report)));
|
|
5402
5506
|
sub.add(this.actions$.pipe(ofType(answerSent), take(1)).subscribe(() => {
|
|
5403
5507
|
if (this.session) this.session.handshakeFlow = "server-offer";
|
|
5404
5508
|
}));
|
|
@@ -5444,7 +5548,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5444
5548
|
}));
|
|
5445
5549
|
});
|
|
5446
5550
|
});
|
|
5447
|
-
sub.add(this.actions$.pipe(ofType(streamAdopted)).subscribe(({ timings, region }) =>
|
|
5551
|
+
sub.add(this.actions$.pipe(ofType(streamAdopted)).subscribe(({ timings, region, correlationId }) => {
|
|
5552
|
+
if (this.session && correlationId) this.session.connectCorrelationId = correlationId;
|
|
5553
|
+
this.emitAdoptedConnection(timings, region);
|
|
5554
|
+
}));
|
|
5448
5555
|
const bootstrapInfo = this.collectBootstrapInfo();
|
|
5449
5556
|
const embedderUrl = this.collectEmbedderUrl();
|
|
5450
5557
|
this.emitLifecycle("stream.session_started", {
|
|
@@ -5472,14 +5579,19 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5472
5579
|
const errorMessage = this.errorMessage$();
|
|
5473
5580
|
const granularMessage = errorMessage?.reason === granularReason ? errorMessage.message : void 0;
|
|
5474
5581
|
const includeGranular = granularReason && granularReason !== DisconnectReason.None;
|
|
5582
|
+
const waiting = this.adoption.waitingReport();
|
|
5583
|
+
if (waiting) this.trackPrefetch(waiting);
|
|
5475
5584
|
const openPhase = this.phaseCatalog[session.openPhaseIdx];
|
|
5476
|
-
if (openPhase) this.emitPhaseEnded(openPhase, session.openPhaseIdx, null, true);
|
|
5585
|
+
if (openPhase) this.emitPhaseEnded(openPhase, session.openPhaseIdx, openPhase.id === "signaling-connection" ? this.signalingParams() : null, true);
|
|
5477
5586
|
this.flushPaks(true);
|
|
5587
|
+
const close = session.cirrusClose;
|
|
5478
5588
|
this.emitLifecycle("stream.session_ended", {
|
|
5479
5589
|
sessionId: id,
|
|
5480
5590
|
reason,
|
|
5481
5591
|
...includeGranular ? { disconnectReason: granularReason } : {},
|
|
5482
5592
|
...includeGranular && granularMessage ? { disconnectMessage: granularMessage } : {},
|
|
5593
|
+
...close && { closeCode: close.code },
|
|
5594
|
+
...close?.reason && { closeReason: close.reason },
|
|
5483
5595
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5484
5596
|
});
|
|
5485
5597
|
this.flushOutbox(session, id);
|
|
@@ -5558,7 +5670,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5558
5670
|
const region = parkedRegion ?? NO_REGION;
|
|
5559
5671
|
this.emitAdoptedPhase(0, startMs, 0, adoptionAtMs, regionParams(region));
|
|
5560
5672
|
this.emitAdoptedPhase(1, startMs, sigDur, adoptionAtMs, {
|
|
5561
|
-
...this.
|
|
5673
|
+
...this.signalingParams(),
|
|
5562
5674
|
...savings,
|
|
5563
5675
|
prefetchSignalingMs: sigDur
|
|
5564
5676
|
}, [{
|
|
@@ -5593,13 +5705,22 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5593
5705
|
});
|
|
5594
5706
|
this.closePhase(bucketIdx);
|
|
5595
5707
|
}
|
|
5596
|
-
|
|
5597
|
-
const
|
|
5598
|
-
const
|
|
5708
|
+
signalingParams() {
|
|
5709
|
+
const s = this.session;
|
|
5710
|
+
const aws = s?.aws ?? this.awsInstance$();
|
|
5599
5711
|
return {
|
|
5600
5712
|
client: aws?.wsUrl ?? null,
|
|
5601
5713
|
instanceName: aws?.instanceName ?? null,
|
|
5602
|
-
|
|
5714
|
+
transport: aws ? aws.kind ?? "direct" : null,
|
|
5715
|
+
orchestrationUrl: aws?.kind === "socket" ? aws.pollingUrl : null,
|
|
5716
|
+
streamRequestId: aws?.streamRequestId ?? null,
|
|
5717
|
+
connectCorrelationId: s?.connectCorrelationId ?? null,
|
|
5718
|
+
orchestrationError: s?.orchestrationError ?? null,
|
|
5719
|
+
iceServers: s?.iceServers ?? null,
|
|
5720
|
+
orchPolls: s?.polls.count || null,
|
|
5721
|
+
orchPollErrors: s?.polls.errors || null,
|
|
5722
|
+
orchLastPoll: s?.polls.last ? pollText(s.polls.last) : null,
|
|
5723
|
+
...this.ssData$() ?? {},
|
|
5603
5724
|
connectionId: this.connectionId$()
|
|
5604
5725
|
};
|
|
5605
5726
|
}
|
|
@@ -5842,6 +5963,26 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
|
|
|
5842
5963
|
...session?.wireId ? { sessionId: session.wireId } : {}
|
|
5843
5964
|
});
|
|
5844
5965
|
}
|
|
5966
|
+
trackPrefetch(report) {
|
|
5967
|
+
const session = this.session;
|
|
5968
|
+
if (!session || session.prefetchReported) return;
|
|
5969
|
+
session.prefetchReported = true;
|
|
5970
|
+
this.track("stream.prefetch", {
|
|
5971
|
+
sessionId: session.id,
|
|
5972
|
+
outcome: report.outcome,
|
|
5973
|
+
...report.failReason && { failReason: report.failReason },
|
|
5974
|
+
...report.failError && { failError: report.failError },
|
|
5975
|
+
...report.waitMs !== null && { waitMs: report.waitMs },
|
|
5976
|
+
...report.kind && { kind: report.kind },
|
|
5977
|
+
...report.instanceName && { instanceName: report.instanceName },
|
|
5978
|
+
...report.streamRequestId && { streamRequestId: report.streamRequestId },
|
|
5979
|
+
trail: report.trail.map((entry) => ({
|
|
5980
|
+
...entry,
|
|
5981
|
+
t: Math.round(entry.t - session.anchorPerf)
|
|
5982
|
+
})),
|
|
5983
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5984
|
+
});
|
|
5985
|
+
}
|
|
5845
5986
|
emit(event, data) {
|
|
5846
5987
|
this.store.dispatch(trackTelemetryData({
|
|
5847
5988
|
event,
|
|
@@ -5912,6 +6053,26 @@ function iceParams(ice) {
|
|
|
5912
6053
|
function fmtCounts(counts) {
|
|
5913
6054
|
return Object.entries(counts).map(([k, n]) => `${k} ${n}`).join(" · ") || null;
|
|
5914
6055
|
}
|
|
6056
|
+
function pollText(poll) {
|
|
6057
|
+
return [
|
|
6058
|
+
poll.status,
|
|
6059
|
+
poll.status === 200 ? poll.ready ? "ready" : "waiting" : null,
|
|
6060
|
+
poll.percent !== null ? `${poll.percent}%` : null,
|
|
6061
|
+
poll.message,
|
|
6062
|
+
`${poll.ms}ms`
|
|
6063
|
+
].filter((part) => part !== null && part !== "").join(" ");
|
|
6064
|
+
}
|
|
6065
|
+
function iceServerHosts(servers) {
|
|
6066
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
6067
|
+
for (const server of servers ?? []) {
|
|
6068
|
+
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
6069
|
+
for (const url of urls) {
|
|
6070
|
+
const m = /^(stuns?|turns?):([^:?/]+)/i.exec(url ?? "");
|
|
6071
|
+
if (m) hosts.add(`${m[1].toLowerCase()}:${m[2].toLowerCase()}`);
|
|
6072
|
+
}
|
|
6073
|
+
}
|
|
6074
|
+
return hosts.size ? [...hosts].join(", ") : null;
|
|
6075
|
+
}
|
|
5915
6076
|
function toPhaseParams(value) {
|
|
5916
6077
|
if (!value || typeof value !== "object") return [];
|
|
5917
6078
|
const out = [];
|