@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/.cspell.json +48 -0
  2. package/.eslintignore +8 -0
  3. package/.eslintrc.js +8 -0
  4. package/.prettierignore +0 -0
  5. package/.prettierrc.json +6 -0
  6. package/dist/lib-pixelstreamingfrontend.esm.js +1 -0
  7. package/dist/lib-pixelstreamingfrontend.js +1 -0
  8. package/jest.config.js +18 -0
  9. package/package.json +48 -0
  10. package/readme.md +15 -0
  11. package/src/AFK/AFKController.test.ts +162 -0
  12. package/src/AFK/AFKController.ts +158 -0
  13. package/src/Config/Config.test.ts +222 -0
  14. package/src/Config/Config.ts +970 -0
  15. package/src/Config/SettingBase.ts +65 -0
  16. package/src/Config/SettingFlag.ts +99 -0
  17. package/src/Config/SettingNumber.ts +111 -0
  18. package/src/Config/SettingOption.ts +124 -0
  19. package/src/Config/SettingText.ts +82 -0
  20. package/src/DataChannel/DataChannelController.ts +138 -0
  21. package/src/DataChannel/DataChannelLatencyTestController.ts +129 -0
  22. package/src/DataChannel/DataChannelLatencyTestResults.ts +67 -0
  23. package/src/DataChannel/DataChannelSender.ts +59 -0
  24. package/src/DataChannel/InitialSettings.ts +61 -0
  25. package/src/DataChannel/LatencyTestResults.ts +76 -0
  26. package/src/FreezeFrame/FreezeFrame.ts +114 -0
  27. package/src/FreezeFrame/FreezeFrameController.ts +114 -0
  28. package/src/Inputs/FakeTouchController.ts +199 -0
  29. package/src/Inputs/GamepadController.ts +314 -0
  30. package/src/Inputs/GamepadTypes.ts +10 -0
  31. package/src/Inputs/HoveringMouseEvents.ts +192 -0
  32. package/src/Inputs/IMouseEvents.ts +64 -0
  33. package/src/Inputs/ITouchController.ts +29 -0
  34. package/src/Inputs/InputClassesFactory.ts +140 -0
  35. package/src/Inputs/KeyboardController.ts +354 -0
  36. package/src/Inputs/LockedMouseEvents.ts +287 -0
  37. package/src/Inputs/MouseButtons.ts +25 -0
  38. package/src/Inputs/MouseController.ts +362 -0
  39. package/src/Inputs/SpecialKeyCodes.ts +16 -0
  40. package/src/Inputs/TouchController.ts +208 -0
  41. package/src/Inputs/XRGamepadController.ts +126 -0
  42. package/src/PeerConnectionController/AggregatedStats.ts +311 -0
  43. package/src/PeerConnectionController/CandidatePairStats.ts +17 -0
  44. package/src/PeerConnectionController/CandidateStat.ts +13 -0
  45. package/src/PeerConnectionController/CodecStats.ts +19 -0
  46. package/src/PeerConnectionController/DataChannelStats.ts +17 -0
  47. package/src/PeerConnectionController/InboundRTPStats.ts +154 -0
  48. package/src/PeerConnectionController/InboundTrackStats.ts +34 -0
  49. package/src/PeerConnectionController/OutBoundRTPStats.ts +26 -0
  50. package/src/PeerConnectionController/PeerConnectionController.ts +563 -0
  51. package/src/PeerConnectionController/SessionStats.ts +10 -0
  52. package/src/PeerConnectionController/StreamStats.ts +11 -0
  53. package/src/PixelStreaming/PixelStreaming.test.ts +626 -0
  54. package/src/PixelStreaming/PixelStreaming.ts +851 -0
  55. package/src/UI/OnScreenKeyboard.ts +97 -0
  56. package/src/UeInstanceMessage/ResponseController.ts +47 -0
  57. package/src/UeInstanceMessage/SendMessageController.ts +154 -0
  58. package/src/UeInstanceMessage/StreamMessageController.ts +233 -0
  59. package/src/UeInstanceMessage/ToStreamerMessagesController.ts +62 -0
  60. package/src/Util/CoordinateConverter.ts +289 -0
  61. package/src/Util/EventEmitter.ts +611 -0
  62. package/src/Util/EventListenerTracker.ts +29 -0
  63. package/src/Util/FileUtil.ts +140 -0
  64. package/src/Util/RTCUtils.ts +41 -0
  65. package/src/Util/WebGLUtils.ts +49 -0
  66. package/src/Util/WebXRUtils.ts +25 -0
  67. package/src/VideoPlayer/StreamController.ts +89 -0
  68. package/src/VideoPlayer/VideoPlayer.ts +246 -0
  69. package/src/WebRtcPlayer/WebRtcPlayerController.ts +2158 -0
  70. package/src/WebXR/WebXRController.ts +319 -0
  71. package/src/__test__/mockMediaStream.ts +124 -0
  72. package/src/__test__/mockRTCPeerConnection.ts +347 -0
  73. package/src/__test__/mockRTCRtpReceiver.ts +22 -0
  74. package/src/__test__/mockWebSocket.ts +136 -0
  75. package/src/pixelstreamingfrontend.ts +46 -0
  76. package/tsconfig.jest.json +8 -0
  77. package/tsconfig.json +24 -0
  78. package/types/AFK/AFKController.d.ts +39 -0
  79. package/types/Config/Config.d.ts +218 -0
  80. package/types/Config/SettingBase.d.ts +30 -0
  81. package/types/Config/SettingFlag.d.ts +33 -0
  82. package/types/Config/SettingNumber.d.ts +45 -0
  83. package/types/Config/SettingOption.d.ts +43 -0
  84. package/types/Config/SettingText.d.ts +29 -0
  85. package/types/DataChannel/DataChannelController.d.ts +59 -0
  86. package/types/DataChannel/DataChannelLatencyTestController.d.ts +26 -0
  87. package/types/DataChannel/DataChannelLatencyTestResults.d.ts +46 -0
  88. package/types/DataChannel/DataChannelSender.d.ts +21 -0
  89. package/types/DataChannel/InitialSettings.d.ts +44 -0
  90. package/types/DataChannel/LatencyTestResults.d.ts +31 -0
  91. package/types/FreezeFrame/FreezeFrame.d.ts +36 -0
  92. package/types/FreezeFrame/FreezeFrameController.d.ts +37 -0
  93. package/types/Inputs/FakeTouchController.d.ts +61 -0
  94. package/types/Inputs/GamepadController.d.ts +85 -0
  95. package/types/Inputs/GamepadTypes.d.ts +8 -0
  96. package/types/Inputs/HoveringMouseEvents.d.ts +56 -0
  97. package/types/Inputs/IMouseEvents.d.ts +53 -0
  98. package/types/Inputs/ITouchController.d.ts +24 -0
  99. package/types/Inputs/InputClassesFactory.d.ts +54 -0
  100. package/types/Inputs/KeyboardController.d.ts +62 -0
  101. package/types/Inputs/LockedMouseEvents.d.ts +80 -0
  102. package/types/Inputs/MouseButtons.d.ts +22 -0
  103. package/types/Inputs/MouseController.d.ts +75 -0
  104. package/types/Inputs/SpecialKeyCodes.d.ts +14 -0
  105. package/types/Inputs/TouchController.d.ts +53 -0
  106. package/types/Inputs/XRGamepadController.d.ts +15 -0
  107. package/types/PeerConnectionController/AggregatedStats.d.ts +77 -0
  108. package/types/PeerConnectionController/CandidatePairStats.d.ts +15 -0
  109. package/types/PeerConnectionController/CandidateStat.d.ts +11 -0
  110. package/types/PeerConnectionController/CodecStats.d.ts +14 -0
  111. package/types/PeerConnectionController/DataChannelStats.d.ts +15 -0
  112. package/types/PeerConnectionController/InboundRTPStats.d.ts +141 -0
  113. package/types/PeerConnectionController/InboundTrackStats.d.ts +32 -0
  114. package/types/PeerConnectionController/OutBoundRTPStats.d.ts +23 -0
  115. package/types/PeerConnectionController/PeerConnectionController.d.ts +132 -0
  116. package/types/PeerConnectionController/SessionStats.d.ts +8 -0
  117. package/types/PeerConnectionController/StreamStats.d.ts +9 -0
  118. package/types/PixelStreaming/PixelStreaming.d.ts +259 -0
  119. package/types/UI/OnScreenKeyboard.d.ts +31 -0
  120. package/types/UeInstanceMessage/ResponseController.d.ts +19 -0
  121. package/types/UeInstanceMessage/SendMessageController.d.ts +18 -0
  122. package/types/UeInstanceMessage/StreamMessageController.d.ts +29 -0
  123. package/types/UeInstanceMessage/ToStreamerMessagesController.d.ts +32 -0
  124. package/types/Util/CoordinateConverter.d.ts +100 -0
  125. package/types/Util/EventEmitter.d.ts +422 -0
  126. package/types/Util/EventListenerTracker.d.ts +14 -0
  127. package/types/Util/FileUtil.d.ts +32 -0
  128. package/types/Util/RTCUtils.d.ts +8 -0
  129. package/types/Util/WebGLUtils.d.ts +4 -0
  130. package/types/Util/WebXRUtils.d.ts +9 -0
  131. package/types/VideoPlayer/StreamController.d.ts +24 -0
  132. package/types/VideoPlayer/VideoPlayer.d.ts +78 -0
  133. package/types/WebRtcPlayer/WebRtcPlayerController.d.ts +377 -0
  134. package/types/WebXR/WebXRController.d.ts +26 -0
  135. package/types/pixelstreamingfrontend.d.ts +22 -0
  136. package/webpack.common.js +35 -0
  137. package/webpack.dev.js +35 -0
  138. package/webpack.prod.js +36 -0
@@ -0,0 +1,129 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
+ import {
5
+ DataChannelLatencyTestRecord,
6
+ DataChannelLatencyTestRequest,
7
+ DataChannelLatencyTestResponse,
8
+ DataChannelLatencyTestResult,
9
+ DataChannelLatencyTestSeq,
10
+ DataChannelLatencyTestTimestamp
11
+ } from "./DataChannelLatencyTestResults";
12
+
13
+ export type DataChannelLatencyTestConfig = {
14
+ // test duration in milliseconds
15
+ duration: number;
16
+ //requests per second
17
+ rps: number;
18
+ //request filler size
19
+ requestSize: number;
20
+ //response filler size
21
+ responseSize: number;
22
+ }
23
+
24
+ export type DataChannelLatencyTestSink = (request: DataChannelLatencyTestRequest) => void;
25
+ export type DataChannelLatencyTestResultCallback = (result: DataChannelLatencyTestResult) => void;
26
+
27
+ export class DataChannelLatencyTestController {
28
+ startTime: DataChannelLatencyTestTimestamp;
29
+ sink: DataChannelLatencyTestSink;
30
+ callback: DataChannelLatencyTestResultCallback;
31
+ records: Map<DataChannelLatencyTestSeq, DataChannelLatencyTestRecord>;
32
+ seq: DataChannelLatencyTestSeq;
33
+ interval: NodeJS.Timer;
34
+
35
+ constructor(sink: DataChannelLatencyTestSink, callback: DataChannelLatencyTestResultCallback) {
36
+ this.sink = sink;
37
+ this.callback = callback;
38
+ this.records = new Map();
39
+ this.seq = 0;
40
+ }
41
+
42
+ start(config: DataChannelLatencyTestConfig) {
43
+ if (this.isRunning()) {
44
+ return false;
45
+ }
46
+ this.startTime = Date.now();
47
+ this.records.clear();
48
+ this.interval = setInterval((() => {
49
+ if (Date.now() - this.startTime >= config.duration) {
50
+ this.stop();
51
+ } else {
52
+ this.sendRequest(config.requestSize, config.responseSize);
53
+ }
54
+ }).bind(this), Math.floor(1000/config.rps));
55
+ return true;
56
+ }
57
+
58
+ stop() {
59
+ if (this.interval) {
60
+ clearInterval(this.interval);
61
+ this.interval = undefined;
62
+ this.callback(this.produceResult());
63
+ }
64
+ }
65
+
66
+ produceResult(): DataChannelLatencyTestResult {
67
+ const resultRecords = new Map(this.records);
68
+ return {
69
+ records: resultRecords,
70
+ dataChannelRtt: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
71
+ return acc + (next.playerReceivedTimestamp - next.playerSentTimestamp);
72
+ }, 0) / this.records.size),
73
+ playerToStreamerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
74
+ return acc + (next.streamerReceivedTimestamp - next.playerSentTimestamp);
75
+ }, 0) / this.records.size),
76
+ streamerToPlayerTime: Math.ceil(Array.from(this.records.values()).reduce((acc, next) => {
77
+ return acc + (next.playerReceivedTimestamp - next.streamerSentTimestamp);
78
+ }, 0) / this.records.size),
79
+ exportLatencyAsCSV: () => {
80
+ let csv = "Timestamp;RTT;PlayerToStreamer;StreamerToPlayer;\n";
81
+ resultRecords.forEach((record) => {
82
+ csv += record.playerSentTimestamp + ";";
83
+ csv += (record.playerReceivedTimestamp - record.playerSentTimestamp) + ";";
84
+ csv += (record.streamerReceivedTimestamp - record.playerSentTimestamp) + ";";
85
+ csv += (record.playerReceivedTimestamp - record.streamerSentTimestamp) + ";";
86
+ csv += "\n";
87
+ })
88
+ return csv;
89
+ }
90
+ }
91
+ }
92
+
93
+ isRunning() {
94
+ return !!this.interval;
95
+ }
96
+
97
+ receive(response: DataChannelLatencyTestResponse) {
98
+ if (!this.isRunning()) {
99
+ return;
100
+ }
101
+ if (!response) {
102
+ Logger.Error(
103
+ Logger.GetStackTrace(),
104
+ "Undefined response from server"
105
+ );
106
+ return;
107
+ }
108
+ let record = this.records.get(response.Seq);
109
+ if (record) {
110
+ record.update(response);
111
+ }
112
+ }
113
+
114
+ sendRequest(requestSize: number, responseSize: number) {
115
+ let request = this.createRequest(requestSize, responseSize);
116
+ let record = new DataChannelLatencyTestRecord(request);
117
+ this.records.set(record.seq, record);
118
+ this.sink(request);
119
+ }
120
+
121
+ createRequest(requestSize: number, responseSize: number): DataChannelLatencyTestRequest {
122
+ return {
123
+ Seq: this.seq++,
124
+ FillResponseSize: responseSize,
125
+ Filler: requestSize ? "A".repeat(requestSize) : ""
126
+ }
127
+ }
128
+
129
+ }
@@ -0,0 +1,67 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * Data Channel Latency Test types
5
+ */
6
+
7
+
8
+ /**
9
+ * Unix epoch
10
+ */
11
+ export type DataChannelLatencyTestTimestamp = number;
12
+
13
+ /**
14
+ * Sequence number represented by unsigned int
15
+ */
16
+ export type DataChannelLatencyTestSeq = number;
17
+
18
+ /**
19
+ * Request sent to Streamer
20
+ */
21
+ export type DataChannelLatencyTestRequest = {
22
+ Seq: DataChannelLatencyTestSeq;
23
+ FillResponseSize: number;
24
+ Filler: string;
25
+ }
26
+
27
+ /**
28
+ * Response from the Streamer
29
+ */
30
+ export type DataChannelLatencyTestResponse = {
31
+ Seq: DataChannelLatencyTestSeq;
32
+ Filler: string;
33
+ ReceivedTimestamp: DataChannelLatencyTestTimestamp;
34
+ SentTimestamp: DataChannelLatencyTestTimestamp;
35
+ }
36
+
37
+ export type DataChannelLatencyTestResult = {
38
+ records: Map<DataChannelLatencyTestSeq, DataChannelLatencyTestRecord>
39
+ dataChannelRtt: number,
40
+ playerToStreamerTime: number,
41
+ streamerToPlayerTime: number,
42
+ exportLatencyAsCSV: () => string
43
+ }
44
+
45
+ export class DataChannelLatencyTestRecord {
46
+ seq: DataChannelLatencyTestSeq;
47
+ playerSentTimestamp: DataChannelLatencyTestTimestamp;
48
+ playerReceivedTimestamp: DataChannelLatencyTestTimestamp;
49
+ streamerReceivedTimestamp: DataChannelLatencyTestTimestamp;
50
+ streamerSentTimestamp: DataChannelLatencyTestTimestamp;
51
+ requestFillerSize: number;
52
+ responseFillerSize: number;
53
+
54
+ constructor(request: DataChannelLatencyTestRequest) {
55
+ this.seq = request.Seq;
56
+ this.playerSentTimestamp = Date.now();
57
+ this.requestFillerSize = request.Filler ? request.Filler.length : 0;
58
+ }
59
+
60
+ update(response: DataChannelLatencyTestResponse) {
61
+ this.playerReceivedTimestamp = Date.now();
62
+ this.streamerReceivedTimestamp = response.ReceivedTimestamp;
63
+ this.streamerSentTimestamp = response.SentTimestamp;
64
+ this.responseFillerSize = response.Filler ? response.Filler.length : 0;
65
+ }
66
+
67
+ }
@@ -0,0 +1,59 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
+ import { DataChannelController } from './DataChannelController';
5
+
6
+ /**
7
+ * A class for sending data channel messages
8
+ */
9
+ export class DataChannelSender {
10
+ dataChannelProvider: DataChannelController;
11
+
12
+ /**
13
+ * @param dataChannelProvider - Data channel object type
14
+ */
15
+ constructor(dataChannelProvider: DataChannelController) {
16
+ this.dataChannelProvider = dataChannelProvider;
17
+ }
18
+
19
+ canSend(): boolean {
20
+ return (
21
+ this.dataChannelProvider.getDataChannelInstance().dataChannel !==
22
+ undefined &&
23
+ this.dataChannelProvider.getDataChannelInstance().dataChannel
24
+ .readyState == 'open'
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Send Data over the Data channel to the UE Instance
30
+ * @param data - Message Data Array Buffer
31
+ */
32
+ sendData(data: ArrayBuffer) {
33
+ // reset the afk inactivity
34
+ const dataChannelInstance =
35
+ this.dataChannelProvider.getDataChannelInstance();
36
+
37
+ if (dataChannelInstance.dataChannel.readyState == 'open') {
38
+ dataChannelInstance.dataChannel.send(data);
39
+ Logger.Log(
40
+ Logger.GetStackTrace(),
41
+ `Message Sent: ${new Uint8Array(data)}`,
42
+ 6
43
+ );
44
+ this.resetAfkWarningTimerOnDataSend();
45
+ } else {
46
+ Logger.Error(
47
+ Logger.GetStackTrace(),
48
+ `Message Failed: ${new Uint8Array(data)}`
49
+ );
50
+ }
51
+ }
52
+
53
+ /**
54
+ * An override method for resetting the Afk warning timer when data is sent over the data channel
55
+ */
56
+ resetAfkWarningTimerOnDataSend() {
57
+ // Base Functionality: Do Nothing
58
+ }
59
+ }
@@ -0,0 +1,61 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * Latency Test Results Data
5
+ */
6
+ export class InitialSettings {
7
+ PixelStreamingSettings: PixelStreamingSettings;
8
+ EncoderSettings: EncoderSettings;
9
+ WebRTCSettings: WebRTCSettings;
10
+
11
+ constructor() {
12
+ this.PixelStreamingSettings = new PixelStreamingSettings();
13
+ this.EncoderSettings = new EncoderSettings();
14
+ this.WebRTCSettings = new WebRTCSettings();
15
+ }
16
+
17
+ /**
18
+ * Checks for compatibility with the FPS and MaxFPS stats between 4.27 and 5
19
+ */
20
+ ueCompatible() {
21
+ if (this.WebRTCSettings.MaxFPS != null) {
22
+ this.WebRTCSettings.FPS = this.WebRTCSettings.MaxFPS;
23
+ }
24
+ }
25
+ }
26
+
27
+ /**
28
+ * A class for handling Pixel Streaming details
29
+ */
30
+ export class PixelStreamingSettings {
31
+ AllowPixelStreamingCommands?: boolean;
32
+ DisableLatencyTest?: boolean;
33
+ }
34
+
35
+ /**
36
+ * A class for handling encoder stats
37
+ */
38
+ export class EncoderSettings {
39
+ TargetBitrate?: number;
40
+ MaxBitrate?: number;
41
+ MinQP?: number;
42
+ MaxQP?: number;
43
+ RateControl?: 'CBR' | 'VBR' | 'ConstQP';
44
+ FillerData?: boolean;
45
+ MultiPass?: 'DISABLED' | 'QUARTER' | 'FULL';
46
+ }
47
+
48
+ /**
49
+ * A class for handling web rtc stats
50
+ */
51
+ export class WebRTCSettings {
52
+ DegradationPref?: 'BALANCED' | 'MAINTAIN_FRAMERATE' | 'MAINTAIN_RESOLUTION';
53
+ MinBitrate?: number;
54
+ MaxBitrate?: number;
55
+ LowQP?: number;
56
+ HighQP?: number;
57
+ // UE4.27 compatible
58
+ MaxFPS?: number;
59
+ // UE5 compatible
60
+ FPS?: number;
61
+ }
@@ -0,0 +1,76 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
+ /**
5
+ * Latency Test Results Data
6
+ */
7
+ export class LatencyTestResults {
8
+ //Fields Set from the latency payload regardless of version
9
+ ReceiptTimeMs: number = null;
10
+ TransmissionTimeMs: number = null;
11
+
12
+ //Fields Set from the latency payload from 4.27.2
13
+ PreCaptureTimeMs: number = null;
14
+ PostCaptureTimeMs: number = null;
15
+ PreEncodeTimeMs: number = null;
16
+ PostEncodeTimeMs: number = null;
17
+
18
+ //Fields Set from the latency payload from 5.0
19
+ EncodeMs: number = null;
20
+ CaptureToSendMs: number = null;
21
+
22
+ //Fields Set when processed
23
+ testStartTimeMs = 0;
24
+ browserReceiptTimeMs = 0;
25
+
26
+ //Fields set from calculations
27
+ latencyExcludingDecode = 0;
28
+ testDuration = 0;
29
+ //ueLatency: number = 0;
30
+ networkLatency = 0;
31
+ browserSendLatency = 0;
32
+ frameDisplayDeltaTimeMs = 0;
33
+ endToEndLatency = 0;
34
+ //uePixelStreamLatency: number = 0;
35
+ encodeLatency = 0;
36
+
37
+ /**
38
+ * Sets the Delta Time Milliseconds
39
+ * @param DeltaTimeMs - Delta Time Milliseconds
40
+ */
41
+ setFrameDisplayDeltaTime(DeltaTimeMs: number) {
42
+ if (this.frameDisplayDeltaTimeMs == 0) {
43
+ this.frameDisplayDeltaTimeMs = Math.round(DeltaTimeMs);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Process the encoder times and set them
49
+ */
50
+ processFields() {
51
+ if (
52
+ this.EncodeMs == null &&
53
+ (this.PreEncodeTimeMs != null || this.PostEncodeTimeMs != null)
54
+ ) {
55
+ Logger.Log(
56
+ Logger.GetStackTrace(),
57
+ `Setting Encode Ms \n ${this.PostEncodeTimeMs} \n ${this.PreEncodeTimeMs}`,
58
+ 6
59
+ );
60
+ this.EncodeMs = this.PostEncodeTimeMs - this.PreEncodeTimeMs;
61
+ }
62
+
63
+ if (
64
+ this.CaptureToSendMs == null &&
65
+ (this.PreCaptureTimeMs != null || this.PostCaptureTimeMs != null)
66
+ ) {
67
+ Logger.Log(
68
+ Logger.GetStackTrace(),
69
+ `Setting CaptureToSendMs Ms \n ${this.PostCaptureTimeMs} \n ${this.PreCaptureTimeMs}`,
70
+ 6
71
+ );
72
+ this.CaptureToSendMs =
73
+ this.PostCaptureTimeMs - this.PreCaptureTimeMs;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,114 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * A class for managing the freeze frame object
5
+ */
6
+ export class FreezeFrame {
7
+ protected rootDiv: HTMLElement;
8
+ protected rootElement: HTMLDivElement;
9
+ imageElement: HTMLImageElement;
10
+ freezeFrameHeight = 0;
11
+ freezeFrameWidth = 0;
12
+
13
+ /**
14
+ * Construct a freeze frame
15
+ * @param rootDiv the div that a freeze frame element will be injected into
16
+ */
17
+ constructor(rootDiv: HTMLElement) {
18
+ this.rootDiv = rootDiv;
19
+
20
+ // create the overlay
21
+ this.rootElement = document.createElement('div');
22
+ this.rootElement.id = 'freezeFrame';
23
+ this.rootElement.style.display = 'none';
24
+ this.rootElement.style.pointerEvents = 'none';
25
+ this.rootElement.style.position = 'absolute';
26
+ this.rootElement.style.zIndex = '20';
27
+
28
+ // create the image place holder
29
+ this.imageElement = document.createElement('img');
30
+ this.imageElement.style.position = 'absolute';
31
+
32
+ // append the image into the root element and append the element to the root div
33
+ this.rootElement.appendChild(this.imageElement);
34
+ this.rootDiv.appendChild(this.rootElement);
35
+ }
36
+
37
+ /**
38
+ * Set the freeze frame element for showing
39
+ */
40
+ setElementForShow() {
41
+ this.rootElement.style.display = 'block';
42
+ }
43
+
44
+ /**
45
+ * Set the freeze frame element for hiding
46
+ */
47
+ setElementForHide() {
48
+ this.rootElement.style.display = 'none';
49
+ }
50
+
51
+ /**
52
+ * Update the freeze frames image source
53
+ * @param jpeg - the freeze frame image as a byte array data
54
+ */
55
+ updateImageElementSource(jpeg: Uint8Array) {
56
+ const base64 = btoa(
57
+ jpeg.reduce((data, byte) => data + String.fromCharCode(byte), '')
58
+ );
59
+ this.imageElement.src = 'data:image/jpeg;base64,' + base64;
60
+ }
61
+
62
+ /**
63
+ * Set the dimensions for the freeze frame from the element and resize it
64
+ */
65
+ setDimensionsFromElementAndResize() {
66
+ this.freezeFrameHeight = this.imageElement.naturalHeight;
67
+ this.freezeFrameWidth = this.imageElement.naturalWidth;
68
+ this.resize();
69
+ }
70
+
71
+ /**
72
+ * Resize a freeze frame element
73
+ */
74
+ resize() {
75
+ if (this.freezeFrameWidth !== 0 && this.freezeFrameHeight !== 0) {
76
+ let displayWidth = 0;
77
+ let displayHeight = 0;
78
+ let displayTop = 0;
79
+ let displayLeft = 0;
80
+ const parentAspectRatio =
81
+ this.rootDiv.clientWidth / this.rootDiv.clientHeight;
82
+ const videoAspectRatio =
83
+ this.freezeFrameWidth / this.freezeFrameHeight;
84
+ if (parentAspectRatio < videoAspectRatio) {
85
+ displayWidth = this.rootDiv.clientWidth;
86
+ displayHeight = Math.floor(
87
+ this.rootDiv.clientWidth / videoAspectRatio
88
+ );
89
+ displayTop = Math.floor(
90
+ (this.rootDiv.clientHeight - displayHeight) * 0.5
91
+ );
92
+ displayLeft = 0;
93
+ } else {
94
+ displayWidth = Math.floor(
95
+ this.rootDiv.clientHeight * videoAspectRatio
96
+ );
97
+ displayHeight = this.rootDiv.clientHeight;
98
+ displayTop = 0;
99
+ displayLeft = Math.floor(
100
+ (this.rootDiv.clientWidth - displayWidth) * 0.5
101
+ );
102
+ }
103
+ this.rootElement.style.width = this.rootDiv.offsetWidth + 'px';
104
+ this.rootElement.style.height = this.rootDiv.offsetHeight + 'px';
105
+ this.rootElement.style.left = 0 + 'px';
106
+ this.rootElement.style.top = 0 + 'px';
107
+
108
+ this.imageElement.style.width = displayWidth + 'px';
109
+ this.imageElement.style.height = displayHeight + 'px';
110
+ this.imageElement.style.left = displayLeft + 'px';
111
+ this.imageElement.style.top = displayTop + 'px';
112
+ }
113
+ }
114
+ }
@@ -0,0 +1,114 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
+ import { FreezeFrame } from './FreezeFrame';
5
+
6
+ /**
7
+ * A class for controlling freeze frame functionality
8
+ */
9
+ export class FreezeFrameController {
10
+ freezeFrame: FreezeFrame;
11
+ receiving = false;
12
+ size = 0;
13
+ jpeg: Uint8Array = undefined;
14
+ valid = false;
15
+ freezeFrameDelay = 50;
16
+
17
+ /**
18
+ * Construct a freeze frame controller
19
+ * @param rootDiv - the div that a freeze frame element will be injected into
20
+ */
21
+ constructor(rootDiv: HTMLElement) {
22
+ this.freezeFrame = new FreezeFrame(rootDiv);
23
+ }
24
+
25
+ /**
26
+ * Show the freeze frame if it is valid
27
+ */
28
+ showFreezeFrame() {
29
+ if (this.valid) {
30
+ this.freezeFrame.setElementForShow();
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Hide the freeze frame and set the validity to false
36
+ */
37
+ hideFreezeFrame() {
38
+ this.valid = false;
39
+ this.freezeFrame.setElementForHide();
40
+ }
41
+
42
+ /**
43
+ * Update the freeze frames image source and load it
44
+ * @param jpeg - the freeze frame image as a byte array data
45
+ * @param onLoadCallBack - a call back for managing if the play overlay needs to be shown or not
46
+ */
47
+ updateFreezeFrameAndShow(jpeg: Uint8Array, onLoadCallBack: () => void) {
48
+ this.freezeFrame.updateImageElementSource(jpeg);
49
+ this.freezeFrame.imageElement.onload = () => {
50
+ this.freezeFrame.setDimensionsFromElementAndResize();
51
+ onLoadCallBack();
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Process the new freeze frame image and update it
57
+ * @param view - the freeze frame image as a byte array data
58
+ * @param onLoadCallBack - a call back for managing if the play overlay needs to be shown or not
59
+ */
60
+ processFreezeFrameMessage(view: Uint8Array, onLoadCallBack: () => void) {
61
+ // Reset freeze frame if we got a freeze frame message and we are not "receiving" yet.
62
+ if (!this.receiving) {
63
+ this.receiving = true;
64
+ this.valid = false;
65
+ this.size = 0;
66
+ this.jpeg = undefined;
67
+ }
68
+
69
+ // Extract total size of freeze frame (across all chunks)
70
+ this.size = new DataView(view.slice(1, 5).buffer).getInt32(0, true);
71
+
72
+ // Get the jpeg part of the payload
73
+ const jpegBytes = view.slice(1 + 4);
74
+
75
+ // Append to existing jpeg that holds the freeze frame
76
+ if (this.jpeg) {
77
+ const jpeg = new Uint8Array(this.jpeg.length + jpegBytes.length);
78
+ jpeg.set(this.jpeg, 0);
79
+ jpeg.set(jpegBytes, this.jpeg.length);
80
+ this.jpeg = jpeg;
81
+ }
82
+ // No existing freeze frame jpeg, make one
83
+ else {
84
+ this.jpeg = jpegBytes;
85
+ this.receiving = true;
86
+ Logger.Log(
87
+ Logger.GetStackTrace(),
88
+ `received first chunk of freeze frame: ${this.jpeg.length}/${this.size}`,
89
+ 6
90
+ );
91
+ }
92
+
93
+ // Finished receiving freeze frame, we can show it now
94
+ if (this.jpeg.length === this.size) {
95
+ this.receiving = false;
96
+ this.valid = true;
97
+ Logger.Log(
98
+ Logger.GetStackTrace(),
99
+ `received complete freeze frame ${this.size}`,
100
+ 6
101
+ );
102
+ this.updateFreezeFrameAndShow(this.jpeg, onLoadCallBack);
103
+ }
104
+ // We received more data than the freeze frame payload message indicate (this is an error)
105
+ else if (this.jpeg.length > this.size) {
106
+ Logger.Error(
107
+ Logger.GetStackTrace(),
108
+ `received bigger freeze frame than advertised: ${this.jpeg.length}/${this.size}`
109
+ );
110
+ this.jpeg = undefined;
111
+ this.receiving = false;
112
+ }
113
+ }
114
+ }