Skip to content
This repository was archived by the owner on Mar 1, 2024. It is now read-only.

Commit 4969548

Browse files
Add TCP+Relay detection event (#485)
* Change the label for the remote candidate * Added the following missing WebRTC stats id,timestamp,type,lastPacketReceivedTimestamp,lastPacketSentTimestamp,priority,remoteCandidateId,transportId and writable * Added relayProtocol and transport ID to the candidate Stat * Refactored the candidate pair to be an array as many pairs are generated * Implemeneted a WebRtcTCPRelayDetectedEvent that is fired when WebRTC transport is TCP AND we are using a relay candidate (e.g. a TURN server is being used). * Refactored the stats panel to use the `getActiveCandidatePair()`
1 parent e91781b commit 4969548

File tree

8 files changed

+95
-27
lines changed

8 files changed

+95
-27
lines changed

Frontend/library/src/PeerConnectionController/AggregatedStats.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export class AggregatedStats {
2525
inboundAudioStats: InboundAudioStats;
2626
lastVideoStats: InboundVideoStats;
2727
lastAudioStats: InboundAudioStats;
28-
candidatePair: CandidatePairStats;
28+
candidatePairs: Array<CandidatePairStats>;
2929
DataChannelStats: DataChannelStats;
3030
localCandidates: Array<CandidateStat>;
3131
remoteCandidates: Array<CandidateStat>;
@@ -37,7 +37,6 @@ export class AggregatedStats {
3737
constructor() {
3838
this.inboundVideoStats = new InboundVideoStats();
3939
this.inboundAudioStats = new InboundAudioStats();
40-
this.candidatePair = new CandidatePairStats();
4140
this.DataChannelStats = new DataChannelStats();
4241
this.outBoundVideoStats = new OutBoundVideoStats();
4342
this.sessionStats = new SessionStats();
@@ -52,6 +51,7 @@ export class AggregatedStats {
5251
processStats(rtcStatsReport: RTCStatsReport) {
5352
this.localCandidates = new Array<CandidateStat>();
5453
this.remoteCandidates = new Array<CandidateStat>();
54+
this.candidatePairs = new Array<CandidatePairStats>();
5555

5656
rtcStatsReport.forEach((stat) => {
5757
const type: RTCStatsTypePS = stat.type;
@@ -120,16 +120,10 @@ export class AggregatedStats {
120120
* @param stat - the stats coming in from ice candidates
121121
*/
122122
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;
123+
124+
// Add the candidate pair to the candidate pair array
125+
this.candidatePairs.push(stat)
126+
133127
}
134128

135129
/**
@@ -162,6 +156,8 @@ export class AggregatedStats {
162156
localCandidate.protocol = stat.protocol;
163157
localCandidate.candidateType = stat.candidateType;
164158
localCandidate.id = stat.id;
159+
localCandidate.relayProtocol = stat.relayProtocol;
160+
localCandidate.transportId = stat.transportId;
165161
this.localCandidates.push(localCandidate);
166162
}
167163

@@ -171,12 +167,14 @@ export class AggregatedStats {
171167
*/
172168
handleRemoteCandidate(stat: CandidateStat) {
173169
const RemoteCandidate = new CandidateStat();
174-
RemoteCandidate.label = 'local-candidate';
170+
RemoteCandidate.label = 'remote-candidate';
175171
RemoteCandidate.address = stat.address;
176172
RemoteCandidate.port = stat.port;
177173
RemoteCandidate.protocol = stat.protocol;
178174
RemoteCandidate.id = stat.id;
179175
RemoteCandidate.candidateType = stat.candidateType;
176+
RemoteCandidate.relayProtocol = stat.relayProtocol;
177+
RemoteCandidate.transportId = stat.transportId
180178
this.remoteCandidates.push(RemoteCandidate);
181179
}
182180

@@ -308,4 +306,12 @@ export class AggregatedStats {
308306
isNumber(value: unknown): boolean {
309307
return typeof value === 'number' && isFinite(value);
310308
}
309+
310+
/**
311+
* Helper function to return the active candidate pair
312+
* @returns The candidate pair that is currently receiving data
313+
*/
314+
public getActiveCandidatePair(): CandidatePairStats | null {
315+
return this.candidatePairs.find((candidatePair) => candidatePair.bytesReceived > 0, null)
316+
}
311317
}

Frontend/library/src/PeerConnectionController/CandidatePairStats.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,19 @@
66
export class CandidatePairStats {
77
bytesReceived: number;
88
bytesSent: number;
9+
currentRoundTripTime: number;
10+
id: string;
11+
lastPacketReceivedTimestamp: number;
12+
lastPacketSentTimestamp: number;
913
localCandidateId: string;
10-
remoteCandidateId: string;
1114
nominated: boolean;
15+
priority: number;
1216
readable: boolean;
13-
writable: boolean;
17+
remoteCandidateId: string;
1418
selected: boolean;
1519
state: string;
16-
currentRoundTripTime: number;
20+
timestamp: number;
21+
transportId: string;
22+
type: string;
23+
writable: boolean;
1724
}

Frontend/library/src/PeerConnectionController/CandidateStat.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
* ICE Candidate Stat collected from the RTC Stats Report
55
*/
66
export class CandidateStat {
7-
label: string;
8-
id: string;
97
address: string;
108
candidateType: string;
9+
id: string;
10+
label: string;
1111
port: number;
1212
protocol: 'tcp' | 'udp';
13+
relayProtocol: 'tcp' | 'udp' | 'tls';
14+
transportId: string;
1315
}

Frontend/library/src/PixelStreaming/PixelStreaming.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,9 @@ describe('PixelStreaming', () => {
398398
expect.objectContaining({
399399
data: {
400400
aggregatedStats: expect.objectContaining({
401-
candidatePair: expect.objectContaining({
402-
bytesReceived: 123
403-
}),
401+
candidatePairs: [
402+
expect.objectContaining({ bytesReceived: 123 })
403+
],
404404
localCandidates: [
405405
expect.objectContaining({ address: 'mock-address' })
406406
]

Frontend/library/src/PixelStreaming/PixelStreaming.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ import {
2828
WebRtcSdpEvent,
2929
DataChannelLatencyTestResponseEvent,
3030
DataChannelLatencyTestResultEvent,
31-
PlayerCountEvent
31+
PlayerCountEvent,
32+
WebRtcTCPRelayDetectedEvent
3233
} from '../Util/EventEmitter';
3334
import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive';
3435
import { WebXRController } from '../WebXR/WebXRController';
@@ -62,6 +63,7 @@ export class PixelStreaming {
6263
protected _webRtcController: WebRtcPlayerController;
6364
protected _webXrController: WebXRController;
6465
protected _dataChannelLatencyTestController: DataChannelLatencyTestController;
66+
6567
/**
6668
* Configuration object. You can read or modify config through this object. Whenever
6769
* the configuration is changed, the library will emit a `settingsChanged` event.
@@ -116,6 +118,13 @@ export class PixelStreaming {
116118
this.onScreenKeyboardHelper.showOnScreenKeyboard(command);
117119

118120
this._webXrController = new WebXRController(this._webRtcController);
121+
122+
// Add event listener for the webRtcConnected event
123+
this._eventEmitter.addEventListener("webRtcConnected", (webRtcConnectedEvent: WebRtcConnectedEvent) => {
124+
125+
// Bind to the stats received event
126+
this._eventEmitter.addEventListener("statsReceived", this._setupWebRtcTCPRelayDetection.bind(this));
127+
});
119128
}
120129

121130
/**
@@ -627,6 +636,28 @@ export class PixelStreaming {
627636
);
628637
}
629638

639+
// Sets up to emit the webrtc tcp relay detect event
640+
_setupWebRtcTCPRelayDetection(statsReceivedEvent: StatsReceivedEvent) {
641+
// Get the active candidate pair
642+
let activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();
643+
644+
// Check if the active candidate pair is not null
645+
if (activeCandidatePair != null) {
646+
647+
// Get the local candidate assigned to the active candidate pair
648+
let localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)
649+
650+
// Check if the local candidate is not null, candidate type is relay and the relay protocol is tcp
651+
if (localCandidate != null && localCandidate.candidateType == 'relay' && localCandidate.relayProtocol == 'tcp') {
652+
653+
// Send the web rtc tcp relay detected event
654+
this._eventEmitter.dispatchEvent(new WebRtcTCPRelayDetectedEvent());
655+
}
656+
// The check is completed and the stats listen event can be removed
657+
this._eventEmitter.removeEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
658+
}
659+
}
660+
630661
/**
631662
* Request a connection latency test.
632663
* NOTE: There are plans to refactor all request* functions. Expect changes if you use this!

Frontend/library/src/Util/EventEmitter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,16 @@ export class PlayerCountEvent extends Event {
537537
}
538538
}
539539

540+
/**
541+
* An event that is emitted when the webRTC connections is relayed over TCP.
542+
*/
543+
export class WebRtcTCPRelayDetectedEvent extends Event {
544+
readonly type: 'webRtcTCPRelayDetected';
545+
constructor() {
546+
super('webRtcTCPRelayDetected');
547+
}
548+
}
549+
540550
export type PixelStreamingEvent =
541551
| AfkWarningActivateEvent
542552
| AfkWarningUpdateEvent
@@ -573,7 +583,8 @@ export type PixelStreamingEvent =
573583
| XrSessionStartedEvent
574584
| XrSessionEndedEvent
575585
| XrFrameEvent
576-
| PlayerCountEvent;
586+
| PlayerCountEvent
587+
| WebRtcTCPRelayDetectedEvent;
577588

578589
export class EventEmitter extends EventTarget {
579590
/**

Frontend/ui-library/src/Application/Application.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,14 @@ export class Application {
378378
({ data: { count }}) =>
379379
this.onPlayerCount(count)
380380
);
381+
this.stream.addEventListener(
382+
'webRtcTCPRelayDetected',
383+
({}) =>
384+
Logger.Warning(
385+
Logger.GetStackTrace(),
386+
`Stream quailty degraded due to network enviroment, stream is relayed over TCP.`
387+
)
388+
);
381389
}
382390

383391
/**

Frontend/ui-library/src/UI/StatsPanel.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright Epic Games, Inc. All Rights Reserved.
22

33
import { LatencyTest } from './LatencyTest';
4-
import { InitialSettings, Logger, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
4+
import { CandidatePairStats, InitialSettings, Logger, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
55
import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
66
import { MathUtils } from '../Util/MathUtils';
77
import {DataChannelLatencyTest} from "./DataChannelLatencyTest";
@@ -318,14 +318,17 @@ export class StatsPanel {
318318
);
319319
}
320320

321+
// Store the active candidate pair return a new Candidate pair stat if getActiveCandidate is null
322+
let activeCandidatePair = stats.getActiveCandidatePair() != null ? stats.getActiveCandidatePair() : new CandidatePairStats();
323+
321324
// RTT
322325
const netRTT =
323326
Object.prototype.hasOwnProperty.call(
324-
stats.candidatePair,
327+
activeCandidatePair,
325328
'currentRoundTripTime'
326-
) && stats.isNumber(stats.candidatePair.currentRoundTripTime)
329+
) && stats.isNumber(activeCandidatePair.currentRoundTripTime)
327330
? numberFormat.format(
328-
stats.candidatePair.currentRoundTripTime * 1000
331+
activeCandidatePair.currentRoundTripTime * 1000
329332
)
330333
: "Can't calculate";
331334
this.addOrUpdateStat('RTTStat', 'Net RTT (ms)', netRTT);

0 commit comments

Comments
 (0)