@3dsource/angular-unreal-module 0.0.191 → 0.0.193-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.
@@ -1356,6 +1356,13 @@ const tapLog = (text, ...args) => (source) => source.pipe(tap$1((data) => {
1356
1356
  function isMetaboxMode(config) {
1357
1357
  return (config?.mode ?? "metabox") === "metabox";
1358
1358
  }
1359
+ function callbackCorrelationId(json) {
1360
+ return "commandCallback" in json ? json.commandCallback?.correlationId : json?.correlationId;
1361
+ }
1362
+ function callbackStamps(json) {
1363
+ const raw = json;
1364
+ return raw.timestamp ?? raw.timeStamp;
1365
+ }
1359
1366
  const selectCommandProgress = createSelector(unrealFeature.selectTotalCommandsStarted, unrealFeature.selectTotalCommandsCompleted, (started, completed) => started ? completed / started : 0);
1360
1367
  const selectFreezeFrameDataUrlFromVideo = createSelector(unrealFeature.selectFreezeFrameFromVideo, (freezeFrameFromVideo) => freezeFrameFromVideo?.dataUrl || null);
1361
1368
  const selectFreezeFrameProgressMessageFromVideo = createSelector(unrealFeature.selectFreezeFrameFromVideo, (freezeFrameFromVideo) => freezeFrameFromVideo?.progress || null);
@@ -1472,205 +1479,6 @@ i0.ɵɵngDeclareClassMetadata({
1472
1479
  }]
1473
1480
  }]
1474
1481
  });
1475
- var SubService = class {
1476
- constructor() {
1477
- this.destroyRef = inject(DestroyRef);
1478
- this.destroy$ = new Subject();
1479
- this.actions$ = inject(Actions);
1480
- this.store = inject(Store);
1481
- this.disconnect$ = this.store.select(unrealFeature.selectCirrusConnected).pipe(skip(1), filter$1(Falsy), share());
1482
- this.destroyRef.onDestroy(() => {
1483
- this.destroy$.next();
1484
- this.destroy$.complete();
1485
- });
1486
- }
1487
- };
1488
- var CommandTelemetryService = class CommandTelemetryService extends SubService {
1489
- constructor() {
1490
- super();
1491
- this.appId = "metabox-telemetry";
1492
- this.commandsSent = {};
1493
- this.exileTimeout = 6e4;
1494
- this.pollingTime = 5e3;
1495
- this.commandSequenceNumber = 0;
1496
- this.payloads = [];
1497
- this.httpClient = inject(HttpClient);
1498
- this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
1499
- this.init();
1500
- }
1501
- init() {
1502
- if (!this.unrealConfig?.commandTelemetryReceiver || this.unrealConfig?.playwright) return;
1503
- this.reset();
1504
- this.sessionId = sessionStorage.getItem(this.appId) || this.uuid;
1505
- this.userId = localStorage.getItem(this.appId) || this.uuid;
1506
- sessionStorage.setItem(this.appId, this.sessionId);
1507
- localStorage.setItem(this.appId, this.userId);
1508
- timer(0, this.pollingTime).pipe(takeUntil$1(this.destroy$)).subscribe(() => {
1509
- this.send(this.payloads);
1510
- });
1511
- }
1512
- get timeNow() {
1513
- return Date.now();
1514
- }
1515
- get sessionTime() {
1516
- return this.timeNow - this.startTime;
1517
- }
1518
- get canSkipSending() {
1519
- return !(this.timeNow - this.lastTime > this.pollingTime * 2);
1520
- }
1521
- decorate(funcToDecorate) {
1522
- this.listenCallbacks();
1523
- return this.wrapper.bind(this, funcToDecorate);
1524
- }
1525
- trackStartCommand(trackingId) {
1526
- const out = {
1527
- command: trackingId,
1528
- trackingId
1529
- };
1530
- const timers = { sessionElapsedTime: this.sessionTime };
1531
- this.pushStatToBuffer("commandSent", out, timers);
1532
- this.trackTime(out);
1533
- }
1534
- trackStopCommand(trackingId, multi = false, response = {}, timeStampsRaw) {
1535
- const commandResponse = this.commandsSent[trackingId];
1536
- if (!commandResponse) return;
1537
- const totalRoundTripTime = this.timeNow - commandResponse.timeStampJs;
1538
- const commandSentJsTime = commandResponse.timeStampJs - this.startTime;
1539
- let unrealTimers = {};
1540
- const t = timeStampsRaw?.timeStampCommand;
1541
- if (t) unrealTimers = {
1542
- unrealRoundTripTime: t.onCompleteCommand.timestamp - t.onRequestCommand.timestamp,
1543
- unrealExecutionDuration: t.onCompleteCommand.timestamp - t.onStartCommand.timestamp,
1544
- timeDiffRequest: t.onRequestCommand.timestamp - commandResponse.timeStampJs,
1545
- timeDiffCallBack: this.timeNow - t.onCompleteCommand.timestamp
1546
- };
1547
- const timers = {
1548
- sessionElapsedTime: this.sessionTime,
1549
- commandSentJsTime,
1550
- totalRoundTripTime,
1551
- ...unrealTimers
1552
- };
1553
- this.pushStatToBuffer("commandStop", {
1554
- ...commandResponse.command,
1555
- response
1556
- }, timers, timeStampsRaw);
1557
- if (!multi) delete this.commandsSent[trackingId];
1558
- }
1559
- reset() {
1560
- this.uuid = generateUuid();
1561
- this.commandSequenceNumber = 0;
1562
- this.viewId = this.uuid;
1563
- this.startTime = this.timeNow;
1564
- this.payloads.length = 0;
1565
- this.commandsSent = {};
1566
- }
1567
- pushStatToBuffer(type, commandContent, computedTimers, timeStampsRaw) {
1568
- this.pushData({
1569
- type,
1570
- commandSequenceNumber: this.commandSequenceNumber++,
1571
- commandContent,
1572
- timeStampsRaw,
1573
- computedTimers
1574
- });
1575
- }
1576
- trackTime(command) {
1577
- this.commandsSent[command.trackingId] = {
1578
- timeStampJs: this.timeNow,
1579
- command
1580
- };
1581
- }
1582
- send(payload) {
1583
- if (payload.length === 0 || this.canSkipSending && payload.length < 10) return;
1584
- this.lastTime = this.timeNow;
1585
- const out = {
1586
- appId: this.appId,
1587
- sessionId: this.sessionId,
1588
- viewId: this.viewId,
1589
- userId: this.userId,
1590
- ua: navigator.userAgent,
1591
- timeStamp: new Date(this.timeNow).toString(),
1592
- href: location.href,
1593
- resolution: {
1594
- width: window.screen.width,
1595
- height: window.screen.height,
1596
- pixelRatio: window.devicePixelRatio
1597
- },
1598
- payload
1599
- };
1600
- const lastIndex = payload[payload.length - 1].commandSequenceNumber;
1601
- this.httpClient.post(this.unrealConfig?.commandTelemetryReceiver || "", out).pipe(catchError(() => of(null)), take(1)).subscribe(() => {
1602
- this.payloads = this.payloads.filter((p) => p.commandSequenceNumber > lastIndex);
1603
- });
1604
- }
1605
- listenCallbacks() {
1606
- this.actions$.pipe(ofType(unrealCommandCallback), filter$1(({ json }) => {
1607
- const callback = "commandCallback" in json ? json.commandCallback : json;
1608
- return Truthy(callback?.trackingId);
1609
- }), takeUntil$1(this.destroy$)).subscribe(({ json }) => {
1610
- const callback = "commandCallback" in json ? json.commandCallback : json;
1611
- const timeStampsRaw = "timeStamp" in json ? json?.timeStamp : void 0;
1612
- const trackingId = callback?.trackingId || "";
1613
- this.trackStopCommand(trackingId, false, {}, timeStampsRaw);
1614
- this.removeExileCommands();
1615
- });
1616
- }
1617
- pushData(payload) {
1618
- this.payloads.push(payload);
1619
- }
1620
- removeExileCommands() {
1621
- const time = (/* @__PURE__ */ new Date()).getTime();
1622
- const markForDelete = [];
1623
- Object.entries(this.commandsSent).forEach(([trackingId, { timeStampJs }]) => {
1624
- if (time - timeStampJs > this.exileTimeout) markForDelete.push(trackingId);
1625
- });
1626
- markForDelete.forEach((trackingId) => {
1627
- delete this.commandsSent[trackingId];
1628
- });
1629
- }
1630
- wrapper(funcToDecorate, data) {
1631
- const out = {
1632
- ...data,
1633
- trackingId: generateUuid()
1634
- };
1635
- const timers = { sessionElapsedTime: this.sessionTime };
1636
- this.pushStatToBuffer("commandSent", {
1637
- ...out,
1638
- trackingId: `${out.trackingId}`
1639
- }, timers);
1640
- this.trackTime(out);
1641
- funcToDecorate(out);
1642
- }
1643
- static {
1644
- this.ɵfac = i0.ɵɵngDeclareFactory({
1645
- minVersion: "12.0.0",
1646
- version: "22.2.0",
1647
- ngImport: i0,
1648
- type: CommandTelemetryService,
1649
- deps: [],
1650
- target: i0.ɵɵFactoryTarget.Injectable
1651
- });
1652
- }
1653
- static {
1654
- this.ɵprov = i0.ɵɵngDeclareInjectable({
1655
- minVersion: "12.0.0",
1656
- version: "22.2.0",
1657
- ngImport: i0,
1658
- type: CommandTelemetryService,
1659
- providedIn: "root"
1660
- });
1661
- }
1662
- };
1663
- i0.ɵɵngDeclareClassMetadata({
1664
- minVersion: "12.0.0",
1665
- version: "22.2.0",
1666
- ngImport: i0,
1667
- type: CommandTelemetryService,
1668
- decorators: [{
1669
- type: Injectable,
1670
- args: [{ providedIn: "root" }]
1671
- }],
1672
- ctorParameters: () => []
1673
- });
1674
1482
  var DevModeService = class DevModeService {
1675
1483
  get isDevMode() {
1676
1484
  return Truthy(localStorage.getItem("devMode"));
@@ -1907,6 +1715,19 @@ i0.ɵɵngDeclareClassMetadata({
1907
1715
  }],
1908
1716
  ctorParameters: () => []
1909
1717
  });
1718
+ var SubService = class {
1719
+ constructor() {
1720
+ this.destroyRef = inject(DestroyRef);
1721
+ this.destroy$ = new Subject();
1722
+ this.actions$ = inject(Actions);
1723
+ this.store = inject(Store);
1724
+ this.disconnect$ = this.store.select(unrealFeature.selectCirrusConnected).pipe(skip(1), filter$1(Falsy), share());
1725
+ this.destroyRef.onDestroy(() => {
1726
+ this.destroy$.next();
1727
+ this.destroy$.complete();
1728
+ });
1729
+ }
1730
+ };
1910
1731
  function StreamerListHandler(msg) {
1911
1732
  this.send(JSON.stringify({
1912
1733
  type: "subscribe",
@@ -2000,7 +1821,6 @@ var SignallingService = class SignallingService extends SubService {
2000
1821
  super();
2001
1822
  this.httpClient = inject(HttpClient);
2002
1823
  this.regionsPingService = inject(RegionsPingService);
2003
- this.commandTelemetryService = inject(CommandTelemetryService);
2004
1824
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
2005
1825
  this.region = signal("", ...ngDevMode ? [{ debugName: "region" }] : /* istanbul ignore next */ []);
2006
1826
  this.selectClientAndViewIds = this.store.selectSignal(selectClientAndViewIds);
@@ -2045,7 +1865,6 @@ var SignallingService = class SignallingService extends SubService {
2045
1865
  Logger.log("CONNECT TO SIGNALLING");
2046
1866
  return defer(() => {
2047
1867
  this.correlationId = generateUuid();
2048
- this.commandTelemetryService.reset();
2049
1868
  this.startEstablishingConnection();
2050
1869
  return this.getRegion().pipe(tap$1((region) => this.region.set(region ?? "")), map$1((region) => this.adaptUrlsToRegion(urlList, region)), switchMap((urls) => this.connectAttempt(urls)), takeUntil$1(this.abort$)).pipe(retry({ delay: (err, attempt) => {
2051
1870
  this.store.dispatch(signalingRetryAttempted({
@@ -2117,39 +1936,22 @@ var SignallingService = class SignallingService extends SubService {
2117
1936
  }
2118
1937
  requestCirrusViaPolling(url) {
2119
1938
  return defer(() => {
2120
- let lastResp;
2121
1939
  this.store.dispatch(pollingStarted());
2122
- this.commandTelemetryService.trackStartCommand("EXT-getSignaling");
2123
1940
  return this.store.select(selectClientAndViewIds).pipe(tap$1(({ clientId, viewId }) => {
2124
1941
  if (!clientId || !viewId) Logger.error("Client ID or View ID is not set");
2125
- }), filter$1(({ clientId, viewId }) => !!clientId && !!viewId), take(1), switchMap(({ clientId, viewId }) => this.pollLoop(`${url}${clientId}/${viewId}`).pipe(tap$1((data) => {
2126
- lastResp = data;
2127
- }))), map$1((data) => ({
1942
+ }), filter$1(({ clientId, viewId }) => !!clientId && !!viewId), take(1), switchMap(({ clientId, viewId }) => this.pollLoop(`${url}${clientId}/${viewId}`)), map$1((data) => ({
2128
1943
  wsUrl: httpUrlToWs(`${location.protocol}//${data.signallingServer}`),
2129
1944
  pollingUrl: url,
2130
1945
  instanceName: data.signallingServer ?? "",
2131
1946
  streamRequestId: data.streamRequestId,
2132
1947
  kind: "polling"
2133
- })), finalize(() => {
2134
- this.commandTelemetryService.trackStopCommand("EXT-getSignaling", true, {
2135
- ...lastResp ?? {},
2136
- multi: true
2137
- });
2138
- this.store.dispatch(pollingEnded());
2139
- }));
1948
+ })), finalize(() => this.store.dispatch(pollingEnded())));
2140
1949
  });
2141
1950
  }
2142
1951
  requestCirrusViaSocket(url) {
2143
1952
  return defer(() => {
2144
- let resolved;
2145
1953
  this.orchestrationRetryCount = 0;
2146
- this.commandTelemetryService.trackStartCommand("EXT-getSignaling");
2147
- return this.waitForClientAndViewIds().pipe(switchMap(() => createWebSocket(url)), switchMap((control) => this.awaitInstanceReady(control, url)), tap$1((instance) => {
2148
- resolved = instance;
2149
- }), finalize(() => this.commandTelemetryService.trackStopCommand("EXT-getSignaling", true, {
2150
- ...resolved ?? {},
2151
- multi: true
2152
- })));
1954
+ return this.waitForClientAndViewIds().pipe(switchMap(() => createWebSocket(url)), switchMap((control) => this.awaitInstanceReady(control, url)));
2153
1955
  });
2154
1956
  }
2155
1957
  awaitInstanceReady(control, orchestrationUrl) {
@@ -2197,10 +1999,7 @@ var SignallingService = class SignallingService extends SubService {
2197
1999
  reason: DisconnectReason.NewConnectionAttempt
2198
2000
  });
2199
2001
  return createWebSocket(wsUrl);
2200
- }).pipe(takeUntil$1(this.abort$), tapLog("WS CREATED"), tap$1((ws) => {
2201
- this.commandTelemetryService.trackStartCommand("EXT-iceCandidate");
2202
- this.addWsHandlers(ws);
2203
- }));
2002
+ }).pipe(takeUntil$1(this.abort$), tapLog("WS CREATED"), tap$1((ws) => this.addWsHandlers(ws)));
2204
2003
  }
2205
2004
  addWsHandlers(ws) {
2206
2005
  ws.onmessage = OnMessageHandler.bind(this);
@@ -2362,6 +2161,58 @@ i0.ɵɵngDeclareClassMetadata({
2362
2161
  }],
2363
2162
  ctorParameters: () => []
2364
2163
  });
2164
+ function finite(value) {
2165
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2166
+ }
2167
+ function candidateInfo(candidate, isLocal) {
2168
+ if (!candidate) return {};
2169
+ const isRelay = candidate.candidateType === "relay";
2170
+ return {
2171
+ type: candidate.candidateType,
2172
+ protocol: candidate.protocol,
2173
+ ...isRelay ? {
2174
+ address: candidate.address ?? void 0,
2175
+ port: finite(candidate.port)
2176
+ } : {},
2177
+ ...isLocal ? {
2178
+ url: candidate.url,
2179
+ relayProtocol: candidate.relayProtocol
2180
+ } : {}
2181
+ };
2182
+ }
2183
+ function selectedPairId(stats) {
2184
+ let fromTransport;
2185
+ let flagged;
2186
+ stats.forEach((r) => {
2187
+ if (r.type === "transport" && r.selectedCandidatePairId) fromTransport = r.selectedCandidatePairId;
2188
+ if (r.type === "candidate-pair" && r.selected === true) flagged = r.id;
2189
+ });
2190
+ return fromTransport ?? flagged;
2191
+ }
2192
+ function iceSnapshot(stats) {
2193
+ const count = (type, key) => {
2194
+ const out = {};
2195
+ stats.forEach((r) => {
2196
+ if (r.type !== type) return;
2197
+ const k = r[key] || "unknown";
2198
+ out[k] = (out[k] ?? 0) + 1;
2199
+ });
2200
+ return out;
2201
+ };
2202
+ const pairId = selectedPairId(stats);
2203
+ const pair = pairId ? stats.get(pairId) : void 0;
2204
+ const rtt = finite(pair?.currentRoundTripTime);
2205
+ return {
2206
+ local: count("local-candidate", "candidateType"),
2207
+ remote: count("remote-candidate", "candidateType"),
2208
+ pairs: count("candidate-pair", "state"),
2209
+ selected: pair ? {
2210
+ local: candidateInfo(stats.get(pair.localCandidateId), true),
2211
+ remote: candidateInfo(stats.get(pair.remoteCandidateId), false)
2212
+ } : null,
2213
+ ...rtt !== void 0 && { selectedRttMs: Math.round(rtt * 1e3) }
2214
+ };
2215
+ }
2365
2216
  var VideoService = class VideoService extends SubService {
2366
2217
  constructor() {
2367
2218
  super();
@@ -2486,21 +2337,17 @@ var VideoService = class VideoService extends SubService {
2486
2337
  }
2487
2338
  generateAggregatedStatsFunction(stats, pcEpoch = 0) {
2488
2339
  const newStat = {};
2489
- let selectedPairId;
2490
- let flaggedPairId;
2491
2340
  newStat.codecs = {};
2492
2341
  newStat.currentRoundTripTime = -1;
2493
2342
  stats.forEach((report) => {
2494
2343
  if (report.type === "inbound-rtp" && report.kind === "video") this.applyInboundVideoReport(newStat, report);
2495
2344
  if (report.type === "candidate-pair" && report.nominated === true && Object.hasOwn(report, "currentRoundTripTime")) newStat.currentRoundTripTime = report.currentRoundTripTime ?? 0;
2496
- if (report.type === "transport" && report.selectedCandidatePairId) selectedPairId = report.selectedCandidatePairId;
2497
- if (report.type === "candidate-pair" && report.selected === true) flaggedPairId = report.id;
2498
2345
  if (Object.hasOwn(newStat, "codecs") && report.type === "codec" && report.mimeType && report.id) {
2499
2346
  const codecId = report.id;
2500
2347
  newStat.codecs[codecId] = report.mimeType.replace("video/", "").replace("audio/", "");
2501
2348
  }
2502
2349
  });
2503
- this.applySelectedPair(newStat, stats, selectedPairId ?? flaggedPairId, pcEpoch);
2350
+ this.applySelectedPair(newStat, stats, selectedPairId(stats), pcEpoch);
2504
2351
  this.applySessionTotals(newStat);
2505
2352
  this.aggregatedStats = newStat;
2506
2353
  return this.onAggregatedStats(newStat);
@@ -2638,25 +2485,6 @@ i0.ɵɵngDeclareClassMetadata({
2638
2485
  function pinBaseline(pinned, current) {
2639
2486
  return pinned || current;
2640
2487
  }
2641
- function finite(value) {
2642
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2643
- }
2644
- function candidateInfo(candidate, isLocal) {
2645
- if (!candidate) return {};
2646
- const isRelay = candidate.candidateType === "relay";
2647
- return {
2648
- type: candidate.candidateType,
2649
- protocol: candidate.protocol,
2650
- ...isRelay ? {
2651
- address: candidate.address ?? void 0,
2652
- port: finite(candidate.port)
2653
- } : {},
2654
- ...isLocal ? {
2655
- url: candidate.url,
2656
- relayProtocol: candidate.relayProtocol
2657
- } : {}
2658
- };
2659
- }
2660
2488
  function isAutoplayBlocked(error) {
2661
2489
  return !!error && typeof error === "object" && "name" in error && error.name === "NotAllowedError";
2662
2490
  }
@@ -2664,11 +2492,11 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2664
2492
  constructor() {
2665
2493
  super();
2666
2494
  this.onDataChannelMessage$ = new Subject();
2495
+ this.iceState$ = new Subject();
2667
2496
  this.pcClient = null;
2668
2497
  this.dcClient = null;
2669
2498
  this.signallingSrv = inject(SignallingService);
2670
2499
  this.videoService = inject(VideoService);
2671
- this.commandTelemetryService = inject(CommandTelemetryService);
2672
2500
  this.cfg = {};
2673
2501
  this.useMic = false;
2674
2502
  this.forceTURN = false;
@@ -2693,7 +2521,6 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2693
2521
  this.handleServerAnswer(answer);
2694
2522
  });
2695
2523
  this.signallingSrv.onWebRtcIce$.pipe(takeUntil$1(this.destroy$)).subscribe((candidate) => {
2696
- this.commandTelemetryService.trackStopCommand("EXT-iceCandidate", true, { multi: true });
2697
2524
  if (this.remoteDescriptionSet) this.handleCandidateFromServer(candidate);
2698
2525
  else this.iceCandidateBuffer.push(candidate);
2699
2526
  });
@@ -2755,9 +2582,6 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2755
2582
  this.closePC();
2756
2583
  Logger.log("Creating new RTCPeerConnection with config");
2757
2584
  this.pcClient = new RTCPeerConnection(config);
2758
- this.commandTelemetryService.trackStartCommand("EXT-Browser ICE gathering");
2759
- this.commandTelemetryService.trackStartCommand("EXT-Browser ICE connection");
2760
- this.commandTelemetryService.trackStartCommand("EXT-Signaling state change");
2761
2585
  this.handlePeerConnectionEvents(this.pcClient);
2762
2586
  this.disconnect$.pipe(take(1), takeUntil$1(this.destroy$)).subscribe(() => {
2763
2587
  this.closePC();
@@ -2831,6 +2655,15 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2831
2655
  canSend() {
2832
2656
  return this.dcClient && this.dcClient.readyState === "open";
2833
2657
  }
2658
+ async readIceSnapshot() {
2659
+ const pc = this.pcClient;
2660
+ if (!pc || pc.connectionState === "closed") return null;
2661
+ try {
2662
+ return iceSnapshot(await pc.getStats(null));
2663
+ } catch {
2664
+ return null;
2665
+ }
2666
+ }
2834
2667
  describeConnectionState() {
2835
2668
  const pc = this.pcClient;
2836
2669
  return pc ? `signaling=${pc.signalingState}, ice=${pc.iceConnectionState}, connection=${pc.connectionState}` : "no peer connection";
@@ -2890,7 +2723,6 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2890
2723
  dc.onclose = null;
2891
2724
  this.dcClient = this.setupDataChannelCallbacks(dc);
2892
2725
  for (const buffered of dcBuffer) this.onDataChannelMessage$.next(buffered);
2893
- this.commandTelemetryService.trackStopCommand("EXT-DataChannel Connection");
2894
2726
  this.onDataChannelConnected();
2895
2727
  this.videoService.create();
2896
2728
  this.videoService.attachVideoStream(stream, pc);
@@ -2917,23 +2749,19 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2917
2749
  handlePeerConnectionEvents(pcClient) {
2918
2750
  fromEvent(pcClient, "signalingstatechange").pipe(map$1(() => this.pcClient?.signalingState), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((state) => {
2919
2751
  Logger.info("[Signaling state change] |", state, "|");
2920
- this.commandTelemetryService.trackStopCommand("EXT-Signaling state change", true, {
2921
- state,
2922
- multi: true
2923
- });
2924
2752
  });
2925
2753
  fromEvent(pcClient, "iceconnectionstatechange").pipe(map$1(() => this.pcClient?.iceConnectionState), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((state) => {
2926
2754
  Logger.info("[Browser ICE connection] |", state, "|");
2927
- this.commandTelemetryService.trackStopCommand("EXT-Browser ICE connection", true, {
2928
- state,
2929
- multi: true
2755
+ if (state) this.iceState$.next({
2756
+ kind: "connection",
2757
+ state
2930
2758
  });
2931
2759
  });
2932
2760
  fromEvent(pcClient, "icegatheringstatechange").pipe(map$1(() => this.pcClient?.iceGatheringState), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((state) => {
2933
2761
  Logger.info("[Browser ICE gathering] |", state, "|");
2934
- this.commandTelemetryService.trackStopCommand("EXT-Browser ICE gathering", true, {
2935
- state,
2936
- multi: true
2762
+ if (state) this.iceState$.next({
2763
+ kind: "gathering",
2764
+ state
2937
2765
  });
2938
2766
  });
2939
2767
  fromEvent(pcClient, "connectionstatechange").pipe(map$1(() => this.pcClient?.connectionState), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((state) => {
@@ -2993,7 +2821,6 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
2993
2821
  setupDataChannelCallbacks(datachannel) {
2994
2822
  try {
2995
2823
  Logger.log(`[DATACHANNEL] Setting up datachannel "${datachannel.label}" callbacks...`);
2996
- this.commandTelemetryService.trackStartCommand("EXT-DataChannel Connection");
2997
2824
  datachannel.binaryType = "arraybuffer";
2998
2825
  fromEvent(datachannel, "close").pipe(filter$1(() => !this.isReconnecting()), take(1), takeUntil$1(this.destroy$)).subscribe((e) => {
2999
2826
  Logger.warn(`[DATACHANNEL] Data channel disconnected: ${datachannel.label}(${datachannel.id})`, e);
@@ -3008,10 +2835,7 @@ var WebRtcPlayerService = class WebRtcPlayerService extends SubService {
3008
2835
  });
3009
2836
  fromEvent(datachannel, "open").pipe(tap(() => {
3010
2837
  Logger.info(`[DATACHANNEL] Data channel "${datachannel.label}" connected. id:${datachannel.id}`);
3011
- }), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe(() => {
3012
- this.commandTelemetryService.trackStopCommand("EXT-DataChannel Connection");
3013
- this.onDataChannelConnected();
3014
- });
2838
+ }), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe(() => this.onDataChannelConnected());
3015
2839
  fromEvent(datachannel, "message").pipe(map$1((e) => e.data), takeUntil$1(merge(this.disconnect$, this.destroy$))).subscribe((data) => this.onDataChannelMessage$.next(data));
3016
2840
  return datachannel;
3017
2841
  } catch (e) {
@@ -3244,10 +3068,24 @@ i0.ɵɵngDeclareClassMetadata({
3244
3068
  args: [{ providedIn: "root" }]
3245
3069
  }]
3246
3070
  });
3071
+ function collectPakPaths(payload) {
3072
+ const found = /* @__PURE__ */ new Set();
3073
+ const walk = (value) => {
3074
+ if (Array.isArray(value)) {
3075
+ value.forEach(walk);
3076
+ return;
3077
+ }
3078
+ if (!value || typeof value !== "object") return;
3079
+ for (const [key, v] of Object.entries(value)) if (key === "pakFilePath") {
3080
+ if (typeof v === "string" && v) found.add(v);
3081
+ } else walk(v);
3082
+ };
3083
+ walk(payload);
3084
+ return found.size ? [...found].join(", ") : void 0;
3085
+ }
3247
3086
  var UnrealCommunicatorService = class UnrealCommunicatorService extends SubService {
3248
3087
  constructor() {
3249
3088
  super();
3250
- this.commandTelemetryService = inject(CommandTelemetryService);
3251
3089
  this.webRtcPlayerService = inject(WebRtcPlayerService);
3252
3090
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
3253
3091
  this.cirrusConnected = this.store.selectSignal(unrealFeature.selectCirrusConnected);
@@ -3259,7 +3097,6 @@ var UnrealCommunicatorService = class UnrealCommunicatorService extends SubServi
3259
3097
  }
3260
3098
  init() {
3261
3099
  if (this.isPlaywrightMode()) return;
3262
- this.emitUIInteraction = this.commandTelemetryService.decorate(this.emitUIInteraction.bind(this));
3263
3100
  this.actions$.pipe(ofType(dataChannelConnected), takeUntil(this.destroy$)).subscribe(() => {
3264
3101
  this.requestInitialSettings();
3265
3102
  this.requestQualityControl();
@@ -3280,7 +3117,8 @@ var UnrealCommunicatorService = class UnrealCommunicatorService extends SubServi
3280
3117
  this.store.dispatch(commandStarted({
3281
3118
  id: uniqId,
3282
3119
  command: data.command,
3283
- timeStamp: Date.now()
3120
+ timeStamp: Date.now(),
3121
+ paks: collectPakPaths(data)
3284
3122
  }));
3285
3123
  }
3286
3124
  }
@@ -3428,7 +3266,6 @@ var UnrealEffects = class UnrealEffects {
3428
3266
  this.unrealInitialConfig = inject(UNREAL_CONFIG);
3429
3267
  this.webRtcPlayerService = inject(WebRtcPlayerService);
3430
3268
  this.commandsSender = inject(UnrealCommunicatorService);
3431
- this.commandTelemetryService = inject(CommandTelemetryService);
3432
3269
  this.regionsPingService = inject(RegionsPingService);
3433
3270
  this.signallingService = inject(SignallingService);
3434
3271
  this.streamAdoptionService = inject(StreamAdoptionService);
@@ -3532,12 +3369,11 @@ var UnrealEffects = class UnrealEffects {
3532
3369
  command: MetaBoxCommand.FDebugModeCommand,
3533
3370
  payload: { value: this.isDevMode }
3534
3371
  });
3535
- this.commandTelemetryService.trackStartCommand("EXT-SetViewportReady");
3536
3372
  return this.actions$.pipe(ofType(commandStarted), debounceTime(50), take(1), switchMap(() => this.unrealCallbackService.observeCommandResponse({ command: MetaBoxCommand.FLoopBackCommand }, (data) => this.commandsSender.sendCommandToUnreal(data))), switchMap(() => this.videoService.videoStats$), filter$1(({ aggregatedStats: s }) => !!s.framesPerSecond && s.framesPerSecond > 6 && (s.framesDecoded ?? 0) > 15), take(1), map(() => setLoopBackCommandIsCompleted()));
3537
3373
  }));
3538
3374
  });
3539
3375
  this.setViewportReadyBySetLoopBackCommandIsCompleted$ = createEffect(() => {
3540
- return this.actions$.pipe(ofType(setLoopBackCommandIsCompleted, reconnectPeerSuccess), tap$1(() => this.commandTelemetryService.trackStopCommand("EXT-SetViewportReady", false, { ready: true })), map(() => setViewportReady()));
3376
+ return this.actions$.pipe(ofType(setLoopBackCommandIsCompleted, reconnectPeerSuccess), map(() => setViewportReady()));
3541
3377
  });
3542
3378
  this.startStream$ = createEffect(() => {
3543
3379
  return this.actions$.pipe(ofType(startStream), map(() => initSignalling()));
@@ -3614,7 +3450,7 @@ var UnrealEffects = class UnrealEffects {
3614
3450
  }))));
3615
3451
  }, { dispatch: false });
3616
3452
  this.listenUnrealCallbackByInitSignalling$ = createEffect(() => {
3617
- return this.actions$.pipe(ofType(initSignalling), filter$1(() => !this.unrealInitialConfig?.playwright), switchMap(() => this.actions$.pipe(ofType(unrealCommandCallback), map(({ json }) => "commandCallback" in json ? json.commandCallback?.correlationId : json?.correlationId), filter$1(Truthy), map((id) => commandCompleted({
3453
+ return this.actions$.pipe(ofType(initSignalling), filter$1(() => !this.unrealInitialConfig?.playwright), switchMap(() => this.actions$.pipe(ofType(unrealCommandCallback), map(({ json }) => callbackCorrelationId(json)), filter$1(Truthy), map((id) => commandCompleted({
3618
3454
  id,
3619
3455
  now: Date.now()
3620
3456
  })))));
@@ -4635,6 +4471,16 @@ const TAB_KEY_CODE = 9;
4635
4471
  const ARROW_KEY_MIN = 37;
4636
4472
  const ARROW_KEY_MAX = 40;
4637
4473
  const TEXT_ENTRY_SELECTOR = "input, textarea, select, [contenteditable]:not([contenteditable=\"false\"])";
4474
+ const MOUSE_BUTTONS = [
4475
+ [MouseButton.MainButton, MouseButtonsMask.PrimaryButton],
4476
+ [MouseButton.AuxiliaryButton, MouseButtonsMask.AuxiliaryButton],
4477
+ [MouseButton.SecondaryButton, MouseButtonsMask.SecondaryButton],
4478
+ [MouseButton.FourthButton, MouseButtonsMask.FourthButton],
4479
+ [MouseButton.FifthButton, MouseButtonsMask.FifthButton]
4480
+ ];
4481
+ function buttonMask(button) {
4482
+ return MOUSE_BUTTONS.find(([b]) => b === button)?.[1] ?? 0;
4483
+ }
4638
4484
  var InputService = class InputService extends SubService {
4639
4485
  constructor() {
4640
4486
  super();
@@ -4677,6 +4523,12 @@ var InputService = class InputService extends SubService {
4677
4523
  this.maxByteValue = 255;
4678
4524
  this.fingers = [...INITIAL_FINGER_IDS];
4679
4525
  this.heldKeys = /* @__PURE__ */ new Set();
4526
+ this.heldButtons = 0;
4527
+ this.lastMousePosition = {
4528
+ x: 0,
4529
+ y: 0
4530
+ };
4531
+ this.leavePending = false;
4680
4532
  this.mouseMoveView = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(9));
4681
4533
  this.mouseButtonView = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(6));
4682
4534
  this.mouseWheelView = /* @__PURE__ */ new DataView(/* @__PURE__ */ new ArrayBuffer(7));
@@ -4716,6 +4568,8 @@ var InputService = class InputService extends SubService {
4716
4568
  this.fingerIds.clear();
4717
4569
  this.fingers = [...INITIAL_FINGER_IDS];
4718
4570
  this.heldKeys.clear();
4571
+ this.heldButtons = 0;
4572
+ this.leavePending = false;
4719
4573
  merge(fromResizeObserver(video), this.actions$.pipe(ofType(videoAdaptedToContainer))).pipe(debounceTime$1(RESIZE_DEBOUNCE_MS), takeUntil(this.deactivatedVideoTrigger$)).subscribe(() => this.setupNormalizeAndQuantize(video));
4720
4574
  this.setupNormalizeAndQuantize(video);
4721
4575
  this.registerInputs(video);
@@ -4770,8 +4624,9 @@ var InputService = class InputService extends SubService {
4770
4624
  this.commandsSender.sendInputData(data);
4771
4625
  }
4772
4626
  registerInputs(video) {
4773
- fromEvent(window, "blur").pipe(filter$1(() => !!video), takeUntil(this.deactivatedVideoTrigger$)).subscribe(() => this.releaseMouseButtons(255, 0, 0));
4774
- merge(fromEvent(window, "blur"), fromEvent(document, "visibilitychange").pipe(filter$1(() => document.visibilityState === "hidden"))).pipe(takeUntil(merge(this.reInit$, this.destroy$))).subscribe(() => this.releaseKeys());
4627
+ const focusLost$ = merge(fromEvent(window, "blur"), fromEvent(document, "visibilitychange").pipe(filter$1(() => document.visibilityState === "hidden")));
4628
+ focusLost$.pipe(takeUntil(merge(this.reInit$, this.destroy$))).subscribe(() => this.releaseKeys());
4629
+ focusLost$.pipe(takeUntil(merge(this.disconnect$, this.reInit$, this.destroy$))).subscribe(() => this.releaseMouseButtons(this.heldButtons));
4775
4630
  this.registerMouseEnterAndLeaveEvents(video);
4776
4631
  this.registerHoveringMouseEvents(video);
4777
4632
  this.registerKeyboardEvents();
@@ -4875,6 +4730,7 @@ var InputService = class InputService extends SubService {
4875
4730
  data.setUint16(2, coord.x, true);
4876
4731
  data.setUint16(4, coord.y, true);
4877
4732
  this.sendInputData(data.buffer);
4733
+ this.heldButtons |= buttonMask(button);
4878
4734
  }
4879
4735
  emitMouseUp(button, x, y) {
4880
4736
  const coord = this.normalizeAndQuantizeUnsigned(x, y);
@@ -4884,6 +4740,7 @@ var InputService = class InputService extends SubService {
4884
4740
  data.setUint16(2, coord.x, true);
4885
4741
  data.setUint16(4, coord.y, true);
4886
4742
  this.sendInputData(data.buffer);
4743
+ this.heldButtons &= ~buttonMask(button);
4887
4744
  }
4888
4745
  emitMouseWheel(delta, x, y) {
4889
4746
  const coord = this.normalizeAndQuantizeUnsigned(x, y);
@@ -4894,36 +4751,52 @@ var InputService = class InputService extends SubService {
4894
4751
  data.setUint16(5, coord.y, true);
4895
4752
  this.sendInputData(data.buffer);
4896
4753
  }
4897
- releaseMouseButtons(buttons, x, y) {
4898
- if (buttons & MouseButtonsMask.PrimaryButton) this.emitMouseUp(MouseButton.MainButton, x, y);
4899
- if (buttons & MouseButtonsMask.SecondaryButton) this.emitMouseUp(MouseButton.SecondaryButton, x, y);
4900
- if (buttons & MouseButtonsMask.AuxiliaryButton) this.emitMouseUp(MouseButton.AuxiliaryButton, x, y);
4901
- if (buttons & MouseButtonsMask.FourthButton) this.emitMouseUp(MouseButton.FourthButton, x, y);
4902
- if (buttons & MouseButtonsMask.FifthButton) this.emitMouseUp(MouseButton.FifthButton, x, y);
4754
+ releaseMouseButtons(buttons) {
4755
+ const { x, y } = this.lastMousePosition;
4756
+ for (const [button, mask] of MOUSE_BUTTONS) if (buttons & mask & this.heldButtons) this.emitMouseUp(button, x, y);
4757
+ if (this.leavePending && !this.heldButtons) {
4758
+ this.leavePending = false;
4759
+ this.sendMouseLeave();
4760
+ }
4903
4761
  }
4904
4762
  pressMouseButtons(buttons, x, y) {
4905
- if (buttons & MouseButtonsMask.PrimaryButton) this.emitMouseDown(MouseButton.MainButton, x, y);
4906
- if (buttons & MouseButtonsMask.SecondaryButton) this.emitMouseDown(MouseButton.SecondaryButton, x, y);
4907
- if (buttons & MouseButtonsMask.AuxiliaryButton) this.emitMouseDown(MouseButton.AuxiliaryButton, x, y);
4908
- if (buttons & MouseButtonsMask.FourthButton) this.emitMouseDown(MouseButton.FourthButton, x, y);
4909
- if (buttons & MouseButtonsMask.FifthButton) this.emitMouseDown(MouseButton.FifthButton, x, y);
4763
+ for (const [button, mask] of MOUSE_BUTTONS) if (buttons & mask & ~this.heldButtons) this.emitMouseDown(button, x, y);
4764
+ }
4765
+ sendMouseEnter() {
4766
+ const data = this.singleByteView;
4767
+ data.setUint8(0, MessageType.MouseEnter);
4768
+ this.sendInputData(data.buffer);
4769
+ }
4770
+ sendMouseLeave() {
4771
+ const data = this.singleByteView;
4772
+ data.setUint8(0, MessageType.MouseLeave);
4773
+ this.sendInputData(data.buffer);
4910
4774
  }
4911
4775
  registerHoveringMouseEvents(htmlVideoElement) {
4912
4776
  fromEvent(htmlVideoElement, "mousemove").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4913
- const { x, y } = this.getMousePositionWithOffset(e);
4777
+ this.leavePending = false;
4778
+ this.releaseMouseButtons(this.heldButtons & ~e.buttons);
4779
+ this.lastMousePosition = this.getMousePositionWithOffset(e);
4780
+ const { x, y } = this.lastMousePosition;
4914
4781
  this.emitMouseMove(x, y, e.movementX, e.movementY);
4915
4782
  e.preventDefault();
4916
4783
  });
4917
4784
  fromEvent(htmlVideoElement, "mousedown").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4918
- const { x, y } = this.getMousePositionWithOffset(e);
4785
+ this.leavePending = false;
4919
4786
  this.focusWindow();
4920
- this.emitMouseDown(e.button, x, y);
4921
4787
  e.preventDefault();
4788
+ if (!buttonMask(e.button)) return;
4789
+ this.lastMousePosition = this.getMousePositionWithOffset(e);
4790
+ const { x, y } = this.lastMousePosition;
4791
+ this.emitMouseDown(e.button, x, y);
4922
4792
  });
4923
4793
  fromEvent(htmlVideoElement, "mouseup").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4924
- const { x, y } = this.getMousePositionWithOffset(e);
4925
- this.emitMouseUp(e.button, x, y);
4794
+ this.leavePending = false;
4926
4795
  e.preventDefault();
4796
+ if (this.heldButtons & buttonMask(e.button)) {
4797
+ const { x, y } = this.getMousePositionWithOffset(e);
4798
+ this.emitMouseUp(e.button, x, y);
4799
+ }
4927
4800
  });
4928
4801
  fromEvent(htmlVideoElement, "wheel").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4929
4802
  this.emitMouseWheel(e.deltaY * WHEEL_DELTA_SCALE, e.offsetX, e.offsetY);
@@ -4933,17 +4806,22 @@ var InputService = class InputService extends SubService {
4933
4806
  registerMouseEnterAndLeaveEvents(htmlVideoElement) {
4934
4807
  fromEvent(htmlVideoElement, "mouseenter").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4935
4808
  this.focusWindow();
4936
- const data = this.singleByteView;
4937
- data.setUint8(0, MessageType.MouseEnter);
4938
- this.sendInputData(data.buffer);
4939
- this.pressMouseButtons(e.buttons, e.offsetX, e.offsetY);
4940
- });
4941
- fromEvent(htmlVideoElement, "mouseleave").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => {
4942
- const data = this.singleByteView;
4943
- data.setUint8(0, MessageType.MouseLeave);
4944
- this.sendInputData(data.buffer);
4945
- this.releaseMouseButtons(e.buttons, e.offsetX, e.offsetY);
4809
+ const dragReturned = this.leavePending;
4810
+ this.leavePending = false;
4811
+ this.releaseMouseButtons(this.heldButtons & ~e.buttons);
4812
+ if (!dragReturned) this.sendMouseEnter();
4813
+ this.lastMousePosition = this.getMousePositionWithOffset(e);
4814
+ const { x, y } = this.lastMousePosition;
4815
+ this.pressMouseButtons(e.buttons, x, y);
4816
+ });
4817
+ fromEvent(htmlVideoElement, "mouseleave").pipe(takeUntil(this.deactivatedVideoTrigger$)).subscribe(() => {
4818
+ if (this.heldButtons) this.leavePending = true;
4819
+ else {
4820
+ this.leavePending = false;
4821
+ this.sendMouseLeave();
4822
+ }
4946
4823
  });
4824
+ fromEvent(window, "mouseup", { capture: true }).pipe(filter$1((e) => e.composedPath()[0] !== htmlVideoElement), takeUntil(this.deactivatedVideoTrigger$)).subscribe((e) => this.releaseMouseButtons(buttonMask(e.button) | ~e.buttons));
4947
4825
  }
4948
4826
  isBrowserKey(keyCode) {
4949
4827
  return keyCode >= FUNCTION_KEY_MIN && keyCode <= FUNCTION_KEY_MAX || keyCode >= ARROW_KEY_MIN && keyCode <= ARROW_KEY_MAX || keyCode === TAB_KEY_CODE;
@@ -5180,6 +5058,67 @@ var RouteLog = class {
5180
5058
  };
5181
5059
  }
5182
5060
  };
5061
+ var PakLog = class {
5062
+ constructor(anchorPerf) {
5063
+ this.anchorPerf = anchorPerf;
5064
+ this.inFlight = /* @__PURE__ */ new Map();
5065
+ this.done = [];
5066
+ }
5067
+ start(id, pak, command, nowPerf) {
5068
+ this.inFlight.set(id, {
5069
+ pak,
5070
+ command,
5071
+ sentPerf: nowPerf
5072
+ });
5073
+ }
5074
+ complete(id, nowPerf, stamps) {
5075
+ const sent = this.inFlight.get(id);
5076
+ if (!sent) return;
5077
+ this.inFlight.delete(id);
5078
+ this.done.push({
5079
+ ...this.base(sent),
5080
+ totalMs: Math.round(nowPerf - sent.sentPerf),
5081
+ ...unrealTimings(stamps)
5082
+ });
5083
+ }
5084
+ take(nowPerf, final = false) {
5085
+ for (const [id, sent] of this.inFlight) if (final || nowPerf - sent.sentPerf > 6e4) {
5086
+ this.inFlight.delete(id);
5087
+ this.done.push({
5088
+ ...this.base(sent),
5089
+ lost: true
5090
+ });
5091
+ }
5092
+ const out = this.done;
5093
+ this.done = [];
5094
+ return out;
5095
+ }
5096
+ base({ pak, command, sentPerf }) {
5097
+ return {
5098
+ pak,
5099
+ command,
5100
+ sentMs: Math.round(sentPerf - this.anchorPerf)
5101
+ };
5102
+ }
5103
+ };
5104
+ function unrealTimings(stamps) {
5105
+ const cmd = field(stamps, "timeStampCommand");
5106
+ const at = (key) => {
5107
+ const t = field(field(cmd, key), "timestamp");
5108
+ return typeof t === "number" && Number.isFinite(t) ? t : void 0;
5109
+ };
5110
+ const span = (from, to) => from !== void 0 && to !== void 0 && to >= from ? Math.round(to - from) : void 0;
5111
+ const start = at("onStartCommand");
5112
+ const queueMs = span(at("onRequestCommand"), start);
5113
+ const execMs = span(start, at("onCompleteCommand"));
5114
+ return {
5115
+ ...queueMs !== void 0 && { queueMs },
5116
+ ...execMs !== void 0 && { execMs }
5117
+ };
5118
+ }
5119
+ function field(value, key) {
5120
+ return value && typeof value === "object" ? value[key] : void 0;
5121
+ }
5183
5122
  function shortResourceName(url) {
5184
5123
  try {
5185
5124
  return new URL(url).pathname;
@@ -5187,6 +5126,12 @@ function shortResourceName(url) {
5187
5126
  return (url || "").split("?")[0];
5188
5127
  }
5189
5128
  }
5129
+ var PinnedEnd = class {
5130
+ constructor(atPerf, extras) {
5131
+ this.atPerf = atPerf;
5132
+ this.extras = extras;
5133
+ }
5134
+ };
5190
5135
  const QP_RED_THRESHOLD = 35;
5191
5136
  const FPS_LOW_RATIO = .7;
5192
5137
  const UNLIMITED_REFERENCE_FPS = 60;
@@ -5203,6 +5148,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5203
5148
  this.actions$ = inject(Actions);
5204
5149
  this.videoService = inject(VideoService);
5205
5150
  this.signallingService = inject(SignallingService);
5151
+ this.webRtcPlayer = inject(WebRtcPlayerService);
5206
5152
  this.unrealConfig = inject(UNREAL_CONFIG, { optional: true });
5207
5153
  this.destroyRef = inject(DestroyRef);
5208
5154
  this.destroy$ = new Subject();
@@ -5234,6 +5180,14 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5234
5180
  id: "signaling-ws-connected",
5235
5181
  event: this.store.select(unrealFeature.selectCirrusConnected).pipe(filter(Boolean), take(1))
5236
5182
  };
5183
+ this.iceCheckingEntry = {
5184
+ id: "ice-checking",
5185
+ event: this.iceConnection$("checking")
5186
+ };
5187
+ this.iceConnectedEntry = {
5188
+ id: "ice-connected",
5189
+ event: this.iceConnection$("connected", "completed")
5190
+ };
5237
5191
  this.configReceivedEntry = {
5238
5192
  id: "config-received",
5239
5193
  event: this.signallingService.onConfig$.pipe(filter(Boolean), take(1))
@@ -5300,6 +5254,11 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5300
5254
  {
5301
5255
  id: "webrtc-negotiation",
5302
5256
  color: "#e5c07b",
5257
+ overlays: [{
5258
+ from: this.iceCheckingEntry,
5259
+ to: this.iceConnectedEntry,
5260
+ id: "ice-connectivity-checks"
5261
+ }],
5303
5262
  milestones: [
5304
5263
  {
5305
5264
  id: "offer-received",
@@ -5322,12 +5281,28 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5322
5281
  event: this.signallingService.onWebRtcIce$.pipe(filter(Boolean)),
5323
5282
  mode: "last"
5324
5283
  },
5284
+ this.iceCheckingEntry,
5285
+ {
5286
+ id: "ice-gathering-complete",
5287
+ event: this.webRtcPlayer.iceState$.pipe(filter((e) => e.kind === "gathering" && e.state === "complete"))
5288
+ },
5289
+ this.iceConnectedEntry,
5290
+ {
5291
+ id: "ice-failed",
5292
+ event: this.iceConnection$("failed")
5293
+ },
5325
5294
  {
5326
5295
  id: "datachannel-connected",
5327
5296
  event: this.store.select(unrealFeature.selectDataChannelConnected).pipe(filter(Boolean), take(1), map(() => ({
5328
- iceCandidateCount: this.session?.iceCandidateCount ?? 0,
5329
- handshakeFlow: this.session?.handshakeFlow ?? null
5330
- })))
5297
+ atPerf: performance.now(),
5298
+ extras: {
5299
+ iceCandidateCount: this.session?.iceCandidateCount ?? 0,
5300
+ handshakeFlow: this.session?.handshakeFlow ?? null
5301
+ }
5302
+ })), switchMap(({ atPerf, extras }) => from(this.webRtcPlayer.readIceSnapshot()).pipe(map((ice) => new PinnedEnd(atPerf, {
5303
+ ...extras,
5304
+ ...iceParams(ice)
5305
+ })))))
5331
5306
  }
5332
5307
  ]
5333
5308
  },
@@ -5407,6 +5382,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5407
5382
  sub,
5408
5383
  bytes: new CounterWindow(),
5409
5384
  routes: new RouteLog(),
5385
+ paks: new PakLog(anchorPerf),
5410
5386
  qpBad: false,
5411
5387
  fpsLow: false,
5412
5388
  maxFpsTarget: UNLIMITED_REFERENCE_FPS,
@@ -5426,9 +5402,17 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5426
5402
  sub.add(this.signallingService.onWebRtcAnswer$.pipe(filter(Boolean), take(1)).subscribe(() => {
5427
5403
  if (this.session) this.session.handshakeFlow = "client-offer";
5428
5404
  }));
5429
- sub.add(this.actions$.pipe(ofType(commandStarted)).subscribe(() => {
5430
- if (this.session) this.session.commandCount++;
5405
+ sub.add(this.actions$.pipe(ofType(commandStarted)).subscribe(({ id, command, paks }) => {
5406
+ if (!this.session) return;
5407
+ this.session.commandCount++;
5408
+ if (paks) this.session.paks.start(id, paks, command, performance.now());
5409
+ }));
5410
+ sub.add(this.actions$.pipe(ofType(unrealCommandCallback)).subscribe(({ json }) => {
5411
+ const id = callbackCorrelationId(json);
5412
+ if (!id || !this.session) return;
5413
+ this.session.paks.complete(id, performance.now(), callbackStamps(json));
5431
5414
  }));
5415
+ sub.add(interval(SAMPLE_WINDOW_MS).subscribe(() => this.flushPaks()));
5432
5416
  sub.add(this.actions$.pipe(ofType(signalingRetryAttempted)).subscribe(({ attempt, reason, kind }) => {
5433
5417
  if (!this.session) return;
5434
5418
  this.track("stream.signaling_retry", {
@@ -5447,7 +5431,8 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5447
5431
  if (!this.session) return;
5448
5432
  if (isCloser) {
5449
5433
  if (this.adopted$() && PREFETCHED_PHASE_IDS.has(bucket.id)) return;
5450
- this.emitPhaseEnded(bucket, bucketIdx, value);
5434
+ const pinned = value instanceof PinnedEnd ? value : null;
5435
+ this.emitPhaseEnded(bucket, bucketIdx, pinned ? pinned.extras : value, false, pinned?.atPerf);
5451
5436
  return;
5452
5437
  }
5453
5438
  const time = Math.round(performance.now() - this.session.anchorPerf);
@@ -5484,6 +5469,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5484
5469
  const includeGranular = granularReason && granularReason !== DisconnectReason.None;
5485
5470
  const openPhase = this.phaseCatalog[session.openPhaseIdx];
5486
5471
  if (openPhase) this.emitPhaseEnded(openPhase, session.openPhaseIdx, null, true);
5472
+ this.flushPaks(true);
5487
5473
  this.emitLifecycle("stream.session_ended", {
5488
5474
  sessionId: id,
5489
5475
  reason,
@@ -5519,9 +5505,9 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5519
5505
  });
5520
5506
  this.session.lastMilestonePerf = now;
5521
5507
  }
5522
- emitPhaseEnded(bucket, bucketIdx, extrasValue, incomplete = false) {
5508
+ emitPhaseEnded(bucket, bucketIdx, extrasValue, incomplete = false, atPerf) {
5523
5509
  if (!this.session) return;
5524
- const now = performance.now();
5510
+ const now = atPerf ?? performance.now();
5525
5511
  const phaseStartMs = Math.round(this.session.lastMilestonePerf - this.session.anchorPerf);
5526
5512
  const phaseEndMs = Math.round(now - this.session.anchorPerf);
5527
5513
  const milestones = this.milestonesOfBucket(bucket);
@@ -5536,7 +5522,7 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5536
5522
  params,
5537
5523
  overlays,
5538
5524
  ...incomplete && { incomplete: true }
5539
- });
5525
+ }, now);
5540
5526
  this.closePhase(bucketIdx);
5541
5527
  }
5542
5528
  closePhase(bucketIdx) {
@@ -5672,6 +5658,20 @@ var StreamStatusTelemetryService2 = class StreamStatusTelemetryService2 {
5672
5658
  });
5673
5659
  s.fpsLow = fpsLowNow;
5674
5660
  }
5661
+ flushPaks(final = false) {
5662
+ const s = this.session;
5663
+ if (!s) return;
5664
+ const items = s.paks.take(performance.now(), final);
5665
+ if (!items.length) return;
5666
+ this.track("stream.pak_timing", {
5667
+ sessionId: s.id,
5668
+ seq: s.seq,
5669
+ items
5670
+ });
5671
+ }
5672
+ iceConnection$(...states) {
5673
+ return this.webRtcPlayer.iceState$.pipe(filter((e) => e.kind === "connection" && states.includes(e.state)));
5674
+ }
5675
5675
  updateMaxFpsTarget(maxFps) {
5676
5676
  if (this.session) this.session.maxFpsTarget = maxFps;
5677
5677
  }
@@ -5886,6 +5886,20 @@ function fmtCandidate(c) {
5886
5886
  if (!c?.type) return null;
5887
5887
  return c.url ? `${c.type} (${c.url})` : c.type;
5888
5888
  }
5889
+ function iceParams(ice) {
5890
+ if (!ice) return {};
5891
+ return {
5892
+ localCandidates: fmtCounts(ice.local),
5893
+ remoteCandidates: fmtCounts(ice.remote),
5894
+ candidatePairs: fmtCounts(ice.pairs),
5895
+ selectedLocal: fmtCandidate(ice.selected?.local),
5896
+ selectedRemote: fmtCandidate(ice.selected?.remote),
5897
+ selectedRttMs: ice.selectedRttMs ?? null
5898
+ };
5899
+ }
5900
+ function fmtCounts(counts) {
5901
+ return Object.entries(counts).map(([k, n]) => `${k} ${n}`).join(" · ") || null;
5902
+ }
5889
5903
  function toPhaseParams(value) {
5890
5904
  if (!value || typeof value !== "object") return [];
5891
5905
  const out = [];
@@ -7929,6 +7943,6 @@ function provideUnrealModule() {
7929
7943
  inject(AnalyticsService);
7930
7944
  })]);
7931
7945
  }
7932
- 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, 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 };
7946
+ 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 };
7933
7947
 
7934
7948
  //# sourceMappingURL=3dsource-angular-unreal-module.mjs.map