From 930c396aeb7afa0e27b9482bd98af5edd0b4604c Mon Sep 17 00:00:00 2001 From: Nazar Rudenko Date: Wed, 19 Jul 2023 10:13:17 +1000 Subject: [PATCH 1/3] Add: DataChannelLatencyTest --- .../DataChannelLatencyTestController.ts | 129 ++++++++++++++++++ .../DataChannelLatencyTestResults.ts | 67 +++++++++ .../src/PixelStreaming/PixelStreaming.ts | 41 ++++++ .../SendDescriptorController.ts | 9 ++ .../StreamMessageController.ts | 6 + Frontend/library/src/Util/EventEmitter.ts | 37 +++++ .../WebRtcPlayer/WebRtcPlayerController.ts | 33 +++++ .../ui-library/src/Application/Application.ts | 36 +++-- .../src/UI/DataChannelLatencyTest.ts | 122 +++++++++++++++++ Frontend/ui-library/src/UI/StatsPanel.ts | 49 ++++++- 10 files changed, 508 insertions(+), 21 deletions(-) create mode 100644 Frontend/library/src/DataChannel/DataChannelLatencyTestController.ts create mode 100644 Frontend/library/src/DataChannel/DataChannelLatencyTestResults.ts create mode 100644 Frontend/ui-library/src/UI/DataChannelLatencyTest.ts diff --git a/Frontend/library/src/DataChannel/DataChannelLatencyTestController.ts b/Frontend/library/src/DataChannel/DataChannelLatencyTestController.ts new file mode 100644 index 00000000..20b642b3 --- /dev/null +++ b/Frontend/library/src/DataChannel/DataChannelLatencyTestController.ts @@ -0,0 +1,129 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Logger } from '../Logger/Logger'; +import { + DataChannelLatencyTestRecord, + DataChannelLatencyTestRequest, + DataChannelLatencyTestResponse, + DataChannelLatencyTestResult, + DataChannelLatencyTestSeq, + DataChannelLatencyTestTimestamp +} from "./DataChannelLatencyTestResults"; + +export type DataChannelLatencyTestConfig = { + // test duration in milliseconds + duration: number; + //requests per second + rps: number; + //request filler size + requestSize: number; + //response filler size + responseSize: number; +} + +export type DataChannelLatencyTestSink = (request: DataChannelLatencyTestRequest) => void; +export type DataChannelLatencyTestResultCallback = (result: DataChannelLatencyTestResult) => void; + +export class DataChannelLatencyTestController { + startTime: DataChannelLatencyTestTimestamp; + sink: DataChannelLatencyTestSink; + callback: DataChannelLatencyTestResultCallback; + records: Map; + seq: DataChannelLatencyTestSeq; + interval: NodeJS.Timer; + + constructor(sink: DataChannelLatencyTestSink, callback: DataChannelLatencyTestResultCallback) { + this.sink = sink; + this.callback = callback; + this.records = new Map(); + this.seq = 0; + } + + start(config: DataChannelLatencyTestConfig) { + if (this.isRunning()) { + return false; + } + this.startTime = Date.now(); + this.records.clear(); + this.interval = setInterval((() => { + if (Date.now() - this.startTime >= config.duration) { + this.stop(); + } else { + this.sendRequest(config.requestSize, config.responseSize); + } + }).bind(this), Math.floor(1000/config.rps)); + return true; + } + + stop() { + if (this.interval) { + clearInterval(this.interval); + this.interval = undefined; + this.callback(this.produceResult()); + } + } + + produceResult(): DataChannelLatencyTestResult { + const resultRecords = new Map(this.records); + return { + records: resultRecords, + dataChannelRtt: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => { + return acc + (next.playerReceivedTimestamp - next.playerSentTimestamp); + }, 0) / this.records.size), + playerToStreamerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => { + return acc + (next.streamerReceivedTimestamp - next.playerSentTimestamp); + }, 0) / this.records.size), + streamerToPlayerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => { + return acc + (next.playerReceivedTimestamp - next.streamerSentTimestamp); + }, 0) / this.records.size), + exportLatencyAsCSV: () => { + let csv = "Timestamp;RTT;PlayerToStreamer;StreamerToPlayer;\n"; + resultRecords.forEach((record) => { + csv += record.playerSentTimestamp + ";"; + csv += (record.playerReceivedTimestamp - record.playerSentTimestamp) + ";"; + csv += (record.streamerReceivedTimestamp - record.playerSentTimestamp) + ";"; + csv += (record.playerReceivedTimestamp - record.streamerSentTimestamp) + ";"; + csv += "\n"; + }) + return csv; + } + } + } + + isRunning() { + return !!this.interval; + } + + receive(response: DataChannelLatencyTestResponse) { + if (!this.isRunning()) { + return; + } + if (!response) { + Logger.Error( + Logger.GetStackTrace(), + "Undefined response from server" + ); + return; + } + let record = this.records.get(response.Seq); + if (record) { + record.update(response); + } + } + + sendRequest(requestSize: number, responseSize: number) { + let request = this.createRequest(requestSize, responseSize); + let record = new DataChannelLatencyTestRecord(request); + this.records.set(record.seq, record); + this.sink(request); + } + + createRequest(requestSize: number, responseSize: number): DataChannelLatencyTestRequest { + return { + Seq: this.seq++, + FillResponseSize: responseSize, + Filler: requestSize ? "A".repeat(requestSize) : "" + } + } + +} diff --git a/Frontend/library/src/DataChannel/DataChannelLatencyTestResults.ts b/Frontend/library/src/DataChannel/DataChannelLatencyTestResults.ts new file mode 100644 index 00000000..22b3b761 --- /dev/null +++ b/Frontend/library/src/DataChannel/DataChannelLatencyTestResults.ts @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +/** + * Data Channel Latency Test types + */ + + +/** + * Unix epoch + */ +export type DataChannelLatencyTestTimestamp = number; + +/** + * Sequence number represented by unsigned int + */ +export type DataChannelLatencyTestSeq = number; + +/** + * Request sent to Streamer + */ +export type DataChannelLatencyTestRequest = { + Seq: DataChannelLatencyTestSeq; + FillResponseSize: number; + Filler: string; +} + +/** + * Response from the Streamer + */ +export type DataChannelLatencyTestResponse = { + Seq: DataChannelLatencyTestSeq; + Filler: string; + ReceivedTimestamp: DataChannelLatencyTestTimestamp; + SentTimestamp: DataChannelLatencyTestTimestamp; +} + +export type DataChannelLatencyTestResult = { + records: Map + dataChannelRtt: number, + playerToStreamerTime: number, + streamerToPlayerTime: number, + exportLatencyAsCSV: () => string +} + +export class DataChannelLatencyTestRecord { + seq: DataChannelLatencyTestSeq; + playerSentTimestamp: DataChannelLatencyTestTimestamp; + playerReceivedTimestamp: DataChannelLatencyTestTimestamp; + streamerReceivedTimestamp: DataChannelLatencyTestTimestamp; + streamerSentTimestamp: DataChannelLatencyTestTimestamp; + requestFillerSize: number; + responseFillerSize: number; + + constructor(request: DataChannelLatencyTestRequest) { + this.seq = request.Seq; + this.playerSentTimestamp = Date.now(); + this.requestFillerSize = request.Filler ? request.Filler.length : 0; + } + + update(response: DataChannelLatencyTestResponse) { + this.playerReceivedTimestamp = Date.now(); + this.streamerReceivedTimestamp = response.ReceivedTimestamp; + this.streamerSentTimestamp = response.SentTimestamp; + this.responseFillerSize = response.Filler ? response.Filler.length : 0; + } + +} diff --git a/Frontend/library/src/PixelStreaming/PixelStreaming.ts b/Frontend/library/src/PixelStreaming/PixelStreaming.ts index cc1f8ea3..2d80e077 100644 --- a/Frontend/library/src/PixelStreaming/PixelStreaming.ts +++ b/Frontend/library/src/PixelStreaming/PixelStreaming.ts @@ -26,10 +26,20 @@ import { WebRtcDisconnectedEvent, WebRtcFailedEvent, WebRtcSdpEvent, + DataChannelLatencyTestResponseEvent, + DataChannelLatencyTestResultEvent, PlayerCountEvent } from '../Util/EventEmitter'; import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive'; import { WebXRController } from '../WebXR/WebXRController'; +import { + DataChannelLatencyTestConfig, + DataChannelLatencyTestController +} from "../DataChannel/DataChannelLatencyTestController"; +import { + DataChannelLatencyTestResponse, + DataChannelLatencyTestResult +} from "../DataChannel/DataChannelLatencyTestResults"; export interface PixelStreamingOverrides { /** The DOM elment where Pixel Streaming video and user input event handlers are attached to. @@ -48,6 +58,7 @@ export interface PixelStreamingOverrides { 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. @@ -460,6 +471,12 @@ export class PixelStreaming { ); } + _onDataChannelLatencyTestResponse(response: DataChannelLatencyTestResponse) { + this._eventEmitter.dispatchEvent( + new DataChannelLatencyTestResponseEvent({ response }) + ); + } + /** * Set up functionality to happen when receiving video statistics * @param videoStats - video statistics as a aggregate stats object @@ -581,6 +598,30 @@ export class PixelStreaming { return true; } + /** + * Request a data channel latency test. + * NOTE: There are plans to refactor all request* functions. Expect changes if you use this! + */ + public requestDataChannelLatencyTest(config: DataChannelLatencyTestConfig) { + if (!this._webRtcController.videoPlayer.isVideoReady()) { + return false; + } + if (!this._dataChannelLatencyTestController) { + this._dataChannelLatencyTestController = new DataChannelLatencyTestController( + this._webRtcController.sendDataChannelLatencyTest.bind(this._webRtcController), + (result: DataChannelLatencyTestResult) => { + this._eventEmitter.dispatchEvent(new DataChannelLatencyTestResultEvent( { result })) + }); + this.addEventListener( + "dataChannelLatencyTestResponse", + ({data: {response} }) => { + this._dataChannelLatencyTestController.receive(response); + } + ) + } + return this._dataChannelLatencyTestController.start(config); + } + /** * Request for the UE application to show FPS counter. * NOTE: There are plans to refactor all request* functions. Expect changes if you use this! diff --git a/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts b/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts index 553007f5..817629c6 100644 --- a/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts +++ b/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts @@ -3,6 +3,7 @@ import { DataChannelSender } from '../DataChannel/DataChannelSender'; import { Logger } from '../Logger/Logger'; import { StreamMessageController } from './StreamMessageController'; +import {DataChannelLatencyTestRequest} from "../DataChannel/DataChannelLatencyTestResults"; export class SendDescriptorController { toStreamerMessagesMapProvider: StreamMessageController; @@ -24,6 +25,14 @@ export class SendDescriptorController { this.sendDescriptor('LatencyTest', descriptor); } + /** + * Send a Data Channel Latency Test to the UE Instance + * @param descriptor - the descriptor for a latency test + */ + sendDataChannelLatencyTest(descriptor: DataChannelLatencyTestRequest) { + this.sendDescriptor('DataChannelLatencyTest', descriptor); + } + /** * Send a Latency Test to the UE Instance * @param descriptor - the descriptor for a command diff --git a/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts b/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts index 024fefc3..31bcd290 100644 --- a/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts +++ b/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts @@ -82,6 +82,11 @@ export class StreamMessageController { byteLength: 0, structure: [] }); + this.toStreamerMessages.add('DataChannelLatencyTest', { + id: 9, + byteLength: 0, + structure: [] + }); /* * Input Messages. Range = 50..89. */ @@ -220,6 +225,7 @@ export class StreamMessageController { this.fromStreamerMessages.add('TestEcho', 11); this.fromStreamerMessages.add('InputControlOwnership', 12); this.fromStreamerMessages.add('GamepadResponse', 13); + this.fromStreamerMessages.add('DataChannelLatencyTest', 14); this.fromStreamerMessages.add('Protocol', 255); } diff --git a/Frontend/library/src/Util/EventEmitter.ts b/Frontend/library/src/Util/EventEmitter.ts index b6ee9bdb..32ee39a1 100644 --- a/Frontend/library/src/Util/EventEmitter.ts +++ b/Frontend/library/src/Util/EventEmitter.ts @@ -12,6 +12,10 @@ import { SettingFlag } from '../Config/SettingFlag'; import { SettingNumber } from '../Config/SettingNumber'; import { SettingText } from '../Config/SettingText'; import { SettingOption } from '../Config/SettingOption'; +import { + DataChannelLatencyTestResponse, + DataChannelLatencyTestResult +} from "../DataChannel/DataChannelLatencyTestResults"; /** * An event that is emitted when AFK disconnect is about to happen. @@ -366,6 +370,37 @@ export class LatencyTestResultEvent extends Event { } } +/** + * An event that is emitted when receiving data channel latency test response from server. + * This event is handled by DataChannelLatencyTestController + */ +export class DataChannelLatencyTestResponseEvent extends Event { + readonly type: 'dataChannelLatencyTestResponse'; + readonly data: { + /** Latency test result object */ + response: DataChannelLatencyTestResponse + }; + constructor(data: DataChannelLatencyTestResponseEvent['data']) { + super('dataChannelLatencyTestResponse'); + this.data = data; + } +} + +/** + * An event that is emitted when data channel latency test results are ready. + */ +export class DataChannelLatencyTestResultEvent extends Event { + readonly type: 'dataChannelLatencyTestResult'; + readonly data: { + /** Latency test result object */ + result: DataChannelLatencyTestResult + }; + constructor(data: DataChannelLatencyTestResultEvent['data']) { + super('dataChannelLatencyTestResult'); + this.data = data; + } +} + /** * An event that is emitted when receiving initial settings from UE. */ @@ -513,6 +548,8 @@ export type PixelStreamingEvent = | StatsReceivedEvent | StreamerListMessageEvent | LatencyTestResultEvent + | DataChannelLatencyTestResponseEvent + | DataChannelLatencyTestResultEvent | InitialSettingsEvent | SettingsChangedEvent | XrSessionStartedEvent diff --git a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts index c5f4802a..cb3dbf2f 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -62,6 +62,10 @@ import { PlayStreamRejectedEvent, StreamerListMessageEvent } from '../Util/EventEmitter'; +import { + DataChannelLatencyTestRequest, + DataChannelLatencyTestResponse +} from "../DataChannel/DataChannelLatencyTestResults"; /** * Entry point for the WebRTC Player */ @@ -385,6 +389,11 @@ export class WebRtcPlayerController { 'LatencyTest', (data: ArrayBuffer) => this.handleLatencyTestResult(data) ); + this.streamMessageController.registerMessageHandler( + MessageDirection.FromStreamer, + 'DataChannelLatencyTest', + (data: ArrayBuffer) => this.handleDataChannelLatencyTestResponse(data) + ) this.streamMessageController.registerMessageHandler( MessageDirection.FromStreamer, 'InitialSettings', @@ -1634,6 +1643,13 @@ export class WebRtcPlayerController { }); } + /** + * Send a Data Channel Latency Test Request to the UE Instance + */ + sendDataChannelLatencyTest(descriptor: DataChannelLatencyTestRequest) { + this.sendDescriptorController.sendDataChannelLatencyTest(descriptor); + } + /** * Send the MinQP encoder setting to the UE Instance. * @param minQP - The lower bound for QP when encoding @@ -1835,6 +1851,23 @@ export class WebRtcPlayerController { this.pixelStreaming._onLatencyTestResult(latencyTestResults); } + /** + * Handles when a Data Channel Latency Test Response is received from the UE Instance + * @param message - Data Channel Latency Test Response + */ + handleDataChannelLatencyTestResponse(message: ArrayBuffer) { + Logger.Log( + Logger.GetStackTrace(), + 'DataChannelReceiveMessageType.dataChannelLatencyResponse', + 6 + ); + const responseAsString = new TextDecoder('utf-16').decode( + message.slice(1) + ); + const latencyTestResponse: DataChannelLatencyTestResponse = JSON.parse(responseAsString); + this.pixelStreaming._onDataChannelLatencyTestResponse(latencyTestResponse); + } + /** * Handles when the Encoder and Web RTC Settings are received from the UE Instance * @param message - Initial Encoder and Web RTC Settings diff --git a/Frontend/ui-library/src/Application/Application.ts b/Frontend/ui-library/src/Application/Application.ts index 2864016e..18347895 100644 --- a/Frontend/ui-library/src/Application/Application.ts +++ b/Frontend/ui-library/src/Application/Application.ts @@ -31,6 +31,9 @@ import { UIElementConfig } from '../UI/UIConfigurationTypes' import { FullScreenIconBase, FullScreenIconExternal } from '../UI/FullscreenIcon'; +import { + DataChannelLatencyTestResult +} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/types/DataChannel/DataChannelLatencyTestResults"; /** @@ -356,6 +359,11 @@ export class Application { ({ data: { latencyTimings } }) => this.onLatencyTestResults(latencyTimings) ); + this.stream.addEventListener( + 'dataChannelLatencyTestResult', + ({data: { result } }) => + this.onDataChannelLatencyTestResults(result) + ) this.stream.addEventListener( 'streamerListMessage', ({ data: { messageStreamerList, autoSelectedStreamerId } }) => @@ -574,10 +582,8 @@ export class Application { `Disconnected: ${eventString}
Click To Restart
` ); } - // disable starting a latency check - this.statsPanel.latencyTest.latencyTestButton.onclick = () => { - // do nothing - }; + // disable starting a latency checks + this.statsPanel.onDisconnect(); } /** @@ -624,11 +630,7 @@ export class Application { if (!this.stream.config.isFlagEnabled(Flags.AutoPlayVideo)) { this.showPlayOverlay(); } - - // starting a latency check - this.statsPanel.latencyTest.latencyTestButton.onclick = () => { - this.stream.requestLatencyTest(); - }; + this.statsPanel.onVideoInitialized(this.stream); } /** @@ -644,17 +646,7 @@ export class Application { onInitialSettings(settings: InitialSettings) { if (settings.PixelStreamingSettings) { - const disableLatencyTest = - settings.PixelStreamingSettings.DisableLatencyTest; - if (disableLatencyTest) { - this.statsPanel.latencyTest.latencyTestButton.disabled = true; - this.statsPanel.latencyTest.latencyTestButton.title = - 'Disabled by -PixelStreamingDisableLatencyTester=true'; - Logger.Info( - Logger.GetStackTrace(), - '-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.' - ); - } + this.statsPanel.configure(settings.PixelStreamingSettings); } } @@ -667,6 +659,10 @@ export class Application { this.statsPanel.latencyTest.handleTestResult(latencyTimings); } + onDataChannelLatencyTestResults(result: DataChannelLatencyTestResult) { + this.statsPanel.dataChannelLatencyTest.handleTestResult(result); + } + onPlayerCount(playerCount: number) { this.statsPanel.handlePlayerCount(playerCount); } diff --git a/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts b/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts new file mode 100644 index 00000000..fad6a24e --- /dev/null +++ b/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts @@ -0,0 +1,122 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3'; +import { + DataChannelLatencyTestResult +} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/types/DataChannel/DataChannelLatencyTestResults"; + +/** + * DataChannel Latency test UI elements and results handling. + */ +export class DataChannelLatencyTest { + _rootElement: HTMLElement; + _latencyTestButton: HTMLInputElement; + _latencyTestResultsElement: HTMLElement; + + /** + * Get the button containing the stats icon. + */ + public get rootElement(): HTMLElement { + if (!this._rootElement) { + this._rootElement = document.createElement('section'); + this._rootElement.classList.add('settingsContainer'); + + // make heading + const heading = document.createElement('div'); + heading.id = 'dataChannelLatencyTestHeader'; + heading.classList.add('settings-text'); + heading.classList.add('settingsHeader'); + this._rootElement.appendChild(heading); + + const headingText = document.createElement('div'); + headingText.innerHTML = 'Data Channel Latency Test'; + heading.appendChild(headingText); + heading.appendChild(this.latencyTestButton); + + // make test results element + const resultsParentElem = document.createElement('div'); + resultsParentElem.id = 'dataChannelLatencyTestContainer'; + resultsParentElem.classList.add('d-none'); + this._rootElement.appendChild(resultsParentElem); + + resultsParentElem.appendChild(this.latencyTestResultsElement); + } + return this._rootElement; + } + + public get latencyTestResultsElement(): HTMLElement { + if (!this._latencyTestResultsElement) { + this._latencyTestResultsElement = document.createElement('div'); + this._latencyTestResultsElement.id = 'dataChannelLatencyStatsResults'; + this._latencyTestResultsElement.classList.add('StatsResult'); + } + return this._latencyTestResultsElement; + } + + public get latencyTestButton(): HTMLInputElement { + if (!this._latencyTestButton) { + this._latencyTestButton = document.createElement('input'); + this._latencyTestButton.type = 'button'; + this._latencyTestButton.value = 'Run Test'; + this._latencyTestButton.id = 'btn-start-data-channel-latency-test'; + this._latencyTestButton.classList.add('streamTools-button'); + this._latencyTestButton.classList.add('btn-flat'); + } + return this._latencyTestButton; + } + + /** + * Populate the UI based on the latency test's results. + * @param result The latency test results. + */ + public handleTestResult(result: DataChannelLatencyTestResult) { + Logger.Log( + Logger.GetStackTrace(), + result.toString(), + 6 + ); + let latencyStatsInnerHTML = ''; + latencyStatsInnerHTML += + '
Data channel RTT (ms): ' + + result.dataChannelRtt + + '
'; + /** + * Separate path time discovery works only when UE and Player clocks have been synchronized. + */ + if (result.playerToStreamerTime >= 0 && result.streamerToPlayerTime >= 0) { + latencyStatsInnerHTML += + '
Player to Streamer path (ms): ' + result.playerToStreamerTime + '
'; + latencyStatsInnerHTML += + '
Streamer to Player path (ms): ' + + result.streamerToPlayerTime + + '
'; + } + this.latencyTestResultsElement.innerHTML = latencyStatsInnerHTML; + //setup button to download the detailed results + let downloadButton: HTMLInputElement = document.createElement('input'); + downloadButton.type = 'button'; + downloadButton.value = 'Download'; + downloadButton.classList.add('streamTools-button'); + downloadButton.classList.add('btn-flat'); + downloadButton.onclick = () => { + let file = new Blob([result.exportLatencyAsCSV()], {type: 'text/plain'}); + let a = document.createElement("a"), + url = URL.createObjectURL(file); + a.href = url; + a.download = "data_channel_latency_test_results.csv"; + document.body.appendChild(a); + a.click(); + setTimeout(function() { + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + }, 0); + } + this.latencyTestResultsElement.appendChild(downloadButton); + } + + public handleTestStart() { + this.latencyTestResultsElement.innerHTML = + '
Test in progress
'; + } + +} diff --git a/Frontend/ui-library/src/UI/StatsPanel.ts b/Frontend/ui-library/src/UI/StatsPanel.ts index a3c81919..5e88be88 100644 --- a/Frontend/ui-library/src/UI/StatsPanel.ts +++ b/Frontend/ui-library/src/UI/StatsPanel.ts @@ -1,9 +1,11 @@ // Copyright Epic Games, Inc. All Rights Reserved. import { LatencyTest } from './LatencyTest'; -import { Logger } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3'; +import {InitialSettings, Logger, PixelStreaming} from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3'; import { AggregatedStats } from '@epicgames-ps/lib-pixelstreamingfrontend-ue5.3'; import { MathUtils } from '../Util/MathUtils'; +import {DataChannelLatencyTest} from "./DataChannelLatencyTest"; +import {PixelStreamingSettings} from "@epicgames-ps/lib-pixelstreamingfrontend-ue5.3/types/DataChannel/InitialSettings"; /** * A stat structure, an id, the stat string, and the element where it is rendered. @@ -26,12 +28,14 @@ export class StatsPanel { _statsResult: HTMLElement; latencyTest: LatencyTest; + dataChannelLatencyTest: DataChannelLatencyTest; /* A map stats we are storing/rendering */ statsMap = new Map(); constructor() { this.latencyTest = new LatencyTest(); + this.dataChannelLatencyTest = new DataChannelLatencyTest(); } /** @@ -91,6 +95,7 @@ export class StatsPanel { statistics.appendChild(this.statisticsContainer); controlStats.appendChild(this.latencyTest.rootElement); + controlStats.appendChild(this.dataChannelLatencyTest.rootElement); } return this._statsContentElement; } @@ -122,6 +127,48 @@ export class StatsPanel { return this._statsCloseButton; } + public onDisconnect(): void { + this.latencyTest.latencyTestButton.onclick = () => { + // do nothing + } + this.dataChannelLatencyTest.latencyTestButton.onclick = () => { + //do nothing + } + } + + public onVideoInitialized(stream: PixelStreaming): void { + // starting a latency check + this.latencyTest.latencyTestButton.onclick = () => { + stream.requestLatencyTest(); + }; + this.dataChannelLatencyTest.latencyTestButton.onclick = () => { + let started = stream.requestDataChannelLatencyTest({ + duration: 1000, + rps: 10, + requestSize: 200, + responseSize: 200 + }); + if (started) { + this.dataChannelLatencyTest.handleTestStart(); + } + }; + } + + public configure(settings: PixelStreamingSettings): void { + if (settings.DisableLatencyTest) { + this.latencyTest.latencyTestButton.disabled = true; + this.latencyTest.latencyTestButton.title = + 'Disabled by -PixelStreamingDisableLatencyTester=true'; + this.dataChannelLatencyTest.latencyTestButton.disabled = true; + this.dataChannelLatencyTest.latencyTestButton.title = + 'Disabled by -PixelStreamingDisableLatencyTester=true'; + Logger.Info( + Logger.GetStackTrace(), + '-PixelStreamingDisableLatencyTester=true, requesting latency report from the the browser to UE is disabled.' + ); + } + } + /** * Show stats panel. */ From 2a952a11b3db06799ba0d76c5cc1378cce145357 Mon Sep 17 00:00:00 2001 From: Nazar Rudenko Date: Wed, 19 Jul 2023 10:39:06 +1000 Subject: [PATCH 2/3] Friendly output instead of NaN if UE doesn't support the test --- Frontend/ui-library/src/UI/DataChannelLatencyTest.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts b/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts index fad6a24e..c4d124ec 100644 --- a/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts +++ b/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts @@ -75,6 +75,13 @@ export class DataChannelLatencyTest { result.toString(), 6 ); + /** + * Check we have results, NaN would mean that UE version we talk to doesn't support our test + */ + if (isNaN(result.dataChannelRtt)) { + this.latencyTestResultsElement.innerHTML = '
Not supported
'; + return; + } let latencyStatsInnerHTML = ''; latencyStatsInnerHTML += '
Data channel RTT (ms): ' + From ae7861a69f74de62a2b5c00a7abde640db80ee95 Mon Sep 17 00:00:00 2001 From: Matthew Cotton Date: Thu, 31 Aug 2023 16:05:43 +1000 Subject: [PATCH 3/3] Removing empty file left over from merge conflict. --- .../library/src/UeInstanceMessage/SendDescriptorController.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts diff --git a/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts b/Frontend/library/src/UeInstanceMessage/SendDescriptorController.ts deleted file mode 100644 index e69de29b..00000000