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 20993dce..c2c39944 100644 --- a/Frontend/library/src/PixelStreaming/PixelStreaming.ts +++ b/Frontend/library/src/PixelStreaming/PixelStreaming.ts @@ -26,13 +26,24 @@ import { WebRtcDisconnectedEvent, WebRtcFailedEvent, WebRtcSdpEvent, + DataChannelLatencyTestResponseEvent, + DataChannelLatencyTestResultEvent, PlayerCountEvent } from '../Util/EventEmitter'; import { MessageOnScreenKeyboard } from '../WebSockets/MessageReceive'; import { WebXRController } from '../WebXR/WebXRController'; import { MessageDirection } from '../UeInstanceMessage/StreamMessageController'; +import { + DataChannelLatencyTestConfig, + DataChannelLatencyTestController +} from "../DataChannel/DataChannelLatencyTestController"; +import { + DataChannelLatencyTestResponse, + DataChannelLatencyTestResult +} from "../DataChannel/DataChannelLatencyTestResults"; import { RTCUtils } from '../Util/RTCUtils'; + export interface PixelStreamingOverrides { /** The DOM elment where Pixel Streaming video and user input event handlers are attached to. * You can give an existing DOM element here. If not given, the library will create a new div element @@ -50,6 +61,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. @@ -513,6 +525,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 @@ -634,6 +652,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/StreamMessageController.ts b/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts index d00f427f..c44b1ac2 100644 --- a/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts +++ b/Frontend/library/src/UeInstanceMessage/StreamMessageController.ts @@ -71,6 +71,10 @@ export class StreamMessageController { id: 8, structure: [] }); + this.toStreamerMessages.set('DataChannelLatencyTest', { + id: 9, + structure: [] + }); /* * Input Messages. Range = 50..89. */ @@ -189,6 +193,7 @@ export class StreamMessageController { this.fromStreamerMessages.set(11, 'TestEcho'); this.fromStreamerMessages.set(12, 'InputControlOwnership'); this.fromStreamerMessages.set(13, 'GamepadResponse'); + this.fromStreamerMessages.set(14, 'DataChannelLatencyTest'); this.fromStreamerMessages.set(255, 'Protocol'); } 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 4d9ef58e..167e624a 100644 --- a/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts +++ b/Frontend/library/src/WebRtcPlayer/WebRtcPlayerController.ts @@ -61,6 +61,10 @@ import { PlayStreamRejectedEvent, StreamerListMessageEvent } from '../Util/EventEmitter'; +import { + DataChannelLatencyTestRequest, + DataChannelLatencyTestResponse +} from "../DataChannel/DataChannelLatencyTestResults"; /** * Entry point for the WebRTC Player */ @@ -383,6 +387,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', @@ -1650,6 +1659,15 @@ export class WebRtcPlayerController { })]); } + /** + * Send a Data Channel Latency Test Request to the UE Instance + */ + sendDataChannelLatencyTest(descriptor: DataChannelLatencyTestRequest) { + this.streamMessageController.toStreamerHandlers.get( + 'DataChannelLatencyTest' + )([JSON.stringify(descriptor)]); + } + /** * Send the MinQP encoder setting to the UE Instance. * @param minQP - The lower bound for QP when encoding @@ -1877,6 +1895,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 99ff6135..1dc26d66 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..c4d124ec --- /dev/null +++ b/Frontend/ui-library/src/UI/DataChannelLatencyTest.ts @@ -0,0 +1,129 @@ +// 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 + ); + /** + * 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): ' + + 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. */