@3dsource/angular-unreal-module 0.0.199 → 0.0.200-dev.1

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";
@@ -567,6 +567,8 @@ const RECONNECT_CONFIG = {
567
567
  maxAttempts: 3,
568
568
  delayMs: 1e3
569
569
  };
570
+ const DC_WATCH_INTERVAL_MS = 1e3;
571
+ const DC_CLOSING_GRACE_MS = 2e3;
570
572
  const DATA_CHANNEL_TIMEOUT_RESTARTS = {
571
573
  hidden: 3,
572
574
  visible: 10
@@ -1804,6 +1806,7 @@ const ORCHESTRATION_ERROR_CODES = {
1804
1806
  };
1805
1807
  const RETRY_CODES = /* @__PURE__ */ new Set([ORCHESTRATION_ERROR_CODES.NO_AVAILABLE_INSTANCES.code, ORCHESTRATION_ERROR_CODES.LIMIT_REACHED.code]);
1806
1808
  function OrchestrationErrorHandler(msg) {
1809
+ this.onOrchestrationError$.next(msg.payload);
1807
1810
  if (RETRY_CODES.has(msg.payload.code)) {
1808
1811
  const errorInfo = Object.values(ORCHESTRATION_ERROR_CODES).find(({ code }) => code === msg.payload.code);
1809
1812
  this.store.dispatch(setOrchestrationMessage({ message: errorInfo?.description }));
@@ -1831,6 +1834,10 @@ var SignallingService = class SignallingService extends SubService {
1831
1834
  this.onWebRtcIce$ = new Subject();
1832
1835
  this.onWebRtcAnswer$ = new Subject();
1833
1836
  this.onInstanceReady$ = new Subject();
1837
+ this.onRequestStreamSent$ = new Subject();
1838
+ this.onOrchestrationError$ = new Subject();
1839
+ this.onCirrusClose$ = new Subject();
1840
+ this.onPollResponse$ = new Subject();
1834
1841
  this.abort$ = this.actions$.pipe(ofType(abortEstablishingConnection));
1835
1842
  this.ws = null;
1836
1843
  this.controlWs = null;
@@ -1978,8 +1985,13 @@ var SignallingService = class SignallingService extends SubService {
1978
1985
  }));
1979
1986
  }
1980
1987
  pollLoop(url) {
1981
- return defer(() => this.httpClient.get(url)).pipe(timeout(POLLING_TIME), catchError((err) => {
1988
+ let sentAt = 0;
1989
+ return defer(() => {
1990
+ sentAt = performance.now();
1991
+ return this.httpClient.get(url);
1992
+ }).pipe(timeout(POLLING_TIME), tap$1((data) => this.onPollResponse$.next(pollResponse(200, data, sentAt))), catchError((err) => {
1982
1993
  const status = err?.status;
1994
+ this.onPollResponse$.next(pollResponse(typeof status === "number" && status > 0 ? status : err instanceof TimeoutError ? "timeout" : "network", null, sentAt));
1983
1995
  if (typeof status === "number" && status >= 400 && status < 500 && status !== 429) {
1984
1996
  Logger.warn(`Polling HARD error on ${url} (HTTP ${status}); rotating URL`);
1985
1997
  return throwError(() => err);
@@ -2122,6 +2134,7 @@ var SignallingService = class SignallingService extends SubService {
2122
2134
  context
2123
2135
  }
2124
2136
  });
2137
+ this.onRequestStreamSent$.next(this.correlationId);
2125
2138
  }
2126
2139
  isConnected() {
2127
2140
  return !!(this.ws && this.ws.readyState === 1);
@@ -2161,6 +2174,15 @@ i0.ɵɵngDeclareClassMetadata({
2161
2174
  }],
2162
2175
  ctorParameters: () => []
2163
2176
  });
2177
+ function pollResponse(status, data, sentAt) {
2178
+ return {
2179
+ status,
2180
+ ms: Math.round(performance.now() - sentAt),
2181
+ ready: !!data?.signallingServer,
2182
+ percent: data?.info?.percent ?? null,
2183
+ message: data?.mm_message || data?.error || null
2184
+ };
2185
+ }
2164
2186
  function finite(value) {
2165
2187
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2166
2188
  }
@@ -2838,14 +2860,22 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2838
2860
  });
2839
2861
  fromEvent(datachannel, "message").pipe(map$1((e) => e.data), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((data) => this.onDataChannelMessage$.next(data));
2840
2862
  for (const buffered of replay) this.onDataChannelMessage$.next(buffered);
2841
- (datachannel.readyState === "open" ? of(null) : fromEvent(datachannel, "open").pipe(take(1))).pipe(tap(() => {
2863
+ const open$ = datachannel.readyState === "open" ? of(null) : fromEvent(datachannel, "open").pipe(take(1));
2864
+ open$.pipe(tap(() => {
2842
2865
  Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
2843
2866
  }), switchMap$1(() => this.videoService.video$.pipe(filter$1(Truthy), take(1))), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe(() => this.onDataChannelConnected());
2867
+ this.watchForStuckClose(datachannel, open$);
2844
2868
  } catch (e) {
2845
2869
  Logger.warn("[DATACHANNEL] Datachannel setup caused an exception: ", e);
2846
2870
  this.dcClient = null;
2847
2871
  }
2848
2872
  }
2873
+ watchForStuckClose(datachannel, open$) {
2874
+ open$.pipe(switchMap$1(() => interval(DC_WATCH_INTERVAL_MS)), filter$1(() => datachannel.readyState === "closing" || datachannel.readyState === "closed"), take(1), switchMap$1(() => timer(DC_CLOSING_GRACE_MS)), filter$1(() => this.dcClient === datachannel && datachannel.readyState !== "open"), takeUntil$1(merge(fromEvent(datachannel, "close"), this.disconnect$, this.destroy$))).subscribe(() => {
2875
+ Logger.warn(`[DATACHANNEL] "${datachannel.label}" stuck in "${datachannel.readyState}" with no close event, reconnecting the peer`);
2876
+ this.tryReconnectOnSendFailure();
2877
+ });
2878
+ }
2849
2879
  onDataChannelConnected() {
2850
2880
  let statusMessage = "Session connected, video stream starting.";
2851
2881
  if (navigator.userAgent.includes("Firefox")) statusMessage += "\n If you are experiencing connection problems please try Google Chrome";
@@ -2908,6 +2938,13 @@ var StreamAdoptionService = class StreamAdoptionService {
2908
2938
  this.store = inject(Store);
2909
2939
  this.signalling = inject(SignallingService);
2910
2940
  this.webRtc = inject(WebRtcPlayerService);
2941
+ this.report$ = new Subject();
2942
+ this.waitStarted$ = new Subject();
2943
+ this.waiting = null;
2944
+ }
2945
+ waitingReport() {
2946
+ const w = this.waiting;
2947
+ return w ? reportOf(w.parked, "waiting", w.sincePerf) : null;
2911
2948
  }
2912
2949
  awaitAndAdopt$() {
2913
2950
  return defer(() => {
@@ -2917,32 +2954,47 @@ var StreamAdoptionService = class StreamAdoptionService {
2917
2954
  return of(false);
2918
2955
  }
2919
2956
  if (isAppOwned(slot)) return of(false);
2920
- if (slot.state !== "connecting") return of(this.tryAdoptOrTakeSlot());
2957
+ if (slot.state !== "connecting") return of(this.settle(slot, null));
2921
2958
  Logger.info("[Adoption] Prefetch in progress — waiting before connecting");
2959
+ this.waitStarted$.next();
2922
2960
  return this.awaitSettled$(slot);
2923
2961
  });
2924
2962
  }
2925
2963
  awaitSettled$(parked) {
2926
2964
  return new Observable((subscriber) => {
2927
2965
  let done = false;
2966
+ const sincePerf = performance.now();
2967
+ this.waiting = {
2968
+ parked,
2969
+ sincePerf
2970
+ };
2928
2971
  parked.onSettled(() => {
2929
2972
  if (done) return;
2930
2973
  done = true;
2931
- let adopted = false;
2932
- try {
2933
- adopted = this.tryAdoptOrTakeSlot();
2934
- } catch (e) {
2935
- Logger.error("[Adoption] Adoption threw — falling back", e);
2936
- this.takeSlot();
2937
- }
2938
- subscriber.next(adopted);
2974
+ this.waiting = null;
2975
+ subscriber.next(this.settle(parked, sincePerf));
2939
2976
  subscriber.complete();
2940
2977
  });
2941
2978
  return () => {
2979
+ if (done) return;
2942
2980
  done = true;
2981
+ this.waiting = null;
2982
+ this.report$.next(reportOf(parked, "abandoned", sincePerf));
2943
2983
  };
2944
2984
  });
2945
2985
  }
2986
+ settle(parked, sincePerf) {
2987
+ let adopted = false;
2988
+ try {
2989
+ adopted = this.tryAdoptOrTakeSlot();
2990
+ } catch (e) {
2991
+ Logger.error("[Adoption] Adoption threw — falling back", e);
2992
+ this.takeSlot();
2993
+ }
2994
+ const outcome = adopted ? "adopted" : parked.state === "failed" ? "failed" : parked.state === "ready" ? "dead" : null;
2995
+ if (outcome) this.report$.next(reportOf(parked, outcome, sincePerf));
2996
+ return adopted;
2997
+ }
2946
2998
  tryAdoptOrTakeSlot() {
2947
2999
  const adopted = this.tryAdopt();
2948
3000
  if (!adopted) this.takeSlot();
@@ -2969,7 +3021,7 @@ var StreamAdoptionService = class StreamAdoptionService {
2969
3021
  if (!slot || isAppOwned(slot)) return false;
2970
3022
  const parked = slot;
2971
3023
  if (parked.state !== "ready") return false;
2972
- if (!parked.ws || parked.ws.readyState !== 1 || !parked.pc || !parked.dc || !parked.stream) {
3024
+ 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) {
2973
3025
  Logger.warn("[Adoption] Parked stream is ready but transport is not live");
2974
3026
  return false;
2975
3027
  }
@@ -2980,6 +3032,7 @@ var StreamAdoptionService = class StreamAdoptionService {
2980
3032
  pollingUrl: parked.pollingUrl ?? null,
2981
3033
  ssData: parked.ssData ?? null,
2982
3034
  streamRequestId: parked.ids?.streamRequestId || null,
3035
+ correlationId: parked.ids?.correlationId || null,
2983
3036
  kind: parked.kind ?? null,
2984
3037
  instanceName: parked.instanceName ?? null,
2985
3038
  eta: parked.eta ?? null,
@@ -3020,6 +3073,20 @@ i0.ɵɵngDeclareClassMetadata({
3020
3073
  args: [{ providedIn: "root" }]
3021
3074
  }]
3022
3075
  });
3076
+ const DEAD_PC_STATES = /* @__PURE__ */ new Set(["failed", "closed"]);
3077
+ const DEAD_DC_STATES = /* @__PURE__ */ new Set(["closing", "closed"]);
3078
+ function reportOf(parked, outcome, sincePerf) {
3079
+ return {
3080
+ outcome,
3081
+ failReason: outcome === "dead" ? parked.deadReason ?? "transport-not-live" : parked.failReason ?? null,
3082
+ failError: parked.failError ?? null,
3083
+ waitMs: sincePerf === null ? null : Math.round(performance.now() - sincePerf),
3084
+ kind: parked.kind ?? null,
3085
+ instanceName: parked.instanceName ?? null,
3086
+ streamRequestId: parked.ids?.streamRequestId || null,
3087
+ trail: [...parked.trail ?? []]
3088
+ };
3089
+ }
3023
3090
  var UnrealCallbackService = class UnrealCallbackService {
3024
3091
  constructor() {
3025
3092
  this.actions$ = inject(Actions);
@@ -3542,6 +3609,10 @@ const dropCodes = [
3542
3609
  function OnCloseHandler(e) {
3543
3610
  const message = `WebSocket closed: ${JSON.stringify(e.code)} - ${e.reason}`;
3544
3611
  Logger.warn(message);
3612
+ this.onCirrusClose$.next({
3613
+ code: e.code,
3614
+ reason: e.reason
3615
+ });
3545
3616
  this.store.dispatch(setCirrusDisconnected());
3546
3617
  if (dropCodes.includes(e.code)) this.store.dispatch(dropConnection());
3547
3618
  }
@@ -5155,6 +5226,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5155
5226
  this.videoService = inject(VideoService);
5156
5227
  this.signallingService = inject(SignallingService);
5157
5228
  this.webRtcPlayer = inject(WebRtcPlayerService);
5229
+ this.adoption = inject(StreamAdoptionService);
5158
5230
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
5159
5231
  this.destroyRef = inject(DestroyRef);
5160
5232
  this.destroy$ = new Subject();
@@ -5203,6 +5275,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5203
5275
  id: "region-resolving",
5204
5276
  color: "#61afef",
5205
5277
  milestones: [
5278
+ {
5279
+ id: "prefetch-wait-start",
5280
+ event: this.adoption.waitStarted$
5281
+ },
5206
5282
  {
5207
5283
  id: "region-ping-fetch-start",
5208
5284
  event: this.actions$.pipe(ofType(regionPingFetchStart))
@@ -5235,6 +5311,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5235
5311
  id: "polling-ended",
5236
5312
  event: this.actions$.pipe(ofType(pollingEnded))
5237
5313
  },
5314
+ {
5315
+ id: "request-stream-sent",
5316
+ event: this.signallingService.onRequestStreamSent$
5317
+ },
5238
5318
  this.awsInstanceResolvedEntry,
5239
5319
  this.signalingServerConnectedEntry,
5240
5320
  {
@@ -5248,12 +5328,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5248
5328
  this.configReceivedEntry,
5249
5329
  {
5250
5330
  id: "ssinfo-received",
5251
- event: this.store.select(unrealFeature.selectSsData).pipe(filter(Boolean), take(1), withLatestFrom(this.store.select(unrealFeature.selectAwsInstance), this.store.select(selectCirrusConnectionId)), map(([ssData, aws, connectionId]) => ({
5252
- client: aws.wsUrl,
5253
- instanceName: aws.instanceName,
5254
- ...ssData,
5255
- connectionId
5256
- })))
5331
+ event: this.store.select(unrealFeature.selectSsData).pipe(filter(Boolean), take(1), map(() => this.signalingParams()))
5257
5332
  }
5258
5333
  ]
5259
5334
  },
@@ -5396,12 +5471,48 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5396
5471
  handshakeFlow: null,
5397
5472
  commandCount: 0,
5398
5473
  times,
5399
- openPhaseIdx: 0
5474
+ openPhaseIdx: 0,
5475
+ connectCorrelationId: null,
5476
+ orchestrationError: null,
5477
+ iceServers: null,
5478
+ cirrusClose: null,
5479
+ aws: null,
5480
+ prefetchReported: false,
5481
+ polls: {
5482
+ count: 0,
5483
+ errors: 0,
5484
+ last: null
5485
+ }
5400
5486
  };
5401
5487
  sub.add(this.store.select(selectCirrusConnectionId).pipe(filter(Boolean), take(1)).subscribe((connectionId) => this.resolveWireId(connectionId)));
5402
5488
  sub.add(this.signallingService.onWebRtcIce$.pipe(filter(Boolean)).subscribe(() => {
5403
5489
  if (this.session) this.session.iceCandidateCount++;
5404
5490
  }));
5491
+ sub.add(this.signallingService.onRequestStreamSent$.subscribe((id) => {
5492
+ if (!this.session) return;
5493
+ this.session.connectCorrelationId = id;
5494
+ this.session.aws = null;
5495
+ }));
5496
+ sub.add(this.signallingService.onOrchestrationError$.subscribe((e) => {
5497
+ if (this.session) this.session.orchestrationError = `${e?.code}: ${e?.message ?? ""}`;
5498
+ }));
5499
+ sub.add(this.signallingService.onConfig$.pipe(take(1)).subscribe((msg) => {
5500
+ if (this.session) this.session.iceServers = iceServerHosts(msg?.peerConnectionOptions?.iceServers);
5501
+ }));
5502
+ sub.add(this.actions$.pipe(ofType(setAwsInstance)).subscribe(() => {
5503
+ if (this.session) this.session.aws = this.awsInstance$();
5504
+ }));
5505
+ sub.add(this.signallingService.onCirrusClose$.subscribe((close) => {
5506
+ if (this.session) this.session.cirrusClose = close;
5507
+ }));
5508
+ sub.add(this.signallingService.onPollResponse$.subscribe((poll) => {
5509
+ const polls = this.session?.polls;
5510
+ if (!polls) return;
5511
+ polls.count++;
5512
+ if (poll.status !== 200) polls.errors++;
5513
+ polls.last = poll;
5514
+ }));
5515
+ sub.add(this.adoption.report$.subscribe((report) => this.trackPrefetch(report)));
5405
5516
  sub.add(this.actions$.pipe(ofType(answerSent), take(1)).subscribe(() => {
5406
5517
  if (this.session) this.session.handshakeFlow = "server-offer";
5407
5518
  }));
@@ -5447,7 +5558,10 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5447
5558
  }));
5448
5559
  });
5449
5560
  });
5450
- sub.add(this.actions$.pipe(ofType(streamAdopted)).subscribe(({ timings, region }) => this.emitAdoptedConnection(timings, region)));
5561
+ sub.add(this.actions$.pipe(ofType(streamAdopted)).subscribe(({ timings, region, correlationId }) => {
5562
+ if (this.session && correlationId) this.session.connectCorrelationId = correlationId;
5563
+ this.emitAdoptedConnection(timings, region);
5564
+ }));
5451
5565
  const bootstrapInfo = this.collectBootstrapInfo();
5452
5566
  const embedderUrl = this.collectEmbedderUrl();
5453
5567
  this.emitLifecycle("stream.session_started", {
@@ -5475,14 +5589,19 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5475
5589
  const errorMessage = this.errorMessage$();
5476
5590
  const granularMessage = errorMessage?.reason === granularReason ? errorMessage.message : void 0;
5477
5591
  const includeGranular = granularReason && granularReason !== DisconnectReason.None;
5592
+ const waiting = this.adoption.waitingReport();
5593
+ if (waiting) this.trackPrefetch(waiting);
5478
5594
  const openPhase = this.phaseCatalog[session.openPhaseIdx];
5479
- if (openPhase) this.emitPhaseEnded(openPhase, session.openPhaseIdx, null, true);
5595
+ if (openPhase) this.emitPhaseEnded(openPhase, session.openPhaseIdx, openPhase.id === "signaling-connection" ? this.signalingParams() : null, true);
5480
5596
  this.flushPaks(true);
5597
+ const close = session.cirrusClose;
5481
5598
  this.emitLifecycle("stream.session_ended", {
5482
5599
  sessionId: id,
5483
5600
  reason,
5484
5601
  ...includeGranular ? { disconnectReason: granularReason } : {},
5485
5602
  ...includeGranular && granularMessage ? { disconnectMessage: granularMessage } : {},
5603
+ ...close && { closeCode: close.code },
5604
+ ...close?.reason && { closeReason: close.reason },
5486
5605
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5487
5606
  });
5488
5607
  this.flushOutbox(session, id);
@@ -5561,7 +5680,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5561
5680
  const region = parkedRegion ?? NO_REGION;
5562
5681
  this.emitAdoptedPhase(0, startMs, 0, adoptionAtMs, regionParams(region));
5563
5682
  this.emitAdoptedPhase(1, startMs, sigDur, adoptionAtMs, {
5564
- ...this.adoptedSignalingParams(),
5683
+ ...this.signalingParams(),
5565
5684
  ...savings,
5566
5685
  prefetchSignalingMs: sigDur
5567
5686
  }, [{
@@ -5596,13 +5715,22 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5596
5715
  });
5597
5716
  this.closePhase(bucketIdx);
5598
5717
  }
5599
- adoptedSignalingParams() {
5600
- const aws = this.awsInstance$();
5601
- const ssData = this.ssData$();
5718
+ signalingParams() {
5719
+ const s = this.session;
5720
+ const aws = s?.aws ?? this.awsInstance$();
5602
5721
  return {
5603
5722
  client: aws?.wsUrl ?? null,
5604
5723
  instanceName: aws?.instanceName ?? null,
5605
- ...ssData ?? {},
5724
+ transport: aws ? aws.kind ?? "direct" : null,
5725
+ orchestrationUrl: aws?.kind === "socket" ? aws.pollingUrl : null,
5726
+ streamRequestId: aws?.streamRequestId ?? null,
5727
+ connectCorrelationId: s?.connectCorrelationId ?? null,
5728
+ orchestrationError: s?.orchestrationError ?? null,
5729
+ iceServers: s?.iceServers ?? null,
5730
+ orchPolls: s?.polls.count || null,
5731
+ orchPollErrors: s?.polls.errors || null,
5732
+ orchLastPoll: s?.polls.last ? pollText(s.polls.last) : null,
5733
+ ...this.ssData$() ?? {},
5606
5734
  connectionId: this.connectionId$()
5607
5735
  };
5608
5736
  }
@@ -5845,6 +5973,26 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5845
5973
  ...session?.wireId ? { sessionId: session.wireId } : {}
5846
5974
  });
5847
5975
  }
5976
+ trackPrefetch(report) {
5977
+ const session = this.session;
5978
+ if (!session || session.prefetchReported) return;
5979
+ session.prefetchReported = true;
5980
+ this.track("stream.prefetch", {
5981
+ sessionId: session.id,
5982
+ outcome: report.outcome,
5983
+ ...report.failReason && { failReason: report.failReason },
5984
+ ...report.failError && { failError: report.failError },
5985
+ ...report.waitMs !== null && { waitMs: report.waitMs },
5986
+ ...report.kind && { kind: report.kind },
5987
+ ...report.instanceName && { instanceName: report.instanceName },
5988
+ ...report.streamRequestId && { streamRequestId: report.streamRequestId },
5989
+ trail: report.trail.map((entry) => ({
5990
+ ...entry,
5991
+ t: Math.round(entry.t - session.anchorPerf)
5992
+ })),
5993
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5994
+ });
5995
+ }
5848
5996
  emit(event, data) {
5849
5997
  this.store.dispatch(trackTelemetryData({
5850
5998
  event,
@@ -5915,6 +6063,26 @@ function iceParams(ice) {
5915
6063
  function fmtCounts(counts) {
5916
6064
  return Object.entries(counts).map(([k, n]) => `${k} ${n}`).join(" · ") || null;
5917
6065
  }
6066
+ function pollText(poll) {
6067
+ return [
6068
+ poll.status,
6069
+ poll.status === 200 ? poll.ready ? "ready" : "waiting" : null,
6070
+ poll.percent !== null ? `${poll.percent}%` : null,
6071
+ poll.message,
6072
+ `${poll.ms}ms`
6073
+ ].filter((part) => part !== null && part !== "").join(" ");
6074
+ }
6075
+ function iceServerHosts(servers) {
6076
+ const hosts = /* @__PURE__ */ new Set();
6077
+ for (const server of servers ?? []) {
6078
+ const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
6079
+ for (const url of urls) {
6080
+ const m = /^(stuns?|turns?):([^:?/]+)/i.exec(url ?? "");
6081
+ if (m) hosts.add(`${m[1].toLowerCase()}:${m[2].toLowerCase()}`);
6082
+ }
6083
+ }
6084
+ return hosts.size ? [...hosts].join(", ") : null;
6085
+ }
5918
6086
  function toPhaseParams(value) {
5919
6087
  if (!value || typeof value !== "object") return [];
5920
6088
  const out = [];
@@ -7960,6 +8128,6 @@ function provideUnrealModule() {
7960
8128
  inject(AnalyticsService);
7961
8129
  })]);
7962
8130
  }
7963
- export { AFKService, ALL_KEYBOARD_KEYS, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, COLOR_CODES, 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, NO_REGION, 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_PREFETCH_API_VERSION, REGION_PING_PREFETCH_GLOBAL, 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, UNREAL_CONFIG, 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 };
8131
+ export { AFKService, ALL_KEYBOARD_KEYS, AUTO_HIGH_RESOLUTION_IDLE_MS, AfkTimeoutModalComponent, AggregatorService, AnalyticsService, AnswerHandler, COLOR_CODES, ConfigHandler, ConsoleExtensionsService, ControlScheme, DATA_CHANNEL_CONNECTION_TIMEOUT, DATA_CHANNEL_TIMEOUT_RESTARTS, DC_CLOSING_GRACE_MS, DC_WATCH_INTERVAL_MS, 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, NO_REGION, 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_PREFETCH_API_VERSION, REGION_PING_PREFETCH_GLOBAL, 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, UNREAL_CONFIG, 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 };
7964
8132
 
7965
8133
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map