@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.8 → 0.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.5",
3
- "version": "0.0.8",
3
+ "version": "0.0.11",
4
4
  "description": "Frontend library for Unreal Engine 5.5 Pixel Streaming",
5
5
  "main": "dist/lib-pixelstreamingfrontend.js",
6
6
  "module": "dist/lib-pixelstreamingfrontend.esm.js",
@@ -16,11 +16,10 @@
16
16
  "spellcheck": "cspell \"{README.md,.github/*.md,src/**/*.ts}\""
17
17
  },
18
18
  "devDependencies": {
19
- "@epicgames-ps/lib-pixelstreamingcommon-ue5.5": "^0.0.8",
19
+ "@epicgames-ps/lib-pixelstreamingcommon-ue5.5": "^0.0.13",
20
20
  "@types/jest": "27.5.1",
21
21
  "@types/webxr": "^0.5.1",
22
- "@typescript-eslint/eslint-plugin": "^5.16.0",
23
- "@typescript-eslint/parser": "^5.16.0",
22
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
24
23
  "cspell": "^4.1.0",
25
24
  "eslint": "^8.11.0",
26
25
  "jest": "^27.5.1",
@@ -38,7 +37,7 @@
38
37
  },
39
38
  "repository": {
40
39
  "type": "git",
41
- "url": "https://github.com/EpicGames/PixelStreamingInfrastructure.git"
40
+ "url": "https://github.com/EpicGamesExt/PixelStreamingInfrastructure.git"
42
41
  },
43
42
  "author": "Epic Games",
44
43
  "license": "MIT",
@@ -46,4 +45,3 @@
46
45
  "access": "public"
47
46
  }
48
47
  }
49
-
@@ -31,7 +31,8 @@ export class Flags {
31
31
  static TouchInput = 'TouchInput' as const;
32
32
  static GamepadInput = 'GamepadInput' as const;
33
33
  static XRControllerInput = 'XRControllerInput' as const;
34
- static WaitForStreamer = "WaitForStreamer" as const;
34
+ static WaitForStreamer = 'WaitForStreamer' as const;
35
+ static HideUI = 'HideUI' as const;
35
36
  }
36
37
 
37
38
  export type FlagsKeys = Exclude<keyof typeof Flags, 'prototype'>;
@@ -181,7 +182,7 @@ export class Config {
181
182
  TextParameters.SignallingServerUrl,
182
183
  'Signalling url',
183
184
  'Url of the signalling server',
184
- settings && settings.hasOwnProperty(TextParameters.SignallingServerUrl) ?
185
+ settings && Object.prototype.hasOwnProperty.call(settings, TextParameters.SignallingServerUrl) ?
185
186
  settings[TextParameters.SignallingServerUrl] :
186
187
  (location.protocol === 'https:' ? 'wss://' : 'ws://') +
187
188
  window.location.hostname +
@@ -200,7 +201,7 @@ export class Config {
200
201
  OptionParameters.StreamerId,
201
202
  'Streamer ID',
202
203
  'The ID of the streamer to stream.',
203
- settings && settings.hasOwnProperty(OptionParameters.StreamerId) ?
204
+ settings && Object.prototype.hasOwnProperty.call(settings, OptionParameters.StreamerId) ?
204
205
  settings[OptionParameters.StreamerId] :
205
206
  '',
206
207
  [],
@@ -218,7 +219,7 @@ export class Config {
218
219
  'Preferred Codec',
219
220
  'The preferred codec to be used during codec negotiation',
220
221
  'H264 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f',
221
- settings && settings.hasOwnProperty(OptionParameters.PreferredCodec) ?
222
+ settings && Object.prototype.hasOwnProperty.call(settings, OptionParameters.PreferredCodec) ?
222
223
  [settings[OptionParameters.PreferredCodec]] :
223
224
  (function (): Array<string> {
224
225
  const browserSupportedCodecs: Array<string> = [];
@@ -257,7 +258,7 @@ export class Config {
257
258
  Flags.AutoConnect,
258
259
  'Auto connect to stream',
259
260
  'Whether we should attempt to auto connect to the signalling server or show a click to start prompt.',
260
- settings && settings.hasOwnProperty(Flags.AutoConnect) ?
261
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.AutoConnect) ?
261
262
  settings[Flags.AutoConnect] :
262
263
  false,
263
264
  useUrlParams
@@ -270,7 +271,7 @@ export class Config {
270
271
  Flags.AutoPlayVideo,
271
272
  'Auto play video',
272
273
  'When video is ready automatically start playing it as opposed to showing a play button.',
273
- settings && settings.hasOwnProperty(Flags.AutoPlayVideo) ?
274
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.AutoPlayVideo) ?
274
275
  settings[Flags.AutoPlayVideo] :
275
276
  true,
276
277
  useUrlParams
@@ -283,7 +284,7 @@ export class Config {
283
284
  Flags.BrowserSendOffer,
284
285
  'Browser send offer',
285
286
  'Browser will initiate the WebRTC handshake by sending the offer to the streamer',
286
- settings && settings.hasOwnProperty(Flags.BrowserSendOffer) ?
287
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.BrowserSendOffer) ?
287
288
  settings[Flags.BrowserSendOffer] :
288
289
  false,
289
290
  useUrlParams
@@ -296,7 +297,7 @@ export class Config {
296
297
  Flags.UseMic,
297
298
  'Use microphone',
298
299
  'Make browser request microphone access and open an input audio track.',
299
- settings && settings.hasOwnProperty(Flags.UseMic) ?
300
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.UseMic) ?
300
301
  settings[Flags.UseMic] :
301
302
  false,
302
303
  useUrlParams
@@ -309,7 +310,7 @@ export class Config {
309
310
  Flags.StartVideoMuted,
310
311
  'Start video muted',
311
312
  'Video will start muted if true.',
312
- settings && settings.hasOwnProperty(Flags.StartVideoMuted) ?
313
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.StartVideoMuted) ?
313
314
  settings[Flags.StartVideoMuted] :
314
315
  false,
315
316
  useUrlParams
@@ -322,7 +323,7 @@ export class Config {
322
323
  Flags.SuppressBrowserKeys,
323
324
  'Suppress browser keys',
324
325
  'Suppress certain browser keys that we use in UE, for example F5 to show shader complexity instead of refresh the page.',
325
- settings && settings.hasOwnProperty(Flags.SuppressBrowserKeys) ?
326
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.SuppressBrowserKeys) ?
326
327
  settings[Flags.SuppressBrowserKeys] :
327
328
  true,
328
329
  useUrlParams
@@ -335,7 +336,7 @@ export class Config {
335
336
  Flags.IsQualityController,
336
337
  'Is quality controller?',
337
338
  'True if this peer controls stream quality',
338
- settings && settings.hasOwnProperty(Flags.IsQualityController) ?
339
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.IsQualityController) ?
339
340
  settings[Flags.IsQualityController] :
340
341
  true,
341
342
  useUrlParams
@@ -348,7 +349,7 @@ export class Config {
348
349
  Flags.ForceMonoAudio,
349
350
  'Force mono audio',
350
351
  'Force browser to request mono audio in the SDP',
351
- settings && settings.hasOwnProperty(Flags.ForceMonoAudio) ?
352
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.ForceMonoAudio) ?
352
353
  settings[Flags.ForceMonoAudio] :
353
354
  false,
354
355
  useUrlParams
@@ -361,7 +362,7 @@ export class Config {
361
362
  Flags.ForceTURN,
362
363
  'Force TURN',
363
364
  'Only generate TURN/Relayed ICE candidates.',
364
- settings && settings.hasOwnProperty(Flags.ForceTURN) ?
365
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.ForceTURN) ?
365
366
  settings[Flags.ForceTURN] :
366
367
  false,
367
368
  useUrlParams
@@ -374,7 +375,7 @@ export class Config {
374
375
  Flags.AFKDetection,
375
376
  'AFK if idle',
376
377
  'Timeout the experience if user is AFK for a period.',
377
- settings && settings.hasOwnProperty(Flags.AFKDetection) ?
378
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.AFKDetection) ?
378
379
  settings[Flags.AFKDetection] :
379
380
  false,
380
381
  useUrlParams
@@ -387,7 +388,7 @@ export class Config {
387
388
  Flags.MatchViewportResolution,
388
389
  'Match viewport resolution',
389
390
  'Pixel Streaming will be instructed to dynamically resize the video stream to match the size of the video element.',
390
- settings && settings.hasOwnProperty(Flags.MatchViewportResolution) ?
391
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.MatchViewportResolution) ?
391
392
  settings[Flags.MatchViewportResolution] :
392
393
  false,
393
394
  useUrlParams
@@ -400,7 +401,7 @@ export class Config {
400
401
  Flags.HoveringMouseMode,
401
402
  'Control Scheme: Locked Mouse',
402
403
  'Either locked mouse, where the pointer is consumed by the video and locked to it, or hovering mouse, where the mouse is not consumed.',
403
- settings && settings.hasOwnProperty(Flags.HoveringMouseMode) ?
404
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.HoveringMouseMode) ?
404
405
  settings[Flags.HoveringMouseMode] :
405
406
  false,
406
407
  useUrlParams,
@@ -416,7 +417,7 @@ export class Config {
416
417
  Flags.FakeMouseWithTouches,
417
418
  'Fake mouse with touches',
418
419
  'A single finger touch is converted into a mouse event. This allows a non-touch application to be controlled partially via a touch device.',
419
- settings && settings.hasOwnProperty(Flags.FakeMouseWithTouches) ?
420
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.FakeMouseWithTouches) ?
420
421
  settings[Flags.FakeMouseWithTouches] :
421
422
  true,
422
423
  useUrlParams
@@ -429,7 +430,7 @@ export class Config {
429
430
  Flags.KeyboardInput,
430
431
  'Keyboard input',
431
432
  'If enabled, send keyboard events to streamer',
432
- settings && settings.hasOwnProperty(Flags.KeyboardInput) ?
433
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.KeyboardInput) ?
433
434
  settings[Flags.KeyboardInput] :
434
435
  true,
435
436
  useUrlParams
@@ -442,7 +443,7 @@ export class Config {
442
443
  Flags.MouseInput,
443
444
  'Mouse input',
444
445
  'If enabled, send mouse events to streamer',
445
- settings && settings.hasOwnProperty(Flags.MouseInput) ?
446
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.MouseInput) ?
446
447
  settings[Flags.MouseInput] :
447
448
  true,
448
449
  useUrlParams
@@ -455,7 +456,7 @@ export class Config {
455
456
  Flags.TouchInput,
456
457
  'Touch input',
457
458
  'If enabled, send touch events to streamer',
458
- settings && settings.hasOwnProperty(Flags.TouchInput) ?
459
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.TouchInput) ?
459
460
  settings[Flags.TouchInput] :
460
461
  true,
461
462
  useUrlParams
@@ -468,7 +469,7 @@ export class Config {
468
469
  Flags.GamepadInput,
469
470
  'Gamepad input',
470
471
  'If enabled, send gamepad events to streamer',
471
- settings && settings.hasOwnProperty(Flags.GamepadInput) ?
472
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.GamepadInput) ?
472
473
  settings[Flags.GamepadInput] :
473
474
  true,
474
475
  useUrlParams
@@ -481,7 +482,7 @@ export class Config {
481
482
  Flags.XRControllerInput,
482
483
  'XR controller input',
483
484
  'If enabled, send XR controller events to streamer',
484
- settings && settings.hasOwnProperty(Flags.XRControllerInput) ?
485
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.XRControllerInput) ?
485
486
  settings[Flags.XRControllerInput] :
486
487
  true,
487
488
  useUrlParams
@@ -494,12 +495,25 @@ export class Config {
494
495
  Flags.WaitForStreamer,
495
496
  'Wait for streamer',
496
497
  'Will continue trying to connect to the first streamer available.',
497
- settings && settings.hasOwnProperty(Flags.WaitForStreamer) ?
498
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.WaitForStreamer) ?
498
499
  settings[Flags.WaitForStreamer] :
499
500
  true,
500
501
  useUrlParams
501
502
  )
502
503
  );
504
+
505
+ this.flags.set(
506
+ Flags.HideUI,
507
+ new SettingFlag(
508
+ Flags.HideUI,
509
+ 'Hide the UI overlay',
510
+ 'Will hide all UI overlay details',
511
+ settings && settings.hasOwnProperty(Flags.HideUI) ?
512
+ settings[Flags.HideUI] :
513
+ false,
514
+ useUrlParams
515
+ )
516
+ );
503
517
 
504
518
  /**
505
519
  * Numeric parameters
@@ -513,7 +527,7 @@ export class Config {
513
527
  'The time (in seconds) it takes for the application to time out if AFK timeout is enabled.',
514
528
  0 /*min*/,
515
529
  600 /*max*/,
516
- settings && settings.hasOwnProperty(NumericParameters.AFKTimeoutSecs) ?
530
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.AFKTimeoutSecs) ?
517
531
  settings[NumericParameters.AFKTimeoutSecs] :
518
532
  120, /*value*/
519
533
  useUrlParams
@@ -528,7 +542,7 @@ export class Config {
528
542
  'Maximum number of reconnects the application will attempt when a streamer disconnects.',
529
543
  0 /*min*/,
530
544
  999 /*max*/,
531
- settings && settings.hasOwnProperty(NumericParameters.MaxReconnectAttempts) ?
545
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.MaxReconnectAttempts) ?
532
546
  settings[NumericParameters.MaxReconnectAttempts] :
533
547
  3, /*value*/
534
548
  useUrlParams
@@ -543,7 +557,7 @@ export class Config {
543
557
  'The lower bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
544
558
  0 /*min*/,
545
559
  51 /*max*/,
546
- settings && settings.hasOwnProperty(NumericParameters.MinQP) ?
560
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.MinQP) ?
547
561
  settings[NumericParameters.MinQP] :
548
562
  0, /*value*/
549
563
  useUrlParams
@@ -558,7 +572,7 @@ export class Config {
558
572
  'The upper bound for the quantization parameter (QP) of the encoder. 0 = Best quality, 51 = worst quality.',
559
573
  0 /*min*/,
560
574
  51 /*max*/,
561
- settings && settings.hasOwnProperty(NumericParameters.MaxQP) ?
575
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.MaxQP) ?
562
576
  settings[NumericParameters.MaxQP] :
563
577
  51, /*value*/
564
578
  useUrlParams
@@ -573,7 +587,7 @@ export class Config {
573
587
  'The maximum FPS that WebRTC will try to transmit frames at.',
574
588
  1 /*min*/,
575
589
  999 /*max*/,
576
- settings && settings.hasOwnProperty(NumericParameters.WebRTCFPS) ?
590
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.WebRTCFPS) ?
577
591
  settings[NumericParameters.WebRTCFPS] :
578
592
  60, /*value*/
579
593
  useUrlParams
@@ -588,7 +602,7 @@ export class Config {
588
602
  'The minimum bitrate that WebRTC should use.',
589
603
  0 /*min*/,
590
604
  500000 /*max*/,
591
- settings && settings.hasOwnProperty(NumericParameters.WebRTCMinBitrate) ?
605
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.WebRTCMinBitrate) ?
592
606
  settings[NumericParameters.WebRTCMinBitrate] :
593
607
  0, /*value*/
594
608
  useUrlParams
@@ -603,7 +617,7 @@ export class Config {
603
617
  'The maximum bitrate that WebRTC should use.',
604
618
  0 /*min*/,
605
619
  500000 /*max*/,
606
- settings && settings.hasOwnProperty(NumericParameters.WebRTCMaxBitrate) ?
620
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.WebRTCMaxBitrate) ?
607
621
  settings[NumericParameters.WebRTCMaxBitrate] :
608
622
  0, /*value*/
609
623
  useUrlParams
@@ -618,7 +632,7 @@ export class Config {
618
632
  'Delay between retries when waiting for an available streamer.',
619
633
  500 /*min*/,
620
634
  900000 /*max*/,
621
- settings && settings.hasOwnProperty(NumericParameters.StreamerAutoJoinInterval) ?
635
+ settings && Object.prototype.hasOwnProperty.call(settings, NumericParameters.StreamerAutoJoinInterval) ?
622
636
  settings[NumericParameters.StreamerAutoJoinInterval] :
623
637
  3000, /*value*/
624
638
  useUrlParams
@@ -24,7 +24,7 @@ export class SettingOption<
24
24
  // eslint-disable-next-line @typescript-eslint/no-empty-function
25
25
  defaultOnChangeListener: (changedValue: unknown, setting: SettingBase) => void = () => { /* Do nothing, to be overridden. */ }
26
26
  ) {
27
- super(id, label, description, [defaultTextValue], defaultOnChangeListener);
27
+ super(id, label, description, defaultTextValue, defaultOnChangeListener);
28
28
 
29
29
  this.options = options;
30
30
  const urlParams = new URLSearchParams(window.location.search);
@@ -105,15 +105,15 @@ export class DataChannelLatencyTestController {
105
105
  );
106
106
  return;
107
107
  }
108
- let record = this.records.get(response.Seq);
108
+ const record = this.records.get(response.Seq);
109
109
  if (record) {
110
110
  record.update(response);
111
111
  }
112
112
  }
113
113
 
114
114
  sendRequest(requestSize: number, responseSize: number) {
115
- let request = this.createRequest(requestSize, responseSize);
116
- let record = new DataChannelLatencyTestRecord(request);
115
+ const request = this.createRequest(requestSize, responseSize);
116
+ const record = new DataChannelLatencyTestRecord(request);
117
117
  this.records.set(record.seq, record);
118
118
  this.sink(request);
119
119
  }
@@ -260,7 +260,7 @@ export class GamePadController {
260
260
  // Default Functionality: Do Nothing
261
261
  }
262
262
 
263
- onBeforeUnload(ev: Event) {
263
+ onBeforeUnload(_: Event) {
264
264
  // When a user navigates away from the page, we need to inform UE of all the disconnecting
265
265
  // controllers
266
266
  for(const controller of this.controllers) {
@@ -285,6 +285,8 @@ declare global {
285
285
  }
286
286
  }
287
287
 
288
+ /* eslint-disable @typescript-eslint/no-duplicate-enum-values */
289
+
288
290
  /**
289
291
  * Gamepad layout codes enum
290
292
  */
@@ -312,3 +314,6 @@ export enum gamepadLayout {
312
314
  RightStickHorizontal = 2,
313
315
  RightStickVertical = 3
314
316
  }
317
+
318
+ /* eslint-enable @typescript-eslint/no-duplicate-enum-values */
319
+
@@ -178,7 +178,7 @@ export class TouchController implements ITouchController {
178
178
  coord.x,
179
179
  coord.y,
180
180
  this.fingerIds.get(touch.identifier),
181
- this.maxByteValue * touch.force,
181
+ this.maxByteValue * (touch.force > 0 ? touch.force : 1),
182
182
  coord.inRange ? 1 : 0
183
183
  ]);
184
184
  break;
@@ -198,7 +198,7 @@ export class TouchController implements ITouchController {
198
198
  coord.x,
199
199
  coord.y,
200
200
  this.fingerIds.get(touch.identifier),
201
- this.maxByteValue * touch.force,
201
+ this.maxByteValue * (touch.force > 0 ? touch.force : 1),
202
202
  coord.inRange ? 1 : 0
203
203
  ]);
204
204
  break;
@@ -25,7 +25,7 @@ export class AggregatedStats {
25
25
  inboundAudioStats: InboundAudioStats;
26
26
  lastVideoStats: InboundVideoStats;
27
27
  lastAudioStats: InboundAudioStats;
28
- candidatePair: CandidatePairStats;
28
+ candidatePairs: Array<CandidatePairStats>;
29
29
  DataChannelStats: DataChannelStats;
30
30
  localCandidates: Array<CandidateStat>;
31
31
  remoteCandidates: Array<CandidateStat>;
@@ -33,11 +33,11 @@ export class AggregatedStats {
33
33
  sessionStats: SessionStats;
34
34
  streamStats: StreamStats;
35
35
  codecs: Map<string, string>;
36
+ transportStats: RTCTransportStats;
36
37
 
37
38
  constructor() {
38
39
  this.inboundVideoStats = new InboundVideoStats();
39
40
  this.inboundAudioStats = new InboundAudioStats();
40
- this.candidatePair = new CandidatePairStats();
41
41
  this.DataChannelStats = new DataChannelStats();
42
42
  this.outBoundVideoStats = new OutBoundVideoStats();
43
43
  this.sessionStats = new SessionStats();
@@ -52,6 +52,7 @@ export class AggregatedStats {
52
52
  processStats(rtcStatsReport: RTCStatsReport) {
53
53
  this.localCandidates = new Array<CandidateStat>();
54
54
  this.remoteCandidates = new Array<CandidateStat>();
55
+ this.candidatePairs = new Array<CandidatePairStats>();
55
56
 
56
57
  rtcStatsReport.forEach((stat) => {
57
58
  const type: RTCStatsTypePS = stat.type;
@@ -94,6 +95,7 @@ export class AggregatedStats {
94
95
  this.handleTrack(stat);
95
96
  break;
96
97
  case 'transport':
98
+ this.handleTransport(stat);
97
99
  break;
98
100
  case 'stream':
99
101
  this.handleStream(stat);
@@ -120,16 +122,10 @@ export class AggregatedStats {
120
122
  * @param stat - the stats coming in from ice candidates
121
123
  */
122
124
  handleCandidatePair(stat: CandidatePairStats) {
123
- this.candidatePair.bytesReceived = stat.bytesReceived;
124
- this.candidatePair.bytesSent = stat.bytesSent;
125
- this.candidatePair.localCandidateId = stat.localCandidateId;
126
- this.candidatePair.remoteCandidateId = stat.remoteCandidateId;
127
- this.candidatePair.nominated = stat.nominated;
128
- this.candidatePair.readable = stat.readable;
129
- this.candidatePair.selected = stat.selected;
130
- this.candidatePair.writable = stat.writable;
131
- this.candidatePair.state = stat.state;
132
- this.candidatePair.currentRoundTripTime = stat.currentRoundTripTime;
125
+
126
+ // Add the candidate pair to the candidate pair array
127
+ this.candidatePairs.push(stat)
128
+
133
129
  }
134
130
 
135
131
  /**
@@ -162,6 +158,8 @@ export class AggregatedStats {
162
158
  localCandidate.protocol = stat.protocol;
163
159
  localCandidate.candidateType = stat.candidateType;
164
160
  localCandidate.id = stat.id;
161
+ localCandidate.relayProtocol = stat.relayProtocol;
162
+ localCandidate.transportId = stat.transportId;
165
163
  this.localCandidates.push(localCandidate);
166
164
  }
167
165
 
@@ -171,12 +169,14 @@ export class AggregatedStats {
171
169
  */
172
170
  handleRemoteCandidate(stat: CandidateStat) {
173
171
  const RemoteCandidate = new CandidateStat();
174
- RemoteCandidate.label = 'local-candidate';
172
+ RemoteCandidate.label = 'remote-candidate';
175
173
  RemoteCandidate.address = stat.address;
176
174
  RemoteCandidate.port = stat.port;
177
175
  RemoteCandidate.protocol = stat.protocol;
178
176
  RemoteCandidate.id = stat.id;
179
177
  RemoteCandidate.candidateType = stat.candidateType;
178
+ RemoteCandidate.relayProtocol = stat.relayProtocol;
179
+ RemoteCandidate.transportId = stat.transportId
180
180
  this.remoteCandidates.push(RemoteCandidate);
181
181
  }
182
182
 
@@ -269,6 +269,11 @@ export class AggregatedStats {
269
269
  }
270
270
  }
271
271
 
272
+ handleTransport(stat: RTCTransportStats){
273
+ this.transportStats = stat;
274
+ }
275
+
276
+
272
277
  handleCodec(stat: CodecStats) {
273
278
  const codecId = stat.id;
274
279
  const codecType = `${stat.mimeType
@@ -308,4 +313,20 @@ export class AggregatedStats {
308
313
  isNumber(value: unknown): boolean {
309
314
  return typeof value === 'number' && isFinite(value);
310
315
  }
316
+
317
+ /**
318
+ * Helper function to return the active candidate pair
319
+ * @returns The candidate pair that is currently receiving data
320
+ */
321
+ public getActiveCandidatePair(): CandidatePairStats | null {
322
+
323
+ // Check if the RTCTransport stat is not undefined
324
+ if (this.transportStats){
325
+ // Return the candidate pair that matches the transport candidate pair id
326
+ return this.candidatePairs.find((candidatePair) => candidatePair.id === this.transportStats.selectedCandidatePairId, null);
327
+ }
328
+
329
+ // Fall back to the selected candidate pair
330
+ return this.candidatePairs.find((candidatePair) => candidatePair.selected, null);
331
+ }
311
332
  }
@@ -6,12 +6,19 @@
6
6
  export class CandidatePairStats {
7
7
  bytesReceived: number;
8
8
  bytesSent: number;
9
+ currentRoundTripTime: number;
10
+ id: string;
11
+ lastPacketReceivedTimestamp: number;
12
+ lastPacketSentTimestamp: number;
9
13
  localCandidateId: string;
10
- remoteCandidateId: string;
11
14
  nominated: boolean;
15
+ priority: number;
12
16
  readable: boolean;
13
- writable: boolean;
17
+ remoteCandidateId: string;
14
18
  selected: boolean;
15
19
  state: string;
16
- currentRoundTripTime: number;
20
+ timestamp: number;
21
+ transportId: string;
22
+ type: string;
23
+ writable: boolean;
17
24
  }
@@ -4,10 +4,12 @@
4
4
  * ICE Candidate Stat collected from the RTC Stats Report
5
5
  */
6
6
  export class CandidateStat {
7
- label: string;
8
- id: string;
9
7
  address: string;
10
8
  candidateType: string;
9
+ id: string;
10
+ label: string;
11
11
  port: number;
12
12
  protocol: 'tcp' | 'udp';
13
+ relayProtocol: 'tcp' | 'udp' | 'tls';
14
+ transportId: string;
13
15
  }
@@ -400,9 +400,9 @@ describe('PixelStreaming', () => {
400
400
  expect.objectContaining({
401
401
  data: {
402
402
  aggregatedStats: expect.objectContaining({
403
- candidatePair: expect.objectContaining({
404
- bytesReceived: 123
405
- }),
403
+ candidatePairs: [
404
+ expect.objectContaining({ bytesReceived: 123 })
405
+ ],
406
406
  localCandidates: [
407
407
  expect.objectContaining({ address: 'mock-address' })
408
408
  ]
@@ -28,7 +28,8 @@ import {
28
28
  WebRtcSdpEvent,
29
29
  DataChannelLatencyTestResponseEvent,
30
30
  DataChannelLatencyTestResultEvent,
31
- PlayerCountEvent
31
+ PlayerCountEvent,
32
+ WebRtcTCPRelayDetectedEvent
32
33
  } from '../Util/EventEmitter';
33
34
  import { WebXRController } from '../WebXR/WebXRController';
34
35
  import { MessageDirection } from '../UeInstanceMessage/StreamMessageController';
@@ -61,6 +62,7 @@ export class PixelStreaming {
61
62
  protected _webRtcController: WebRtcPlayerController;
62
63
  protected _webXrController: WebXRController;
63
64
  protected _dataChannelLatencyTestController: DataChannelLatencyTestController;
65
+
64
66
  /**
65
67
  * Configuration object. You can read or modify config through this object. Whenever
66
68
  * the configuration is changed, the library will emit a `settingsChanged` event.
@@ -115,6 +117,15 @@ export class PixelStreaming {
115
117
  this.onScreenKeyboardHelper.showOnScreenKeyboard(command);
116
118
 
117
119
  this._webXrController = new WebXRController(this._webRtcController);
120
+
121
+ this._setupWebRtcTCPRelayDetection = this._setupWebRtcTCPRelayDetection.bind(this)
122
+
123
+ // Add event listener for the webRtcConnected event
124
+ this._eventEmitter.addEventListener("webRtcConnected", (webRtcConnectedEvent: WebRtcConnectedEvent) => {
125
+
126
+ // Bind to the stats received event
127
+ this._eventEmitter.addEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
128
+ });
118
129
  }
119
130
 
120
131
  /**
@@ -626,6 +637,28 @@ export class PixelStreaming {
626
637
  );
627
638
  }
628
639
 
640
+ // Sets up to emit the webrtc tcp relay detect event
641
+ _setupWebRtcTCPRelayDetection(statsReceivedEvent: StatsReceivedEvent) {
642
+ // Get the active candidate pair
643
+ let activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();
644
+
645
+ // Check if the active candidate pair is not null
646
+ if (activeCandidatePair != null) {
647
+
648
+ // Get the local candidate assigned to the active candidate pair
649
+ let localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)
650
+
651
+ // Check if the local candidate is not null, candidate type is relay and the relay protocol is tcp
652
+ if (localCandidate != null && localCandidate.candidateType == 'relay' && localCandidate.relayProtocol == 'tcp') {
653
+
654
+ // Send the web rtc tcp relay detected event
655
+ this._eventEmitter.dispatchEvent(new WebRtcTCPRelayDetectedEvent());
656
+ }
657
+ // The check is completed and the stats listen event can be removed
658
+ this._eventEmitter.removeEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
659
+ }
660
+ }
661
+
629
662
  /**
630
663
  * Request a connection latency test.
631
664
  * NOTE: There are plans to refactor all request* functions. Expect changes if you use this!
@@ -35,10 +35,7 @@ export class OnScreenKeyboard {
35
35
  * @returns unquantizeAndDenormalizeUnsigned object
36
36
  */
37
37
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
- unquantizeAndDenormalizeUnsigned(
39
- x: number,
40
- y: number
41
- ): UnquantizedDenormalizedUnsignedCoord {
38
+ unquantizeAndDenormalizeUnsigned(x: number, y: number): UnquantizedDenormalizedUnsignedCoord {
42
39
  return null;
43
40
  }
44
41