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

Commit

Permalink
Merge pull request #491 from EpicGames/master
Browse files Browse the repository at this point in the history
Master into 5.4
  • Loading branch information
mcottontensor authored Feb 19, 2024
2 parents 2612b7d + fa75e9d commit 0346b66
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 28 deletions.
9 changes: 9 additions & 0 deletions Frontend/implementations/angular/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Angular Implementations

Here are a selection of community contributed implementations of Angular frontends.

- [cheikhnadiouf](https://github.com/cheikhnadiouf)'s implementation - [LINK](https://github.com/cheikhnadiouf/PixelStreamingInfrastructure/tree/AngularImplementations/Frontend/implementations/angular)

If you wish to contribute your own example frontend, please open an issue/PR.

**Disclaimer: We do not warrant these for any fitness of purpose, nor do we maintain them.**
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)
}
}
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
35 changes: 34 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,15 @@ export class PixelStreaming {
this.onScreenKeyboardHelper.showOnScreenKeyboard(command);

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

this._setupWebRtcTCPRelayDetection = this._setupWebRtcTCPRelayDetection.bind(this)

// 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);
});
}

/**
Expand Down Expand Up @@ -627,6 +638,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(
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ The Pixel Streaming Infrastructure contains reference implementations for all th
- A matchmaker, found in [`Matchmaker/`](Matchmaker/).
- Several frontend projects for the WebRTC player and input, found in [`Frontend/`](Frontend/):
- shared libraries for [communication](Frontend/library/) and [UI](Frontend/ui-library/) functionality
- separate [implementations](Frontend/implementations/) using different techologies such as TypeScript or React/JSX
- separate [implementations](Frontend/implementations/) using different technologies such as TypeScript or React/JSX

For detailed information, see the [/frontend](/Frontend/).

Expand Down Expand Up @@ -88,6 +88,7 @@ This repository contains the following in branches that track Unreal Engine vers
| Branch | Status |
|--------|--------|
|[Master](https://github.com/EpicGames/PixelStreamingInfrastructure/tree/master)| Dev |
|[UE5.5](https://github.com/EpicGames/PixelStreamingInfrastructure/tree/UE5.5)| Pre-release |
|[UE5.4](https://github.com/EpicGames/PixelStreamingInfrastructure/tree/UE5.4)| Pre-release |
|[UE5.3](https://github.com/EpicGames/PixelStreamingInfrastructure/tree/UE5.3)| Current |
|[UE5.2](https://github.com/EpicGames/PixelStreamingInfrastructure/tree/UE5.2)| Supported |
Expand Down

0 comments on commit 0346b66

Please sign in to comment.