@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.1.3 → 0.2.1

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 (94) hide show
  1. package/.eslintrc.js +1 -1
  2. package/.prettierrc.json +1 -0
  3. package/dist/lib-pixelstreamingfrontend.esm.js +1 -1
  4. package/dist/lib-pixelstreamingfrontend.js +1 -1
  5. package/package.json +6 -5
  6. package/src/AFK/AFKController.ts +10 -32
  7. package/src/Config/Config.ts +179 -201
  8. package/src/Config/SettingBase.ts +61 -2
  9. package/src/Config/SettingFlag.ts +10 -48
  10. package/src/Config/SettingNumber.ts +10 -28
  11. package/src/Config/SettingOption.ts +13 -46
  12. package/src/Config/SettingText.ts +9 -37
  13. package/src/DataChannel/DataChannelController.ts +6 -26
  14. package/src/DataChannel/DataChannelLatencyTestController.ts +38 -33
  15. package/src/DataChannel/DataChannelLatencyTestResults.ts +8 -10
  16. package/src/DataChannel/DataChannelSender.ts +5 -15
  17. package/src/DataChannel/LatencyTestResults.ts +5 -15
  18. package/src/FreezeFrame/FreezeFrame.ts +7 -19
  19. package/src/FreezeFrame/FreezeFrameController.ts +3 -14
  20. package/src/Inputs/GamepadController.ts +123 -221
  21. package/src/Inputs/GamepadTypes.ts +23 -0
  22. package/src/Inputs/IInputController.ts +17 -0
  23. package/src/Inputs/InputClassesFactory.ts +38 -45
  24. package/src/Inputs/KeyCodes.ts +114 -0
  25. package/src/Inputs/KeyboardController.ts +49 -232
  26. package/src/Inputs/MouseController.ts +71 -297
  27. package/src/Inputs/MouseControllerHovering.ts +118 -0
  28. package/src/Inputs/MouseControllerLocked.ts +194 -0
  29. package/src/Inputs/TouchController.ts +49 -105
  30. package/src/Inputs/TouchControllerFake.ts +132 -0
  31. package/src/Inputs/XRGamepadController.ts +35 -44
  32. package/src/PeerConnectionController/AggregatedStats.ts +26 -54
  33. package/src/PeerConnectionController/CandidatePairStats.ts +1 -1
  34. package/src/PeerConnectionController/CandidateStat.ts +1 -1
  35. package/src/PeerConnectionController/PeerConnectionController.ts +217 -164
  36. package/src/PixelStreaming/PixelStreaming.ts +174 -226
  37. package/src/UI/OnScreenKeyboard.ts +14 -9
  38. package/src/UeInstanceMessage/ResponseController.ts +6 -15
  39. package/src/UeInstanceMessage/SendMessageController.ts +16 -18
  40. package/src/UeInstanceMessage/StreamMessageController.ts +3 -12
  41. package/src/UeInstanceMessage/ToStreamerMessagesController.ts +3 -9
  42. package/src/Util/EventEmitter.ts +17 -22
  43. package/src/Util/FileUtil.ts +11 -34
  44. package/src/Util/IURLSearchParams.ts +25 -0
  45. package/src/Util/InputCoordTranslator.ts +73 -0
  46. package/src/Util/RTCUtils.ts +23 -15
  47. package/src/VideoPlayer/StreamController.ts +6 -23
  48. package/src/VideoPlayer/VideoPlayer.ts +9 -30
  49. package/src/WebRtcPlayer/WebRtcPlayerController.ts +328 -690
  50. package/src/WebXR/WebXRController.ts +82 -94
  51. package/src/pixelstreamingfrontend.ts +6 -10
  52. package/types/AFK/AFKController.d.ts +0 -1
  53. package/types/Config/Config.d.ts +6 -5
  54. package/types/Config/SettingBase.d.ts +13 -0
  55. package/types/Config/SettingFlag.d.ts +1 -10
  56. package/types/Config/SettingNumber.d.ts +1 -5
  57. package/types/Config/SettingOption.d.ts +1 -10
  58. package/types/Config/SettingText.d.ts +1 -9
  59. package/types/DataChannel/DataChannelLatencyTestController.d.ts +1 -1
  60. package/types/Inputs/GamepadController.d.ts +22 -46
  61. package/types/Inputs/GamepadTypes.d.ts +7 -0
  62. package/types/Inputs/IInputController.d.ts +16 -0
  63. package/types/Inputs/InputClassesFactory.d.ts +7 -8
  64. package/types/Inputs/KeyCodes.d.ts +5 -0
  65. package/types/Inputs/KeyboardController.d.ts +17 -45
  66. package/types/Inputs/MouseController.d.ts +33 -68
  67. package/types/Inputs/MouseControllerHovering.d.ts +26 -0
  68. package/types/Inputs/MouseControllerLocked.d.ts +31 -0
  69. package/types/Inputs/TouchController.d.ts +19 -44
  70. package/types/Inputs/TouchControllerFake.d.ts +29 -0
  71. package/types/Inputs/XRGamepadController.d.ts +0 -7
  72. package/types/PeerConnectionController/PeerConnectionController.d.ts +10 -1
  73. package/types/PixelStreaming/PixelStreaming.d.ts +14 -2
  74. package/types/UI/OnScreenKeyboard.d.ts +2 -2
  75. package/types/Util/EventEmitter.d.ts +1 -1
  76. package/types/Util/IURLSearchParams.d.ts +9 -0
  77. package/types/Util/InputCoordTranslator.d.ts +29 -0
  78. package/types/VideoPlayer/StreamController.d.ts +0 -2
  79. package/types/WebRtcPlayer/WebRtcPlayerController.d.ts +19 -17
  80. package/types/pixelstreamingfrontend.d.ts +1 -1
  81. package/src/Inputs/FakeTouchController.ts +0 -199
  82. package/src/Inputs/HoveringMouseEvents.ts +0 -192
  83. package/src/Inputs/IMouseEvents.ts +0 -64
  84. package/src/Inputs/ITouchController.ts +0 -29
  85. package/src/Inputs/LockedMouseEvents.ts +0 -287
  86. package/src/Util/CoordinateConverter.ts +0 -290
  87. package/src/Util/EventListenerTracker.ts +0 -29
  88. package/types/Inputs/FakeTouchController.d.ts +0 -61
  89. package/types/Inputs/HoveringMouseEvents.d.ts +0 -56
  90. package/types/Inputs/IMouseEvents.d.ts +0 -53
  91. package/types/Inputs/ITouchController.d.ts +0 -24
  92. package/types/Inputs/LockedMouseEvents.d.ts +0 -80
  93. package/types/Util/CoordinateConverter.d.ts +0 -100
  94. package/types/Util/EventListenerTracker.d.ts +0 -14
@@ -23,11 +23,7 @@ export class PeerConnectionController {
23
23
  * @param options - Peer connection Options
24
24
  * @param config - The config for our PS experience.
25
25
  */
26
- constructor(
27
- options: RTCConfiguration,
28
- config: Config,
29
- preferredCodec: string
30
- ) {
26
+ constructor(options: RTCConfiguration, config: Config, preferredCodec: string) {
31
27
  this.config = config;
32
28
  this.createPeerConnection(options, preferredCodec);
33
29
  }
@@ -36,26 +32,18 @@ export class PeerConnectionController {
36
32
  // Set the ICE transport to relay if TURN enabled
37
33
  if (this.config.isFlagEnabled(Flags.ForceTURN)) {
38
34
  options.iceTransportPolicy = 'relay';
39
- Logger.Log(
40
- Logger.GetStackTrace(),
41
- 'Forcing TURN usage by setting ICE Transport Policy in peer connection config.'
42
- );
35
+ Logger.Info('Forcing TURN usage by setting ICE Transport Policy in peer connection config.');
43
36
  }
44
37
 
45
38
  // build a new peer connection with the options
46
39
  this.peerConnection = new RTCPeerConnection(options);
47
- this.peerConnection.onsignalingstatechange = (ev: Event) =>
48
- this.handleSignalStateChange(ev);
40
+ this.peerConnection.onsignalingstatechange = (ev: Event) => this.handleSignalStateChange(ev);
49
41
  this.peerConnection.oniceconnectionstatechange = (ev: Event) =>
50
42
  this.handleIceConnectionStateChange(ev);
51
- this.peerConnection.onicegatheringstatechange = (ev: Event) =>
52
- this.handleIceGatheringStateChange(ev);
53
- this.peerConnection.ontrack = (ev: RTCTrackEvent) =>
54
- this.handleOnTrack(ev);
55
- this.peerConnection.onicecandidate = (ev: RTCPeerConnectionIceEvent) =>
56
- this.handleIceCandidate(ev);
57
- this.peerConnection.ondatachannel = (ev: RTCDataChannelEvent) =>
58
- this.handleDataChannel(ev);
43
+ this.peerConnection.onicegatheringstatechange = (ev: Event) => this.handleIceGatheringStateChange(ev);
44
+ this.peerConnection.ontrack = (ev: RTCTrackEvent) => this.handleOnTrack(ev);
45
+ this.peerConnection.onicecandidate = (ev: RTCPeerConnectionIceEvent) => this.handleIceCandidate(ev);
46
+ this.peerConnection.ondatachannel = (ev: RTCDataChannelEvent) => this.handleDataChannel(ev);
59
47
  this.aggregatedStats = new AggregatedStats();
60
48
  this.preferredCodec = preferredCodec;
61
49
  this.updateCodecSelection = true;
@@ -66,26 +54,24 @@ export class PeerConnectionController {
66
54
  * @param offerOptions - RTC Offer Options
67
55
  */
68
56
  async createOffer(offerOptions: RTCOfferOptions, config: Config) {
69
- Logger.Log(Logger.GetStackTrace(), 'Create Offer', 6);
57
+ Logger.Info('Create Offer');
70
58
 
71
- const isLocalhostConnection =
72
- location.hostname === 'localhost' ||
73
- location.hostname === '127.0.0.1';
59
+ const isLocalhostConnection = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
74
60
  const isHttpsConnection = location.protocol === 'https:';
75
61
  let useMic = config.isFlagEnabled(Flags.UseMic);
76
- if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
62
+ let useCamera = config.isFlagEnabled(Flags.UseCamera);
63
+ if ((useMic || useCamera) && !(isLocalhostConnection || isHttpsConnection)) {
77
64
  useMic = false;
65
+ useCamera = false;
78
66
  Logger.Error(
79
- Logger.GetStackTrace(),
80
- 'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
67
+ 'Microphone and Webcam access in the browser will not work if you are not on HTTPS or localhost. Disabling mic and webcam access.'
81
68
  );
82
69
  Logger.Error(
83
- Logger.GetStackTrace(),
84
70
  "For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
85
71
  );
86
72
  }
87
73
 
88
- this.setupTransceiversAsync(useMic).finally(() => {
74
+ this.setupTransceiversAsync(useMic, useCamera).finally(() => {
89
75
  this.peerConnection
90
76
  ?.createOffer(offerOptions)
91
77
  .then((offer: RTCSessionDescriptionInit) => {
@@ -104,27 +90,32 @@ export class PeerConnectionController {
104
90
  *
105
91
  */
106
92
  async receiveOffer(offer: RTCSessionDescriptionInit, config: Config) {
107
- Logger.Log(Logger.GetStackTrace(), 'Receive Offer', 6);
93
+ Logger.Info('Receive Offer');
108
94
 
109
95
  this.peerConnection?.setRemoteDescription(offer).then(() => {
110
96
  const isLocalhostConnection =
111
- location.hostname === 'localhost' ||
112
- location.hostname === '127.0.0.1';
97
+ location.hostname === 'localhost' || location.hostname === '127.0.0.1';
113
98
  const isHttpsConnection = location.protocol === 'https:';
114
99
  let useMic = config.isFlagEnabled(Flags.UseMic);
115
- if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
100
+ let useCamera = config.isFlagEnabled(Flags.UseCamera);
101
+ if ((useMic || useCamera) && !(isLocalhostConnection || isHttpsConnection)) {
116
102
  useMic = false;
103
+ useCamera = false;
117
104
  Logger.Error(
118
- Logger.GetStackTrace(),
119
- 'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
105
+ 'Microphone and Webcam access in the browser will not work if you are not on HTTPS or localhost. Disabling mic and webcam access.'
120
106
  );
121
107
  Logger.Error(
122
- Logger.GetStackTrace(),
123
108
  "For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
124
109
  );
125
110
  }
126
111
 
127
- this.setupTransceiversAsync(useMic).finally(() => {
112
+ // Add our list of preferred codecs, in order of preference
113
+ this.config.setOptionSettingOptions(
114
+ OptionParameters.PreferredCodec,
115
+ this.fuzzyIntersectUEAndBrowserCodecs(offer)
116
+ );
117
+
118
+ this.setupTransceiversAsync(useMic, useCamera).finally(() => {
128
119
  this.peerConnection
129
120
  ?.createAnswer()
130
121
  .then((Answer: RTCSessionDescriptionInit) => {
@@ -132,28 +123,13 @@ export class PeerConnectionController {
132
123
  return this.peerConnection?.setLocalDescription(Answer);
133
124
  })
134
125
  .then(() => {
135
- this.onSendWebRTCAnswer(
136
- this.peerConnection?.currentLocalDescription
137
- );
126
+ this.onSendWebRTCAnswer(this.peerConnection?.currentLocalDescription);
138
127
  })
139
128
  .catch(() => {
140
- Logger.Error(
141
- Logger.GetStackTrace(),
142
- 'createAnswer() failed'
143
- );
129
+ Logger.Error('createAnswer() failed');
144
130
  });
145
131
  });
146
132
  });
147
-
148
- // Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
149
- this.config.setOptionSettingOptions(
150
- OptionParameters.PreferredCodec,
151
- this.parseAvailableCodecs(offer).filter((value) =>
152
- this.config
153
- .getSettingOption(OptionParameters.PreferredCodec)
154
- .options.includes(value)
155
- )
156
- );
157
133
  }
158
134
 
159
135
  /**
@@ -162,14 +138,11 @@ export class PeerConnectionController {
162
138
  */
163
139
  receiveAnswer(answer: RTCSessionDescriptionInit) {
164
140
  this.peerConnection?.setRemoteDescription(answer);
165
- // Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
141
+
142
+ // Add our list of preferred codecs, in order of preference
166
143
  this.config.setOptionSettingOptions(
167
144
  OptionParameters.PreferredCodec,
168
- this.parseAvailableCodecs(answer).filter((value) =>
169
- this.config
170
- .getSettingOption(OptionParameters.PreferredCodec)
171
- .options.includes(value)
172
- )
145
+ this.fuzzyIntersectUEAndBrowserCodecs(answer)
173
146
  );
174
147
  }
175
148
 
@@ -190,9 +163,7 @@ export class PeerConnectionController {
190
163
  if (this.updateCodecSelection && !!this.aggregatedStats.inboundVideoStats.codecId) {
191
164
  this.config.setOptionSettingValue(
192
165
  OptionParameters.PreferredCodec,
193
- this.aggregatedStats.codecs.get(
194
- this.aggregatedStats.inboundVideoStats.codecId
195
- )
166
+ this.aggregatedStats.codecs.get(this.aggregatedStats.inboundVideoStats.codecId)
196
167
  );
197
168
  }
198
169
  });
@@ -229,9 +200,7 @@ export class PeerConnectionController {
229
200
  }
230
201
 
231
202
  // Force mono or stereo based on whether ?forceMono was passed or not
232
- audioSDP += this.config.isFlagEnabled(Flags.ForceMonoAudio)
233
- ? 'stereo=0;'
234
- : 'stereo=1;';
203
+ audioSDP += this.config.isFlagEnabled(Flags.ForceMonoAudio) ? 'stereo=0;' : 'stereo=1;';
235
204
 
236
205
  // enable in-band forward error correction for opus audio
237
206
  audioSDP += 'useinbandfec=1';
@@ -247,16 +216,14 @@ export class PeerConnectionController {
247
216
  * @param iceCandidate - RTC Ice Candidate from the Signaling Server
248
217
  */
249
218
  handleOnIce(iceCandidate: RTCIceCandidate) {
250
- Logger.Log(Logger.GetStackTrace(), 'peerconnection handleOnIce', 6);
219
+ Logger.Info('peerconnection handleOnIce');
251
220
 
252
221
  // // if forcing TURN, reject any candidates not relay
253
222
  if (this.config.isFlagEnabled(Flags.ForceTURN)) {
254
223
  // check if no relay address is found, if so, we are assuming it means no TURN server
255
224
  if (iceCandidate.candidate.indexOf('relay') < 0) {
256
225
  Logger.Info(
257
- Logger.GetStackTrace(),
258
- `Dropping candidate because it was not TURN relay. | Type= ${iceCandidate.type} | Protocol= ${iceCandidate.protocol} | Address=${iceCandidate.address} | Port=${iceCandidate.port} |`,
259
- 6
226
+ `Dropping candidate because it was not TURN relay. | Type= ${iceCandidate.type} | Protocol= ${iceCandidate.protocol} | Address=${iceCandidate.address} | Port=${iceCandidate.port} |`
260
227
  );
261
228
  return;
262
229
  }
@@ -270,11 +237,7 @@ export class PeerConnectionController {
270
237
  * @param state - Signaling Server State Change Event
271
238
  */
272
239
  handleSignalStateChange(state: Event) {
273
- Logger.Log(
274
- Logger.GetStackTrace(),
275
- 'signaling state change: ' + state,
276
- 6
277
- );
240
+ Logger.Info('signaling state change: ' + state);
278
241
  }
279
242
 
280
243
  /**
@@ -282,11 +245,7 @@ export class PeerConnectionController {
282
245
  * @param state - Ice Connection State
283
246
  */
284
247
  handleIceConnectionStateChange(state: Event) {
285
- Logger.Log(
286
- Logger.GetStackTrace(),
287
- 'ice connection state change: ' + state,
288
- 6
289
- );
248
+ Logger.Info('ice connection state change: ' + state);
290
249
  this.onIceConnectionStateChange(state);
291
250
  }
292
251
 
@@ -295,11 +254,7 @@ export class PeerConnectionController {
295
254
  * @param state - Ice Gathering State Change
296
255
  */
297
256
  handleIceGatheringStateChange(state: Event) {
298
- Logger.Log(
299
- Logger.GetStackTrace(),
300
- 'ice gathering state change: ' + JSON.stringify(state),
301
- 6
302
- );
257
+ Logger.Info('ice gathering state change: ' + JSON.stringify(state));
303
258
  }
304
259
 
305
260
  /**
@@ -371,18 +326,68 @@ export class PeerConnectionController {
371
326
  // Default Functionality: Do Nothing
372
327
  }
373
328
 
329
+ /**
330
+ * Find the intersection between UE and browser codecs, with fuzzy matching if some parameters are mismatched.
331
+ * @param sdp The remote sdp
332
+ * @returns The intersection between browser supported codecs and ue supported codecs.
333
+ */
334
+ fuzzyIntersectUEAndBrowserCodecs(sdp: RTCSessionDescriptionInit): string[] {
335
+ // We want to build an array of all supported codecs on both sides
336
+ const allSupportedCodecs: Array<string> = new Array<string>();
337
+ const allUECodecs: string[] = this.parseAvailableCodecs(sdp);
338
+ const allBrowserCodecs: string[] = this.config.getSettingOption(
339
+ OptionParameters.PreferredCodec
340
+ ).options;
341
+ for (const ueCodec of allUECodecs) {
342
+ // Check if browser codecs directly matches UE codec (with parameters and everything)
343
+ if (allBrowserCodecs.includes(ueCodec)) {
344
+ allSupportedCodecs.push(ueCodec);
345
+ continue;
346
+ }
347
+ // Otherwise check if browser codec at least contains a match for the UE codec name (without parameters).
348
+ else {
349
+ const ueCodecNameAndParams: string[] = ueCodec.split(' ');
350
+ const ueCodecName = ueCodecNameAndParams[0];
351
+ for (const browserCodec of allBrowserCodecs) {
352
+ if (browserCodec.includes(ueCodecName)) {
353
+ // We pass browser codec here as they option contain extra parameters.
354
+ allSupportedCodecs.push(browserCodec);
355
+ break;
356
+ }
357
+ }
358
+ }
359
+ }
360
+ return allSupportedCodecs;
361
+ }
362
+
374
363
  /**
375
364
  * Setup tracks on the RTC Peer Connection
376
365
  * @param useMic - is mic in use
366
+ * @param useCamera - is webcam in use
377
367
  */
378
- async setupTransceiversAsync(useMic: boolean) {
379
- const hasTransceivers =
380
- this.peerConnection?.getTransceivers().length > 0;
368
+ async setupTransceiversAsync(useMic: boolean, useCamera: boolean) {
369
+ let hasVideoReceiver = false;
370
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
371
+ if (
372
+ transceiver &&
373
+ transceiver.receiver &&
374
+ transceiver.receiver.track &&
375
+ transceiver.receiver.track.kind === 'video'
376
+ ) {
377
+ hasVideoReceiver = true;
378
+ break;
379
+ }
380
+ }
381
381
 
382
- // Setup a transceiver for getting UE video
383
- this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
382
+ // Setup a transceiver for sending webcam video to UE and receiving video from UE
383
+ if (!useCamera) {
384
+ if (!hasVideoReceiver) {
385
+ this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
386
+ }
387
+ } else {
388
+ await this.setupVideoSender(hasVideoReceiver);
389
+ }
384
390
 
385
- // We can only set preferred codec on Chrome
386
391
  if (RTCRtpReceiver.getCapabilities && this.preferredCodec != '') {
387
392
  for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
388
393
  if (
@@ -390,101 +395,152 @@ export class PeerConnectionController {
390
395
  transceiver.receiver &&
391
396
  transceiver.receiver.track &&
392
397
  transceiver.receiver.track.kind === 'video' &&
393
- // As of 06/2023, FireFox has added RTCRtpReceiver.getCapabilities, but hasn't added the ability to set codec preferences
394
398
  transceiver.setCodecPreferences
395
399
  ) {
400
+ // Get our preferred codec from the codecs options drop down
396
401
  const preferredRTPCodec = this.preferredCodec.split(' ');
397
- const codecs = [
398
- {
399
- mimeType:
400
- 'video/' + preferredRTPCodec[0] /* Name */,
401
- clockRate: 90000,
402
- sdpFmtpLine: preferredRTPCodec[1] /* sdpFmtpLine */
403
- ? preferredRTPCodec[1]
404
- : ''
402
+ const preferredRTCRtpCodecCapability: RTCRtpCodecCapability = {
403
+ mimeType: 'video/' + preferredRTPCodec[0] /* Name */,
404
+ clockRate: 90000 /* All current video formats in browsers have 90khz clock rate */,
405
+ sdpFmtpLine: preferredRTPCodec[1] ? preferredRTPCodec[1] : ''
406
+ };
407
+
408
+ // Populate a list of codecs we will support with our preferred one in the first position
409
+ const ourSupportedCodecs: Array<RTCRtpCodecCapability> = [preferredRTCRtpCodecCapability];
410
+
411
+ // Go through all codecs the browser supports and add them to the list (in any order)
412
+ RTCRtpReceiver.getCapabilities('video').codecs.forEach(
413
+ (browserSupportedCodec: RTCRtpCodecCapability) => {
414
+ // Don't add our preferred codec again, but add everything else
415
+ if (browserSupportedCodec.mimeType != preferredRTCRtpCodecCapability.mimeType) {
416
+ ourSupportedCodecs.push(browserSupportedCodec);
417
+ } else if (
418
+ browserSupportedCodec?.sdpFmtpLine !=
419
+ preferredRTCRtpCodecCapability?.sdpFmtpLine
420
+ ) {
421
+ ourSupportedCodecs.push(browserSupportedCodec);
422
+ }
405
423
  }
406
- ];
407
-
408
- this.config
409
- .getSettingOption(OptionParameters.PreferredCodec)
410
- .options.filter((option) => {
411
- // Remove the preferred codec from the list of possible codecs as we've set it already
412
- return option != this.preferredCodec;
413
- })
414
- .forEach((option) => {
415
- // Amend the rest of the browsers supported codecs
416
- const altCodec = option.split(' ');
417
- codecs.push({
418
- mimeType: 'video/' + altCodec[0] /* Name */,
419
- clockRate: 90000,
420
- sdpFmtpLine: altCodec[1] /* sdpFmtpLine */
421
- ? altCodec[1]
422
- : ''
423
- });
424
- });
424
+ );
425
425
 
426
- for (const codec of codecs) {
427
- if (codec.sdpFmtpLine === '') {
426
+ for (const codec of ourSupportedCodecs) {
427
+ if (codec?.sdpFmtpLine === undefined || codec.sdpFmtpLine === '') {
428
428
  // We can't dynamically add members to the codec, so instead remove the field if it's empty
429
429
  delete codec.sdpFmtpLine;
430
430
  }
431
431
  }
432
432
 
433
- transceiver.setCodecPreferences(codecs);
433
+ transceiver.setCodecPreferences(ourSupportedCodecs);
434
434
  }
435
435
  }
436
436
  }
437
437
 
438
+ let hasAudioReceiver = false;
439
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
440
+ if (
441
+ transceiver &&
442
+ transceiver.receiver &&
443
+ transceiver.receiver.track &&
444
+ transceiver.receiver.track.kind === 'audio'
445
+ ) {
446
+ hasAudioReceiver = true;
447
+ break;
448
+ }
449
+ }
450
+
438
451
  // Setup a transceiver for sending mic audio to UE and receiving audio from UE
439
452
  if (!useMic) {
440
- this.peerConnection?.addTransceiver('audio', {
441
- direction: 'recvonly'
442
- });
443
- } else {
444
- // set the audio options based on mic usage
445
- const audioOptions = {
446
- autoGainControl: false,
447
- channelCount: 1,
448
- echoCancellation: false,
449
- latency: 0,
450
- noiseSuppression: false,
451
- sampleRate: 48000,
452
- sampleSize: 16,
453
- volume: 1.0
453
+ if (!hasAudioReceiver) {
454
+ this.peerConnection?.addTransceiver('audio', {
455
+ direction: 'recvonly'
456
+ });
454
457
  }
458
+ } else {
459
+ await this.setupAudioSender(hasAudioReceiver);
460
+ }
461
+ }
455
462
 
456
- // set the media send options
457
- const mediaSendOptions: MediaStreamConstraints = {
458
- video: false,
459
- audio: audioOptions
460
- };
463
+ async setupVideoSender(hasVideoReceiver: boolean) {
464
+ // set the media send options
465
+ const mediaSendOptions: MediaStreamConstraints = {
466
+ video: true
467
+ };
461
468
 
462
- // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
463
- const stream = await navigator.mediaDevices.getUserMedia(
464
- mediaSendOptions
465
- );
466
- if (stream) {
467
- if (hasTransceivers) {
468
- for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
469
- if (RTCUtils.canTransceiverReceiveAudio(transceiver)) {
470
- for (const track of stream.getTracks()) {
471
- if (track.kind && track.kind == 'audio') {
472
- transceiver.sender.replaceTrack(track);
473
- transceiver.direction = 'sendrecv';
474
- }
469
+ // Note using webcam on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
470
+ const stream = await navigator.mediaDevices.getUserMedia(mediaSendOptions);
471
+
472
+ if (stream) {
473
+ if (hasVideoReceiver) {
474
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
475
+ if (RTCUtils.canTransceiverReceiveVideo(transceiver)) {
476
+ for (const track of stream.getTracks()) {
477
+ if (track.kind && track.kind == 'video') {
478
+ transceiver.sender.replaceTrack(track);
479
+ transceiver.direction = 'sendrecv';
475
480
  }
476
481
  }
477
482
  }
478
- } else {
479
- for (const track of stream.getTracks()) {
480
- if (track.kind && track.kind == 'audio') {
481
- this.peerConnection?.addTransceiver(track, {
482
- direction: 'sendrecv'
483
- });
483
+ }
484
+ } else {
485
+ for (const track of stream.getTracks()) {
486
+ if (track.kind && track.kind == 'video') {
487
+ this.peerConnection?.addTransceiver(track, {
488
+ direction: 'sendrecv'
489
+ });
490
+ }
491
+ }
492
+ }
493
+ } else {
494
+ if (!hasVideoReceiver) {
495
+ this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
496
+ }
497
+ }
498
+ }
499
+
500
+ async setupAudioSender(hasAudioReceiver: boolean) {
501
+ // set the audio options based on mic usage
502
+ const audioOptions = {
503
+ autoGainControl: false,
504
+ channelCount: 1,
505
+ echoCancellation: false,
506
+ latency: 0,
507
+ noiseSuppression: false,
508
+ sampleRate: 48000,
509
+ sampleSize: 16,
510
+ volume: 1.0
511
+ };
512
+
513
+ // set the media send options
514
+ const mediaSendOptions: MediaStreamConstraints = {
515
+ video: false,
516
+ audio: audioOptions
517
+ };
518
+
519
+ // Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
520
+ const stream = await navigator.mediaDevices.getUserMedia(mediaSendOptions);
521
+ if (stream) {
522
+ if (hasAudioReceiver) {
523
+ for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
524
+ if (RTCUtils.canTransceiverReceiveAudio(transceiver)) {
525
+ for (const track of stream.getTracks()) {
526
+ if (track.kind && track.kind == 'audio') {
527
+ transceiver.sender.replaceTrack(track);
528
+ transceiver.direction = 'sendrecv';
529
+ }
484
530
  }
485
531
  }
486
532
  }
487
533
  } else {
534
+ for (const track of stream.getTracks()) {
535
+ if (track.kind && track.kind == 'audio') {
536
+ this.peerConnection?.addTransceiver(track, {
537
+ direction: 'sendrecv'
538
+ });
539
+ }
540
+ }
541
+ }
542
+ } else {
543
+ if (!hasAudioReceiver) {
488
544
  this.peerConnection?.addTransceiver('audio', {
489
545
  direction: 'recvonly'
490
546
  });
@@ -533,12 +589,9 @@ export class PeerConnectionController {
533
589
  // Default Functionality: Do Nothing
534
590
  }
535
591
 
536
- parseAvailableCodecs(
537
- rtcSessionDescription: RTCSessionDescriptionInit
538
- ): Array<string> {
592
+ parseAvailableCodecs(rtcSessionDescription: RTCSessionDescriptionInit): Array<string> {
539
593
  // No point in updating the available codecs if on FF
540
- if (!RTCRtpReceiver.getCapabilities)
541
- return ['Only available on Chrome'];
594
+ if (!RTCRtpReceiver.getCapabilities) return ['Only available on Chrome'];
542
595
 
543
596
  const ueSupportedCodecs: Array<string> = [];
544
597
  const sections = splitSections(rtcSessionDescription.sdp);