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

Add TCP+Relay detection event #485

Merged
merged 22 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8b5bbd8
Change the label for the remote candidate
david-macpherson Feb 13, 2024
daff998
Added the following missing properties id,timestamp,type,lastPacketRe…
david-macpherson Feb 13, 2024
5542d91
Added relayProtocol and transport ID to the candidate Stat
david-macpherson Feb 13, 2024
1be9ed5
Added relayProtocol and transport ID to the candidate Stat
david-macpherson Feb 13, 2024
03ee5b6
Set the parsed candidate pair stat to the nominated and selected pair
david-macpherson Feb 13, 2024
7d97eb1
Implemeneted a stream waring event
david-macpherson Feb 14, 2024
f05ce78
Implemented the emitting of the stream warning event when if the loca…
david-macpherson Feb 14, 2024
5b9f8b0
Fixed up styling
david-macpherson Feb 14, 2024
1b8e9c3
Refactored the candidate pair to an array, Implemneted a helper funct…
david-macpherson Feb 15, 2024
86fbdb2
Refactored to use Array.find
david-macpherson Feb 15, 2024
0e89638
Replaced the stream warning event with WebRtcTCPRelayDetectedEvent
david-macpherson Feb 15, 2024
bdf41af
Implemeneted the WebRtcTCPRelayDetectedEvent to dispatch if the web r…
david-macpherson Feb 15, 2024
55d02d6
Refactored the stats panel to use the getActiveCandidatePair
david-macpherson Feb 15, 2024
fb6bf16
Refactored the webrtc tcp detect check to act on an event instead of …
david-macpherson Feb 15, 2024
533d88a
Refactored the getting of the active candidate pair to be stored as a…
david-macpherson Feb 15, 2024
9d26239
Updated the candidate pair to the candidate pairs array
david-macpherson Feb 15, 2024
7eab39d
Added a warning if the stream is relayed over tcp
david-macpherson Feb 15, 2024
51babfd
Moved the logic for emitting the webrtc tcp relay detect event to it'…
david-macpherson Feb 15, 2024
d00bf75
Refactored the binding of the stats recieved event to the setup web r…
david-macpherson Feb 16, 2024
8567df6
Removed un needed new lines
david-macpherson Feb 16, 2024
15ed4ad
Added a check if the get active candidate is null to return a new can…
david-macpherson Feb 16, 2024
ad24cc9
Reverted the refactor the binding of the stats recieved event function
david-macpherson Feb 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions Frontend/library/src/PeerConnectionController/AggregatedStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class AggregatedStats {
inboundAudioStats: InboundAudioStats;
lastVideoStats: InboundVideoStats;
lastAudioStats: InboundAudioStats;
candidatePair: CandidatePairStats;
candidatePairs: Array<CandidatePairStats>;
DataChannelStats: DataChannelStats;
localCandidates: Array<CandidateStat>;
remoteCandidates: Array<CandidateStat>;
Expand All @@ -37,7 +37,6 @@ export class AggregatedStats {
constructor() {
this.inboundVideoStats = new InboundVideoStats();
this.inboundAudioStats = new InboundAudioStats();
this.candidatePair = new CandidatePairStats();
this.DataChannelStats = new DataChannelStats();
this.outBoundVideoStats = new OutBoundVideoStats();
this.sessionStats = new SessionStats();
Expand All @@ -52,6 +51,7 @@ export class AggregatedStats {
processStats(rtcStatsReport: RTCStatsReport) {
this.localCandidates = new Array<CandidateStat>();
this.remoteCandidates = new Array<CandidateStat>();
this.candidatePairs = new Array<CandidatePairStats>();

rtcStatsReport.forEach((stat) => {
const type: RTCStatsTypePS = stat.type;
Expand Down Expand Up @@ -120,16 +120,10 @@ export class AggregatedStats {
* @param stat - the stats coming in from ice candidates
*/
handleCandidatePair(stat: CandidatePairStats) {
this.candidatePair.bytesReceived = stat.bytesReceived;
this.candidatePair.bytesSent = stat.bytesSent;
this.candidatePair.localCandidateId = stat.localCandidateId;
this.candidatePair.remoteCandidateId = stat.remoteCandidateId;
this.candidatePair.nominated = stat.nominated;
this.candidatePair.readable = stat.readable;
this.candidatePair.selected = stat.selected;
this.candidatePair.writable = stat.writable;
this.candidatePair.state = stat.state;
this.candidatePair.currentRoundTripTime = stat.currentRoundTripTime;

// Add the candidate pair to the candidate pair array
this.candidatePairs.push(stat)

}

/**
Expand Down Expand Up @@ -162,6 +156,8 @@ export class AggregatedStats {
localCandidate.protocol = stat.protocol;
localCandidate.candidateType = stat.candidateType;
localCandidate.id = stat.id;
localCandidate.relayProtocol = stat.relayProtocol;
localCandidate.transportId = stat.transportId;
this.localCandidates.push(localCandidate);
}

Expand All @@ -171,12 +167,14 @@ export class AggregatedStats {
*/
handleRemoteCandidate(stat: CandidateStat) {
const RemoteCandidate = new CandidateStat();
RemoteCandidate.label = 'local-candidate';
RemoteCandidate.label = 'remote-candidate';
RemoteCandidate.address = stat.address;
RemoteCandidate.port = stat.port;
RemoteCandidate.protocol = stat.protocol;
RemoteCandidate.id = stat.id;
RemoteCandidate.candidateType = stat.candidateType;
RemoteCandidate.relayProtocol = stat.relayProtocol;
RemoteCandidate.transportId = stat.transportId
this.remoteCandidates.push(RemoteCandidate);
}

Expand Down Expand Up @@ -308,4 +306,12 @@ export class AggregatedStats {
isNumber(value: unknown): boolean {
return typeof value === 'number' && isFinite(value);
}

/**
* Helper function to return the active candidate pair
* @returns The candidate pair that is currently receiving data
*/
public getActiveCandidatePair(): CandidatePairStats | null {
return this.candidatePairs.find((candidatePair) => candidatePair.bytesReceived > 0, null)
}
lukehb marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
export class CandidatePairStats {
bytesReceived: number;
bytesSent: number;
currentRoundTripTime: number;
id: string;
lastPacketReceivedTimestamp: number;
lastPacketSentTimestamp: number;
localCandidateId: string;
remoteCandidateId: string;
nominated: boolean;
priority: number;
readable: boolean;
writable: boolean;
remoteCandidateId: string;
selected: boolean;
state: string;
currentRoundTripTime: number;
timestamp: number;
transportId: string;
type: string;
writable: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
* ICE Candidate Stat collected from the RTC Stats Report
*/
export class CandidateStat {
label: string;
id: string;
address: string;
candidateType: string;
id: string;
label: string;
port: number;
protocol: 'tcp' | 'udp';
relayProtocol: 'tcp' | 'udp' | 'tls';
transportId: string;
}
6 changes: 3 additions & 3 deletions Frontend/library/src/PixelStreaming/PixelStreaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,9 +398,9 @@ describe('PixelStreaming', () => {
expect.objectContaining({
data: {
aggregatedStats: expect.objectContaining({
candidatePair: expect.objectContaining({
bytesReceived: 123
}),
candidatePairs: [
expect.objectContaining({ bytesReceived: 123 })
],
localCandidates: [
expect.objectContaining({ address: 'mock-address' })
]
Expand Down
33 changes: 32 additions & 1 deletion Frontend/library/src/PixelStreaming/PixelStreaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
WebRtcSdpEvent,
DataChannelLatencyTestResponseEvent,
DataChannelLatencyTestResultEvent,
PlayerCountEvent
PlayerCountEvent,
WebRtcTCPRelayDetectedEvent
} from '../Util/EventEmitter';
import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive';
import { WebXRController } from '../WebXR/WebXRController';
Expand Down Expand Up @@ -62,6 +63,7 @@ export class PixelStreaming {
protected _webRtcController: WebRtcPlayerController;
protected _webXrController: WebXRController;
protected _dataChannelLatencyTestController: DataChannelLatencyTestController;

/**
* Configuration object. You can read or modify config through this object. Whenever
* the configuration is changed, the library will emit a `settingsChanged` event.
Expand Down Expand Up @@ -116,6 +118,13 @@ export class PixelStreaming {
this.onScreenKeyboardHelper.showOnScreenKeyboard(command);

this._webXrController = new WebXRController(this._webRtcController);

// Add event listener for the webRtcConnected event
this._eventEmitter.addEventListener("webRtcConnected", (webRtcConnectedEvent: WebRtcConnectedEvent) => {

// Bind to the stats received event
this._eventEmitter.addEventListener("statsReceived", this._setupWebRtcTCPRelayDetection.bind(this));
});
}

/**
Expand Down Expand Up @@ -627,6 +636,28 @@ export class PixelStreaming {
);
}

// Sets up to emit the webrtc tcp relay detect event
_setupWebRtcTCPRelayDetection(statsReceivedEvent: StatsReceivedEvent) {
// Get the active candidate pair
let activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();

// Check if the active candidate pair is not null
if (activeCandidatePair != null) {

// Get the local candidate assigned to the active candidate pair
let localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)

// Check if the local candidate is not null, candidate type is relay and the relay protocol is tcp
if (localCandidate != null && localCandidate.candidateType == 'relay' && localCandidate.relayProtocol == 'tcp') {

// Send the web rtc tcp relay detected event
this._eventEmitter.dispatchEvent(new WebRtcTCPRelayDetectedEvent());
}
// The check is completed and the stats listen event can be removed
this._eventEmitter.removeEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
}
}

/**
* Request a connection latency test.
* NOTE: There are plans to refactor all request* functions. Expect changes if you use this!
Expand Down
13 changes: 12 additions & 1 deletion Frontend/library/src/Util/EventEmitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,16 @@ export class PlayerCountEvent extends Event {
}
}

/**
* An event that is emitted when the webRTC connections is relayed over TCP.
*/
export class WebRtcTCPRelayDetectedEvent extends Event {
readonly type: 'webRtcTCPRelayDetected';
constructor() {
super('webRtcTCPRelayDetected');
}
}

export type PixelStreamingEvent =
| AfkWarningActivateEvent
| AfkWarningUpdateEvent
Expand Down Expand Up @@ -573,7 +583,8 @@ export type PixelStreamingEvent =
| XrSessionStartedEvent
| XrSessionEndedEvent
| XrFrameEvent
| PlayerCountEvent;
| PlayerCountEvent
| WebRtcTCPRelayDetectedEvent;

export class EventEmitter extends EventTarget {
/**
Expand Down
8 changes: 8 additions & 0 deletions Frontend/ui-library/src/Application/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,14 @@ export class Application {
({ data: { count }}) =>
this.onPlayerCount(count)
);
this.stream.addEventListener(
'webRtcTCPRelayDetected',
({}) =>
Logger.Warning(
Logger.GetStackTrace(),
`Stream quailty degraded due to network enviroment, stream is relayed over TCP.`
)
);
}

/**
Expand Down
11 changes: 7 additions & 4 deletions Frontend/ui-library/src/UI/StatsPanel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Epic Games, Inc. All Rights Reserved.

import { LatencyTest } from './LatencyTest';
import { InitialSettings, Logger, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { CandidatePairStats, InitialSettings, Logger, PixelStreaming } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.4';
import { MathUtils } from '../Util/MathUtils';
import {DataChannelLatencyTest} from "./DataChannelLatencyTest";
Expand Down Expand Up @@ -318,14 +318,17 @@ export class StatsPanel {
);
}

// Store the active candidate pair return a new Candidate pair stat if getActiveCandidate is null
let activeCandidatePair = stats.getActiveCandidatePair() != null ? stats.getActiveCandidatePair() : new CandidatePairStats();

// RTT
const netRTT =
Object.prototype.hasOwnProperty.call(
lukehb marked this conversation as resolved.
Show resolved Hide resolved
stats.candidatePair,
activeCandidatePair,
'currentRoundTripTime'
) && stats.isNumber(stats.candidatePair.currentRoundTripTime)
) && stats.isNumber(activeCandidatePair.currentRoundTripTime)
? numberFormat.format(
stats.candidatePair.currentRoundTripTime * 1000
activeCandidatePair.currentRoundTripTime * 1000
)
: "Can't calculate";
this.addOrUpdateStat('RTTStat', 'Net RTT (ms)', netRTT);
Expand Down
Loading