@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,26 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * Outbound Video Stats collected from the RTC Stats Report
5
+ */
6
+ export class OutBoundVideoStats {
7
+ bytesSent: number;
8
+ id: string;
9
+ localId: string;
10
+ packetsSent: number;
11
+ remoteTimestamp: number;
12
+ timestamp: number;
13
+ }
14
+
15
+ /**
16
+ * Outbound Stats collected from the RTC Stats Report
17
+ */
18
+ export class OutBoundRTPStats {
19
+ kind: string;
20
+ bytesSent: number;
21
+ id: string;
22
+ localId: string;
23
+ packetsSent: number;
24
+ remoteTimestamp: number;
25
+ timestamp: number;
26
+ }
@@ -0,0 +1,563 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
4
+ import { Config, OptionParameters, Flags } from '../Config/Config';
5
+ import { AggregatedStats } from './AggregatedStats';
6
+ import { parseRtpParameters, splitSections } from 'sdp';
7
+ import { RTCUtils } from '../Util/RTCUtils';
8
+
9
+ /**
10
+ * Handles the Peer Connection
11
+ */
12
+ export class PeerConnectionController {
13
+ peerConnection: RTCPeerConnection;
14
+ aggregatedStats: AggregatedStats;
15
+ config: Config;
16
+ preferredCodec: string;
17
+ updateCodecSelection: boolean;
18
+
19
+ /**
20
+ * Create a new RTC Peer Connection client
21
+ * @param options - Peer connection Options
22
+ * @param config - The config for our PS experience.
23
+ */
24
+ constructor(
25
+ options: RTCConfiguration,
26
+ config: Config,
27
+ preferredCodec: string
28
+ ) {
29
+ this.config = config;
30
+ this.createPeerConnection(options, preferredCodec);
31
+ }
32
+
33
+ createPeerConnection(options: RTCConfiguration, preferredCodec: string) {
34
+ // Set the ICE transport to relay if TURN enabled
35
+ if (this.config.isFlagEnabled(Flags.ForceTURN)) {
36
+ options.iceTransportPolicy = 'relay';
37
+ Logger.Log(
38
+ Logger.GetStackTrace(),
39
+ 'Forcing TURN usage by setting ICE Transport Policy in peer connection config.'
40
+ );
41
+ }
42
+
43
+ // build a new peer connection with the options
44
+ this.peerConnection = new RTCPeerConnection(options);
45
+ this.peerConnection.onsignalingstatechange = (ev: Event) =>
46
+ this.handleSignalStateChange(ev);
47
+ this.peerConnection.oniceconnectionstatechange = (ev: Event) =>
48
+ this.handleIceConnectionStateChange(ev);
49
+ this.peerConnection.onicegatheringstatechange = (ev: Event) =>
50
+ this.handleIceGatheringStateChange(ev);
51
+ this.peerConnection.ontrack = (ev: RTCTrackEvent) =>
52
+ this.handleOnTrack(ev);
53
+ this.peerConnection.onicecandidate = (ev: RTCPeerConnectionIceEvent) =>
54
+ this.handleIceCandidate(ev);
55
+ this.peerConnection.ondatachannel = (ev: RTCDataChannelEvent) =>
56
+ this.handleDataChannel(ev);
57
+ this.aggregatedStats = new AggregatedStats();
58
+ this.preferredCodec = preferredCodec;
59
+ this.updateCodecSelection = true;
60
+ }
61
+
62
+ /**
63
+ * Create an offer for the Web RTC handshake and send the offer to the signaling server via websocket
64
+ * @param offerOptions - RTC Offer Options
65
+ */
66
+ async createOffer(offerOptions: RTCOfferOptions, config: Config) {
67
+ Logger.Log(Logger.GetStackTrace(), 'Create Offer', 6);
68
+
69
+ const isLocalhostConnection =
70
+ location.hostname === 'localhost' ||
71
+ location.hostname === '127.0.0.1';
72
+ const isHttpsConnection = location.protocol === 'https:';
73
+ let useMic = config.isFlagEnabled(Flags.UseMic);
74
+ if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
75
+ useMic = false;
76
+ Logger.Error(
77
+ Logger.GetStackTrace(),
78
+ 'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
79
+ );
80
+ Logger.Error(
81
+ Logger.GetStackTrace(),
82
+ "For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
83
+ );
84
+ }
85
+
86
+ this.setupTransceiversAsync(useMic).finally(() => {
87
+ this.peerConnection
88
+ ?.createOffer(offerOptions)
89
+ .then((offer: RTCSessionDescriptionInit) => {
90
+ this.showTextOverlayConnecting();
91
+ offer.sdp = this.mungeSDP(offer.sdp, useMic);
92
+ this.peerConnection?.setLocalDescription(offer);
93
+ this.onSendWebRTCOffer(offer);
94
+ })
95
+ .catch(() => {
96
+ this.showTextOverlaySetupFailure();
97
+ });
98
+ });
99
+ }
100
+
101
+ /**
102
+ *
103
+ */
104
+ async receiveOffer(offer: RTCSessionDescriptionInit, config: Config) {
105
+ Logger.Log(Logger.GetStackTrace(), 'Receive Offer', 6);
106
+
107
+ this.peerConnection?.setRemoteDescription(offer).then(() => {
108
+ const isLocalhostConnection =
109
+ location.hostname === 'localhost' ||
110
+ location.hostname === '127.0.0.1';
111
+ const isHttpsConnection = location.protocol === 'https:';
112
+ let useMic = config.isFlagEnabled(Flags.UseMic);
113
+ if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
114
+ useMic = false;
115
+ Logger.Error(
116
+ Logger.GetStackTrace(),
117
+ 'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
118
+ );
119
+ Logger.Error(
120
+ Logger.GetStackTrace(),
121
+ "For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
122
+ );
123
+ }
124
+
125
+ this.setupTransceiversAsync(useMic).finally(() => {
126
+ this.peerConnection
127
+ ?.createAnswer()
128
+ .then((Answer: RTCSessionDescriptionInit) => {
129
+ Answer.sdp = this.mungeSDP(Answer.sdp, useMic);
130
+ return this.peerConnection?.setLocalDescription(Answer);
131
+ })
132
+ .then(() => {
133
+ this.onSendWebRTCAnswer(
134
+ this.peerConnection?.currentLocalDescription
135
+ );
136
+ })
137
+ .catch(() => {
138
+ Logger.Error(
139
+ Logger.GetStackTrace(),
140
+ 'createAnswer() failed'
141
+ );
142
+ });
143
+ });
144
+ });
145
+
146
+ // Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
147
+ this.config.setOptionSettingOptions(
148
+ OptionParameters.PreferredCodec,
149
+ this.parseAvailableCodecs(offer).filter((value) =>
150
+ this.config
151
+ .getSettingOption(OptionParameters.PreferredCodec)
152
+ .options.includes(value)
153
+ )
154
+ );
155
+ }
156
+
157
+ /**
158
+ * Set the Remote Descriptor from the signaling server to the RTC Peer Connection
159
+ * @param answer - RTC Session Descriptor from the Signaling Server
160
+ */
161
+ receiveAnswer(answer: RTCSessionDescriptionInit) {
162
+ this.peerConnection?.setRemoteDescription(answer);
163
+ // Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
164
+ this.config.setOptionSettingOptions(
165
+ OptionParameters.PreferredCodec,
166
+ this.parseAvailableCodecs(answer).filter((value) =>
167
+ this.config
168
+ .getSettingOption(OptionParameters.PreferredCodec)
169
+ .options.includes(value)
170
+ )
171
+ );
172
+ }
173
+
174
+ /**
175
+ * Generate Aggregated Stats and then fire a onVideo Stats event
176
+ */
177
+ generateStats() {
178
+ this.peerConnection?.getStats(null).then((StatsData: RTCStatsReport) => {
179
+ this.aggregatedStats.processStats(StatsData);
180
+ this.onVideoStats(this.aggregatedStats);
181
+
182
+ // Update the preferred codec selection based on what was actually negotiated
183
+ if (this.updateCodecSelection && !!this.aggregatedStats.inboundVideoStats.codecId) {
184
+ this.config.setOptionSettingValue(
185
+ OptionParameters.PreferredCodec,
186
+ this.aggregatedStats.codecs.get(
187
+ this.aggregatedStats.inboundVideoStats.codecId
188
+ )
189
+ );
190
+ }
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Close The Peer Connection
196
+ */
197
+ close() {
198
+ if (this.peerConnection) {
199
+ this.peerConnection.close();
200
+ this.peerConnection = null;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Modify the Session Descriptor
206
+ * @param sdp - Session Descriptor as a string
207
+ * @param useMic - Is the microphone in use
208
+ * @returns A modified Session Descriptor
209
+ */
210
+ mungeSDP(sdp: string, useMic: boolean) {
211
+ let mungedSDP = sdp.replace(
212
+ /(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,
213
+ '$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r\n'
214
+ );
215
+
216
+ // set max bitrate to highest bitrate Opus supports
217
+ let audioSDP = 'maxaveragebitrate=510000;';
218
+
219
+ if (useMic) {
220
+ // set the max capture rate to 48khz (so we can send high quality audio from mic)
221
+ audioSDP += 'sprop-maxcapturerate=48000;';
222
+ }
223
+
224
+ // Force mono or stereo based on whether ?forceMono was passed or not
225
+ audioSDP += this.config.isFlagEnabled(Flags.ForceMonoAudio)
226
+ ? 'stereo=0;'
227
+ : 'stereo=1;';
228
+
229
+ // enable in-band forward error correction for opus audio
230
+ audioSDP += 'useinbandfec=1';
231
+
232
+ // We use the line 'useinbandfec=1' (which Opus uses) to set our Opus specific audio parameters.
233
+ mungedSDP = mungedSDP.replace('useinbandfec=1', audioSDP);
234
+
235
+ return mungedSDP;
236
+ }
237
+
238
+ /**
239
+ * When a Ice Candidate is received add to the RTC Peer Connection
240
+ * @param iceCandidate - RTC Ice Candidate from the Signaling Server
241
+ */
242
+ handleOnIce(iceCandidate: RTCIceCandidate) {
243
+ Logger.Log(Logger.GetStackTrace(), 'peerconnection handleOnIce', 6);
244
+
245
+ // // if forcing TURN, reject any candidates not relay
246
+ if (this.config.isFlagEnabled(Flags.ForceTURN)) {
247
+ // check if no relay address is found, if so, we are assuming it means no TURN server
248
+ if (iceCandidate.candidate.indexOf('relay') < 0) {
249
+ Logger.Info(
250
+ Logger.GetStackTrace(),
251
+ `Dropping candidate because it was not TURN relay. | Type= ${iceCandidate.type} | Protocol= ${iceCandidate.protocol} | Address=${iceCandidate.address} | Port=${iceCandidate.port} |`,
252
+ 6
253
+ );
254
+ return;
255
+ }
256
+ }
257
+
258
+ this.peerConnection?.addIceCandidate(iceCandidate);
259
+ }
260
+
261
+ /**
262
+ * When the RTC Peer Connection Signaling server state Changes
263
+ * @param state - Signaling Server State Change Event
264
+ */
265
+ handleSignalStateChange(state: Event) {
266
+ Logger.Log(
267
+ Logger.GetStackTrace(),
268
+ 'signaling state change: ' + state,
269
+ 6
270
+ );
271
+ }
272
+
273
+ /**
274
+ * Handle when the Ice Connection State Changes
275
+ * @param state - Ice Connection State
276
+ */
277
+ handleIceConnectionStateChange(state: Event) {
278
+ Logger.Log(
279
+ Logger.GetStackTrace(),
280
+ 'ice connection state change: ' + state,
281
+ 6
282
+ );
283
+ this.onIceConnectionStateChange(state);
284
+ }
285
+
286
+ /**
287
+ * Handle when the Ice Gathering State Changes
288
+ * @param state - Ice Gathering State Change
289
+ */
290
+ handleIceGatheringStateChange(state: Event) {
291
+ Logger.Log(
292
+ Logger.GetStackTrace(),
293
+ 'ice gathering state change: ' + JSON.stringify(state),
294
+ 6
295
+ );
296
+ }
297
+
298
+ /**
299
+ * Activates the onTrack method
300
+ * @param event - The webRtc track event
301
+ */
302
+ handleOnTrack(event: RTCTrackEvent) {
303
+ this.onTrack(event);
304
+ }
305
+
306
+ /**
307
+ * Activates the onPeerIceCandidate
308
+ * @param event - The peer ice candidate
309
+ */
310
+ handleIceCandidate(event: RTCPeerConnectionIceEvent) {
311
+ this.onPeerIceCandidate(event);
312
+ }
313
+
314
+ /**
315
+ * Activates the onDataChannel
316
+ * @param event - The peer's data channel
317
+ */
318
+ handleDataChannel(event: RTCDataChannelEvent) {
319
+ this.onDataChannel(event);
320
+ }
321
+
322
+ /**
323
+ * An override method for onTrack for use outside of the PeerConnectionController
324
+ * @param trackEvent - The webRtc track event
325
+ */
326
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
327
+ onTrack(trackEvent: RTCTrackEvent) {
328
+ // Default Functionality: Do Nothing
329
+ }
330
+
331
+ /**
332
+ * An override method for onIceConnectionStateChange for use outside of the PeerConnectionController
333
+ * @param event - The webRtc iceconnectionstatechange event
334
+ */
335
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
336
+ onIceConnectionStateChange(event: Event) {
337
+ // Default Functionality: Do Nothing
338
+ }
339
+
340
+ /**
341
+ * An override method for onPeerIceCandidate for use outside of the PeerConnectionController
342
+ * @param peerConnectionIceEvent - The peer ice candidate
343
+ */
344
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
345
+ onPeerIceCandidate(peerConnectionIceEvent: RTCPeerConnectionIceEvent) {
346
+ // Default Functionality: Do Nothing
347
+ }
348
+
349
+ /**
350
+ * An override method for onDataChannel for use outside of the PeerConnectionController
351
+ * @param datachannelEvent - The peer's data channel
352
+ */
353
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
354
+ onDataChannel(datachannelEvent: RTCDataChannelEvent) {
355
+ // Default Functionality: Do Nothing
356
+ }
357
+
358
+ /**
359
+ * Setup tracks on the RTC Peer Connection
360
+ * @param useMic - is mic in use
361
+ */
362
+ async setupTransceiversAsync(useMic: boolean) {
363
+ const hasTransceivers =
364
+ this.peerConnection?.getTransceivers().length > 0;
365
+
366
+ // Setup a transceiver for getting UE video
367
+ this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
368
+
369
+ // We can only set preferrec codec on Chrome
370
+ if (RTCRtpReceiver.getCapabilities && this.preferredCodec != '') {
371
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
372
+ if (
373
+ transceiver &&
374
+ transceiver.receiver &&
375
+ transceiver.receiver.track &&
376
+ transceiver.receiver.track.kind === 'video' &&
377
+ // As of 06/2023, FireFox has added RTCRtpReceiver.getCapabilities, but hasn't added the ability to set codec preferences
378
+ transceiver.setCodecPreferences
379
+ ) {
380
+ const preferredRTPCodec = this.preferredCodec.split(' ');
381
+ const codecs = [
382
+ {
383
+ mimeType:
384
+ 'video/' + preferredRTPCodec[0] /* Name */,
385
+ clockRate: 90000,
386
+ sdpFmtpLine: preferredRTPCodec[1] /* sdpFmtpLine */
387
+ ? preferredRTPCodec[1]
388
+ : ''
389
+ }
390
+ ];
391
+
392
+ this.config
393
+ .getSettingOption(OptionParameters.PreferredCodec)
394
+ .options.filter((option) => {
395
+ // Remove the preferred codec from the list of possible codecs as we've set it already
396
+ return option != this.preferredCodec;
397
+ })
398
+ .forEach((option) => {
399
+ // Ammend the rest of the browsers supported codecs
400
+ const altCodec = option.split(' ');
401
+ codecs.push({
402
+ mimeType: 'video/' + altCodec[0] /* Name */,
403
+ clockRate: 90000,
404
+ sdpFmtpLine: altCodec[1] /* sdpFmtpLine */
405
+ ? altCodec[1]
406
+ : ''
407
+ });
408
+ });
409
+
410
+ for (const codec of codecs) {
411
+ if (codec.sdpFmtpLine === '') {
412
+ // We can't dynamically add members to the codec, so instead remove the field if it's empty
413
+ delete codec.sdpFmtpLine;
414
+ }
415
+ }
416
+
417
+ transceiver.setCodecPreferences(codecs);
418
+ }
419
+ }
420
+ }
421
+
422
+ // Setup a transceiver for sending mic audio to UE and receiving audio from UE
423
+ if (!useMic) {
424
+ this.peerConnection?.addTransceiver('audio', {
425
+ direction: 'recvonly'
426
+ });
427
+ } else {
428
+ // set the audio options based on mic usage
429
+ const audioOptions = {
430
+ autoGainControl: false,
431
+ channelCount: 1,
432
+ echoCancellation: false,
433
+ latency: 0,
434
+ noiseSuppression: false,
435
+ sampleRate: 48000,
436
+ sampleSize: 16,
437
+ volume: 1.0
438
+ }
439
+
440
+ // set the media send options
441
+ const mediaSendOptions: MediaStreamConstraints = {
442
+ video: false,
443
+ audio: audioOptions
444
+ };
445
+
446
+ // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
447
+ const stream = await navigator.mediaDevices.getUserMedia(
448
+ mediaSendOptions
449
+ );
450
+ if (stream) {
451
+ if (hasTransceivers) {
452
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
453
+ if (RTCUtils.canTransceiverReceiveAudio(transceiver)) {
454
+ for (const track of stream.getTracks()) {
455
+ if (track.kind && track.kind == 'audio') {
456
+ transceiver.sender.replaceTrack(track);
457
+ transceiver.direction = 'sendrecv';
458
+ }
459
+ }
460
+ }
461
+ }
462
+ } else {
463
+ for (const track of stream.getTracks()) {
464
+ if (track.kind && track.kind == 'audio') {
465
+ this.peerConnection?.addTransceiver(track, {
466
+ direction: 'sendrecv'
467
+ });
468
+ }
469
+ }
470
+ }
471
+ } else {
472
+ this.peerConnection?.addTransceiver('audio', {
473
+ direction: 'recvonly'
474
+ });
475
+ }
476
+ }
477
+ }
478
+
479
+ /**
480
+ * And override event for when the video stats are fired
481
+ * @param event - Aggregated Stats
482
+ */
483
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
484
+ onVideoStats(event: AggregatedStats) {
485
+ // Default Functionality: Do Nothing
486
+ }
487
+
488
+ /**
489
+ * Event to send the RTC offer to the Signaling server
490
+ * @param offer - RTC Offer
491
+ */
492
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
493
+ onSendWebRTCOffer(offer: RTCSessionDescriptionInit) {
494
+ // Default Functionality: Do Nothing
495
+ }
496
+
497
+ /**
498
+ * Event to send the RTC Answer to the Signaling server
499
+ * @param answer - RTC Answer
500
+ */
501
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
502
+ onSendWebRTCAnswer(answer: RTCSessionDescriptionInit) {
503
+ // Default Functionality: Do Nothing
504
+ }
505
+
506
+ /**
507
+ * An override for showing the Peer connection connecting Overlay
508
+ */
509
+ showTextOverlayConnecting() {
510
+ // Default Functionality: Do Nothing
511
+ }
512
+
513
+ /**
514
+ * An override for showing the Peer connection Failed overlay
515
+ */
516
+ showTextOverlaySetupFailure() {
517
+ // Default Functionality: Do Nothing
518
+ }
519
+
520
+ parseAvailableCodecs(
521
+ rtcSessionDescription: RTCSessionDescriptionInit
522
+ ): Array<string> {
523
+ // No point in updating the available codecs if on FF
524
+ if (!RTCRtpReceiver.getCapabilities)
525
+ return ['Only available on Chrome'];
526
+
527
+ const ueSupportedCodecs: Array<string> = [];
528
+ const sections = splitSections(rtcSessionDescription.sdp);
529
+ // discard the session information as we only want media related info
530
+ sections.shift();
531
+ sections.forEach((mediaSection) => {
532
+ const { codecs } = parseRtpParameters(mediaSection);
533
+ // Filter only for VPX / H26X / AV1
534
+ const matcher = /(VP\d|H26\d|AV1).*/;
535
+ codecs.forEach((c) => {
536
+ const str =
537
+ c.name +
538
+ ' ' +
539
+ Object.keys(c.parameters || {})
540
+ .map((p) => p + '=' + c.parameters[p])
541
+ .join(';');
542
+ const match = matcher.exec(str);
543
+ if (match !== null) {
544
+ if (c.name == 'VP9') {
545
+ // UE answers don't specify profile but we know we want profile 0
546
+ c.parameters = {
547
+ 'profile-id': '0'
548
+ };
549
+ }
550
+ const codecStr: string =
551
+ c.name +
552
+ ' ' +
553
+ Object.keys(c.parameters || {})
554
+ .map((p) => p + '=' + c.parameters[p])
555
+ .join(';');
556
+ ueSupportedCodecs.push(codecStr);
557
+ }
558
+ });
559
+ });
560
+
561
+ return ueSupportedCodecs;
562
+ }
563
+ }
@@ -0,0 +1,10 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * Session statistics
5
+ */
6
+ export class SessionStats {
7
+ runTime: string;
8
+ controlsStreamInput: string;
9
+ videoEncoderAvgQP: number;
10
+ }
@@ -0,0 +1,11 @@
1
+ // Copyright Epic Games, Inc. All Rights Reserved.
2
+
3
+ /**
4
+ * Class to hold the stream stats data coming in from webRtc
5
+ */
6
+ export class StreamStats {
7
+ id: string;
8
+ streamIdentifier: string;
9
+ timestamp: number;
10
+ trackIds: string[];
11
+ }