@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.11 → 0.1.0

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 (38) hide show
  1. package/.cspell.json +35 -0
  2. package/dist/lib-pixelstreamingfrontend.esm.js +1 -1
  3. package/dist/lib-pixelstreamingfrontend.js +1 -1
  4. package/package.json +3 -2
  5. package/readme.md +1 -1
  6. package/src/Config/Config.ts +1 -1
  7. package/src/Inputs/FakeTouchController.ts +1 -1
  8. package/src/Inputs/GamepadController.ts +2 -2
  9. package/src/Inputs/IMouseEvents.ts +1 -1
  10. package/src/Inputs/ITouchController.ts +1 -1
  11. package/src/Inputs/KeyboardController.ts +2 -2
  12. package/src/Inputs/LockedMouseEvents.ts +2 -2
  13. package/src/Inputs/MouseController.ts +1 -1
  14. package/src/Inputs/TouchController.ts +1 -1
  15. package/src/Inputs/XRGamepadController.ts +44 -26
  16. package/src/PeerConnectionController/PeerConnectionController.ts +20 -4
  17. package/src/PixelStreaming/PixelStreaming.test.ts +34 -51
  18. package/src/PixelStreaming/PixelStreaming.ts +5 -5
  19. package/src/Util/CoordinateConverter.ts +6 -5
  20. package/src/Util/RTCUtils.ts +2 -2
  21. package/src/VideoPlayer/StreamController.ts +6 -0
  22. package/src/WebRtcPlayer/WebRtcPlayerController.ts +21 -2
  23. package/src/WebXR/WebXRController.ts +368 -179
  24. package/src/__test__/mockWebSocket.ts +5 -5
  25. package/types/Inputs/GamepadController.d.ts +1 -1
  26. package/types/Inputs/IMouseEvents.d.ts +1 -1
  27. package/types/Inputs/ITouchController.d.ts +1 -1
  28. package/types/Inputs/KeyboardController.d.ts +1 -1
  29. package/types/Inputs/LockedMouseEvents.d.ts +1 -1
  30. package/types/Inputs/XRGamepadController.d.ts +8 -1
  31. package/types/PeerConnectionController/PeerConnectionController.d.ts +2 -0
  32. package/types/PixelStreaming/PixelStreaming.d.ts +1 -1
  33. package/types/Util/RTCUtils.d.ts +2 -2
  34. package/types/WebXR/WebXRController.d.ts +19 -3
  35. package/src/Util/WebGLUtils.ts +0 -49
  36. package/src/Util/WebXRUtils.ts +0 -25
  37. package/types/Util/WebGLUtils.d.ts +0 -4
  38. package/types/Util/WebXRUtils.d.ts +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.5",
3
- "version": "0.0.11",
3
+ "version": "0.1.0",
4
4
  "description": "Frontend library for Unreal Engine 5.5 Pixel Streaming",
5
5
  "main": "dist/lib-pixelstreamingfrontend.js",
6
6
  "module": "dist/lib-pixelstreamingfrontend.esm.js",
@@ -16,7 +16,7 @@
16
16
  "spellcheck": "cspell \"{README.md,.github/*.md,src/**/*.ts}\""
17
17
  },
18
18
  "devDependencies": {
19
- "@epicgames-ps/lib-pixelstreamingcommon-ue5.5": "^0.0.13",
19
+ "@epicgames-ps/lib-pixelstreamingcommon-ue5.5": "^0.0.14",
20
20
  "@types/jest": "27.5.1",
21
21
  "@types/webxr": "^0.5.1",
22
22
  "@typescript-eslint/eslint-plugin": "^6.21.0",
@@ -45,3 +45,4 @@
45
45
  "access": "public"
46
46
  }
47
47
  }
48
+
package/readme.md CHANGED
@@ -8,7 +8,7 @@ See [lib-pixelstreamingfrontend-ui](/Frontend/implementations/typescript) for an
8
8
  - Create a websocket connection to communicate with the signalling server.
9
9
  - Create a WebRTC player that displays the Unreal Engine video and audio.
10
10
  - Handling of input from the user and transmitting it back to Unreal Engine.
11
- - Opens a datachannel connection sending and receiving custom data (in addition to input).
11
+ - Opens a data channel connection sending and receiving custom data (in addition to input).
12
12
 
13
13
  ### Adding it to your project
14
14
  `npm i @epicgames-ps/lib-pixelstreamingfrontend-ue5.4`
@@ -508,7 +508,7 @@ export class Config {
508
508
  Flags.HideUI,
509
509
  'Hide the UI overlay',
510
510
  'Will hide all UI overlay details',
511
- settings && settings.hasOwnProperty(Flags.HideUI) ?
511
+ settings && Object.prototype.hasOwnProperty.call(settings, Flags.HideUI) ?
512
512
  settings[Flags.HideUI] :
513
513
  false,
514
514
  useUrlParams
@@ -19,7 +19,7 @@ export class FakeTouchController implements ITouchController {
19
19
  coordinateConverter: CoordinateConverter;
20
20
  videoElementParentClientRect: DOMRect;
21
21
 
22
- // Utility for keeping track of event handlers and unregistering them
22
+ // Utility for keeping track of event handlers and to unregister them.
23
23
  private touchEventListenerTracker = new EventListenerTracker();
24
24
 
25
25
  /**
@@ -13,7 +13,7 @@ export class GamePadController {
13
13
  requestAnimationFrame: (callback: FrameRequestCallback) => number;
14
14
  toStreamerMessagesProvider: StreamMessageController;
15
15
 
16
- // Utility for keeping track of event handlers and unregistering them
16
+ // Utility for keeping track of event handlers and to unregister them.
17
17
  private gamePadEventListenerTracker = new EventListenerTracker();
18
18
 
19
19
  /**
@@ -69,7 +69,7 @@ export class GamePadController {
69
69
  }
70
70
 
71
71
  /**
72
- * Unregisters all event handlers
72
+ * Unregister all event handlers.
73
73
  */
74
74
  unregisterGamePadEvents() {
75
75
  this.gamePadEventListenerTracker.unregisterAll();
@@ -58,7 +58,7 @@ export interface IMouseEvents {
58
58
  handleContextMenu?(mouseEvent: MouseEvent): void;
59
59
 
60
60
  /**
61
- * Unregisters any registered mouse event handlers
61
+ * Unregister any registered mouse event handlers
62
62
  */
63
63
  unregisterMouseEvents(): void;
64
64
  }
@@ -23,7 +23,7 @@ export interface ITouchController {
23
23
  onTouchMove(touchEvent: TouchEvent): void;
24
24
 
25
25
  /**
26
- * Unregisters all touch event handlers
26
+ * Unregister all touch event handlers.
27
27
  */
28
28
  unregisterTouchEvents(): void;
29
29
  }
@@ -19,7 +19,7 @@ export class KeyboardController {
19
19
  activeKeysProvider: ActiveKeys;
20
20
  config: Config;
21
21
 
22
- // Utility for keeping track of event handlers and unregistering them
22
+ // Utility for keeping track of event handlers and to unregister them.
23
23
  private keyboardEventListenerTracker = new EventListenerTracker();
24
24
 
25
25
  /*
@@ -177,7 +177,7 @@ export class KeyboardController {
177
177
  }
178
178
 
179
179
  /**
180
- * Unregisters document keyboard events
180
+ * Unregister document keyboard events.
181
181
  */
182
182
  unregisterKeyBoardEvents() {
183
183
  this.keyboardEventListenerTracker.unregisterAll();
@@ -22,7 +22,7 @@ export class LockedMouseEvents implements IMouseEvents {
22
22
  this.updateMouseMovePosition(mouseEvent);
23
23
  };
24
24
 
25
- // Utility for keeping track of event handlers and unregistering them
25
+ // Utility for keeping track of event handlers and to unregister them.
26
26
  private mouseEventListenerTracker = new EventListenerTracker();
27
27
 
28
28
  /**
@@ -51,7 +51,7 @@ export class LockedMouseEvents implements IMouseEvents {
51
51
  }
52
52
 
53
53
  /**
54
- * Unregisters all event handlers
54
+ * Unregister all event handlers.
55
55
  */
56
56
  unregisterMouseEvents() {
57
57
  this.mouseEventListenerTracker.unregisterAll();
@@ -20,7 +20,7 @@ export class MouseController {
20
20
  coordinateConverter: CoordinateConverter;
21
21
  activeKeysProvider: ActiveKeys;
22
22
 
23
- // Utility for keeping track of event handlers and unregistering them
23
+ // Utility for keeping track of event handlers and to unregister them.
24
24
  private mouseEventListenerTracker = new EventListenerTracker();
25
25
 
26
26
  /**
@@ -18,7 +18,7 @@ export class TouchController implements ITouchController {
18
18
  fingerIds = new Map();
19
19
  maxByteValue = 255;
20
20
 
21
- // Utility for keeping track of event handlers and unregistering them
21
+ // Utility for keeping track of event handlers and to unregister them.
22
22
  private touchEventListenerTracker = new EventListenerTracker();
23
23
 
24
24
  /**
@@ -2,10 +2,9 @@
2
2
 
3
3
  import { StreamMessageController } from '../UeInstanceMessage/StreamMessageController';
4
4
  import { Controller } from './GamepadTypes';
5
- import { WebXRUtils } from '../Util/WebXRUtils';
6
5
 
7
6
  /**
8
- * The class that handles the functionality of xrgamepads and controllers
7
+ * The class that handles the functionality of XR gamepads and controllers.
9
8
  */
10
9
  export class XRGamepadController {
11
10
  controllers: Array<Controller>;
@@ -19,6 +18,29 @@ export class XRGamepadController {
19
18
  this.controllers = [];
20
19
  }
21
20
 
21
+ /**
22
+ * Deep copies the values from a gamepad by first converting it to a JSON object and then back to a gamepad
23
+ *
24
+ * @param gamepad the original gamepad
25
+ * @returns a new gamepad object, populated with the original gamepads values
26
+ */
27
+ static deepCopyGamepad(gamepad: Gamepad): Gamepad {
28
+ return JSON.parse(
29
+ JSON.stringify({
30
+ buttons: gamepad.buttons.map((b) =>
31
+ JSON.parse(
32
+ JSON.stringify({
33
+ pressed: b.pressed,
34
+ touched: b.touched,
35
+ value: b.value
36
+ })
37
+ )
38
+ ),
39
+ axes: gamepad.axes
40
+ })
41
+ );
42
+ }
43
+
22
44
  updateStatus(
23
45
  source: XRInputSource,
24
46
  frame: XRFrame,
@@ -75,12 +97,10 @@ export class XRGamepadController {
75
97
  currentState: undefined,
76
98
  id: undefined
77
99
  };
78
- this.controllers[handedness].prevState =
79
- WebXRUtils.deepCopyGamepad(source.gamepad);
100
+ this.controllers[handedness].prevState = XRGamepadController.deepCopyGamepad(source.gamepad);
80
101
  }
81
102
 
82
- this.controllers[handedness].currentState =
83
- WebXRUtils.deepCopyGamepad(source.gamepad);
103
+ this.controllers[handedness].currentState = XRGamepadController.deepCopyGamepad(source.gamepad);
84
104
 
85
105
  const controller = this.controllers[handedness];
86
106
  const currState = controller.currentState;
@@ -92,35 +112,33 @@ export class XRGamepadController {
92
112
 
93
113
  if (currButton.pressed) {
94
114
  // press
95
- this.toStreamerMessagesProvider.toStreamerHandlers.get(
96
- 'XRButtonPressed'
97
- )([handedness, i, prevButton.pressed ? 1 : 0]);
98
- } else if (!currButton.pressed && prevButton.pressed) {
99
- this.toStreamerMessagesProvider.toStreamerHandlers.get(
100
- 'XRButtonReleased'
101
- )([handedness, i, 0]);
115
+ const isRepeat = prevButton.pressed ? 1 : 0;
116
+ this.toStreamerMessagesProvider.toStreamerHandlers.get('XRButtonPressed')([handedness, i, isRepeat, currButton.value]);
117
+ } else if (prevButton.pressed) {
118
+ this.toStreamerMessagesProvider.toStreamerHandlers.get('XRButtonReleased')([handedness, i, 0]);
102
119
  }
103
120
 
104
- if (currButton.touched && !currButton.pressed) {
105
- // press
106
- this.toStreamerMessagesProvider.toStreamerHandlers.get(
107
- 'XRButtonPressed'
108
- )([handedness, 3, prevButton.touched ? 1 : 0]);
109
- } else if (!currButton.touched && prevButton.touched) {
110
- this.toStreamerMessagesProvider.toStreamerHandlers.get(
111
- 'XRButtonReleased'
112
- )([handedness, 3, 0]);
121
+ if (currButton.touched) {
122
+ // touched
123
+ const isRepeat = prevButton.touched ? 1 : 0;
124
+ this.toStreamerMessagesProvider.toStreamerHandlers.get('XRButtonTouched')([handedness, i, isRepeat]);
125
+ }
126
+ else if (prevButton.touched) {
127
+ this.toStreamerMessagesProvider.toStreamerHandlers.get('XRButtonTouchReleased')([handedness, i, 0]);
113
128
  }
114
129
  }
115
130
 
116
131
  // Iterate over gamepad axes
117
132
  for (let i = 0; i < currState.axes.length; i++) {
118
- this.toStreamerMessagesProvider.toStreamerHandlers.get(
119
- 'XRAnalog'
120
- )([handedness, i, currState.axes[i]]);
133
+ const curAxisValue = currState.axes[i];
134
+ const prevAxisValue = prevState.axes[i];
135
+ // Only send axis update if there is a change
136
+ if(curAxisValue != prevAxisValue) {
137
+ this.toStreamerMessagesProvider.toStreamerHandlers.get('XRAnalog')([handedness, i, curAxisValue]);
138
+ }
121
139
  }
122
140
 
123
141
  this.controllers[handedness].prevState = currState;
124
142
  }
125
143
  }
126
- }
144
+ }
@@ -15,6 +15,8 @@ export class PeerConnectionController {
15
15
  config: Config;
16
16
  preferredCodec: string;
17
17
  updateCodecSelection: boolean;
18
+ videoTrack: MediaStreamTrack;
19
+ audioTrack: MediaStreamTrack;
18
20
 
19
21
  /**
20
22
  * Create a new RTC Peer Connection client
@@ -175,10 +177,15 @@ export class PeerConnectionController {
175
177
  * Generate Aggregated Stats and then fire a onVideo Stats event
176
178
  */
177
179
  generateStats() {
178
- this.peerConnection?.getStats(null).then((StatsData: RTCStatsReport) => {
180
+ const statsHandler = (StatsData: RTCStatsReport) => {
179
181
  this.aggregatedStats.processStats(StatsData);
180
- this.onVideoStats(this.aggregatedStats);
182
+ };
183
+
184
+ const audioPromise = this.peerConnection?.getStats(this.audioTrack).then(statsHandler);
185
+ const videoPromise = this.peerConnection?.getStats(this.videoTrack).then(statsHandler);
181
186
 
187
+ Promise.allSettled([audioPromise, videoPromise]).then(() => {
188
+ this.onVideoStats(this.aggregatedStats);
182
189
  // Update the preferred codec selection based on what was actually negotiated
183
190
  if (this.updateCodecSelection && !!this.aggregatedStats.inboundVideoStats.codecId) {
184
191
  this.config.setOptionSettingValue(
@@ -300,6 +307,15 @@ export class PeerConnectionController {
300
307
  * @param event - The webRtc track event
301
308
  */
302
309
  handleOnTrack(event: RTCTrackEvent) {
310
+ if (event.streams.length < 1 || event.streams[0].id == 'probator') {
311
+ return;
312
+ }
313
+ if (event.track.kind == 'video') {
314
+ this.videoTrack = event.track;
315
+ }
316
+ if (event.track.kind == 'audio') {
317
+ this.audioTrack = event.track;
318
+ }
303
319
  this.onTrack(event);
304
320
  }
305
321
 
@@ -366,7 +382,7 @@ export class PeerConnectionController {
366
382
  // Setup a transceiver for getting UE video
367
383
  this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
368
384
 
369
- // We can only set preferrec codec on Chrome
385
+ // We can only set preferred codec on Chrome
370
386
  if (RTCRtpReceiver.getCapabilities && this.preferredCodec != '') {
371
387
  for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
372
388
  if (
@@ -396,7 +412,7 @@ export class PeerConnectionController {
396
412
  return option != this.preferredCodec;
397
413
  })
398
414
  .forEach((option) => {
399
- // Ammend the rest of the browsers supported codecs
415
+ // Amend the rest of the browsers supported codecs
400
416
  const altCodec = option.split(' ');
401
417
  codecs.push({
402
418
  mimeType: 'video/' + altCodec[0] /* Name */,
@@ -6,7 +6,7 @@ import {
6
6
  import { PixelStreaming } from './PixelStreaming';
7
7
  import { SettingsChangedEvent, StreamerListMessageEvent, WebRtcConnectedEvent, WebRtcSdpEvent } from '../Util/EventEmitter';
8
8
  import { mockWebSocket, MockWebSocketSpyFunctions, MockWebSocketTriggerFunctions, unmockWebSocket } from '../__test__/mockWebSocket';
9
- import { MessageReceive } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
9
+ import { BaseMessage, Messages, MessageHelpers } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
10
10
  import { mockRTCPeerConnection, MockRTCPeerConnectionSpyFunctions, MockRTCPeerConnectionTriggerFunctions, unmockRTCPeerConnection } from '../__test__/mockRTCPeerConnection';
11
11
  import { mockHTMLMediaElement, mockMediaStream, unmockMediaStream } from '../__test__/mockMediaStream';
12
12
  import { InitialSettings } from '../DataChannel/InitialSettings';
@@ -32,26 +32,13 @@ describe('PixelStreaming', () => {
32
32
 
33
33
  const triggerWebSocketOpen = () =>
34
34
  webSocketTriggerFunctions.triggerOnOpen?.();
35
- const triggerConfigMessage = () =>
36
- webSocketTriggerFunctions.triggerOnMessage?.({
37
- type: MessageReceive.MessageRecvTypes.CONFIG,
38
- peerConnectionOptions: {}
39
- });
40
- const triggerStreamerListMessage = (streamerIdList: string[]) =>
41
- webSocketTriggerFunctions.triggerOnMessage?.({
42
- type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
43
- ids: streamerIdList
44
- });
45
- const triggerSdpOfferMessage = () =>
46
- webSocketTriggerFunctions.triggerOnMessage?.({
47
- type: MessageReceive.MessageRecvTypes.OFFER,
48
- sdp
49
- });
50
- const triggerIceCandidateMessage = () =>
51
- webSocketTriggerFunctions.triggerOnMessage?.({
52
- type: MessageReceive.MessageRecvTypes.ICE_CANDIDATE,
53
- candidate: iceCandidate
54
- });
35
+ const triggerSignallingMessage = (message: BaseMessage) => {
36
+ webSocketTriggerFunctions.triggerOnMessage?.(message);
37
+ }
38
+ const triggerConfigMessage = () => triggerSignallingMessage(MessageHelpers.createMessage(Messages.config, { peerConnectionOptions: {} }));
39
+ const triggerStreamerListMessage = (streamerIdList: string[]) => triggerSignallingMessage(MessageHelpers.createMessage(Messages.streamerList, { ids: streamerIdList }));
40
+ const triggerSdpOfferMessage = () => triggerSignallingMessage(MessageHelpers.createMessage(Messages.offer, { sdp }));
41
+ const triggerIceCandidateMessage = () => triggerSignallingMessage(MessageHelpers.createMessage(Messages.iceCandidate, { candidate: iceCandidate }));
55
42
  const triggerIceConnectionState = (state: RTCIceConnectionState) =>
56
43
  rtcPeerConnectionTriggerFunctions.triggerIceConnectionStateChange?.(
57
44
  state
@@ -135,7 +122,7 @@ describe('PixelStreaming', () => {
135
122
  expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
136
123
  });
137
124
 
138
- it('should autoconnect to signalling server if autoconnect setting is enabled', () => {
125
+ it('should auto connect to signalling server if auto connect setting is enabled', () => {
139
126
  const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
140
127
 
141
128
  expect(webSocketSpyFunctions.constructorSpy).not.toHaveBeenCalled();
@@ -200,17 +187,13 @@ describe('PixelStreaming', () => {
200
187
 
201
188
  it('should automatically reconnect and request streamer list N times on websocket close', () => {
202
189
  const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true, MaxReconnectAttempts: 3}});
203
- const autoconnectedSpy = jest.fn();
204
-
205
190
  const pixelStreaming = new PixelStreaming(config);
206
- pixelStreaming.addEventListener("webRtcAutoConnect", autoconnectedSpy);
207
191
 
208
192
  expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
209
193
  expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledTimes(1);
210
194
  expect(webSocketSpyFunctions.closeSpy).not.toHaveBeenCalled();
211
195
 
212
-
213
- webSocketTriggerFunctions.triggerRemoteClose();
196
+ webSocketTriggerFunctions.triggerRemoteClose?.();
214
197
 
215
198
  expect(webSocketSpyFunctions.closeSpy).toHaveBeenCalled();
216
199
 
@@ -229,7 +212,7 @@ describe('PixelStreaming', () => {
229
212
  // We don't have a signalling server to respond with data so lets just fake a response with no streamers
230
213
  triggerStreamerListMessage([]);
231
214
  // Wait 2 seconds. This delay waits for the WebRtcPlayerController to realise the previously received list doesn't contain
232
- // the streamer is was preiously subscribed to, so it'll request the list again
215
+ // the streamer is was previously subscribed to, so it'll request the list again
233
216
  jest.advanceTimersByTime(2000);
234
217
 
235
218
  // Same as above but repeated for the second call
@@ -261,7 +244,7 @@ describe('PixelStreaming', () => {
261
244
  );
262
245
  });
263
246
 
264
- it('should autoselect a streamer if receiving only one streamer in streamerList message', () => {
247
+ it('should auto select a streamer if receiving only one streamer in streamerList message', () => {
265
248
  const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
266
249
  const streamerListSpy = jest.fn();
267
250
  const pixelStreaming = new PixelStreaming(config);
@@ -273,18 +256,18 @@ describe('PixelStreaming', () => {
273
256
 
274
257
  expect(streamerListSpy).toHaveBeenCalledWith(new StreamerListMessageEvent({
275
258
  messageStreamerList: expect.objectContaining({
276
- type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
259
+ type: Messages.streamerList.typeName,
277
260
  ids: streamerIdList
278
261
  }),
279
262
  autoSelectedStreamerId: streamerId,
280
- wantedStreamerId: null
263
+ wantedStreamerId: ''
281
264
  }));
282
265
  expect(webSocketSpyFunctions.sendSpy).toHaveBeenCalledWith(
283
266
  expect.stringMatching(/"type":"subscribe".*MOCK_PIXEL_STREAMING/)
284
267
  );
285
268
  });
286
269
 
287
- it('should not autoselect a streamer if receiving multiple streamers in streamerList message', () => {
270
+ it('should not auto select a streamer if receiving multiple streamers in streamerList message', () => {
288
271
  const config = new Config({ initialSettings: {ss: mockSignallingUrl, AutoConnect: true}});
289
272
  const streamerId2 = "MOCK_2_PIXEL_STREAMING";
290
273
  const extendedStreamerIdList = [streamerId, streamerId2];
@@ -298,11 +281,11 @@ describe('PixelStreaming', () => {
298
281
 
299
282
  expect(streamerListSpy).toHaveBeenCalledWith(new StreamerListMessageEvent({
300
283
  messageStreamerList: expect.objectContaining({
301
- type: MessageReceive.MessageRecvTypes.STREAMER_LIST,
284
+ type: Messages.streamerList.typeName,
302
285
  ids: extendedStreamerIdList
303
286
  }),
304
- autoSelectedStreamerId: null,
305
- wantedStreamerId: null
287
+ autoSelectedStreamerId: '',
288
+ wantedStreamerId: ''
306
289
  }));
307
290
  expect(webSocketSpyFunctions.sendSpy).not.toHaveBeenCalledWith(
308
291
  expect.stringMatching(/"type":"subscribe"/)
@@ -443,22 +426,22 @@ describe('PixelStreaming', () => {
443
426
  expect(streamSpy).toHaveBeenCalled();
444
427
  });
445
428
 
446
- it('should emit playStreamRejected if video play is rejected', async () => {
447
- mockHTMLMediaElement({ ableToPlay: false });
448
-
449
- const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
450
- const streamRejectedSpy = jest.fn();
451
- const pixelStreaming = new PixelStreaming(config);
452
- pixelStreaming.addEventListener("playStreamRejected", streamRejectedSpy);
453
- pixelStreaming.connect();
454
-
455
- establishMockedPixelStreamingConnection();
456
-
457
- pixelStreaming.play();
458
- await flushPromises();
459
-
460
- expect(streamRejectedSpy).toHaveBeenCalled();
461
- });
429
+ // it('should emit playStreamRejected if video play is rejected', async () => {
430
+ // mockHTMLMediaElement({ ableToPlay: false });
431
+ //
432
+ // const config = new Config({ initialSettings: {ss: mockSignallingUrl}});
433
+ // const streamRejectedSpy = jest.fn();
434
+ // const pixelStreaming = new PixelStreaming(config);
435
+ // pixelStreaming.addEventListener("playStreamRejected", streamRejectedSpy);
436
+ // pixelStreaming.connect();
437
+ //
438
+ // establishMockedPixelStreamingConnection();
439
+ //
440
+ // pixelStreaming.play();
441
+ // await flushPromises();
442
+ //
443
+ // expect(streamRejectedSpy).toHaveBeenCalled();
444
+ // });
462
445
 
463
446
  it('should send data through the data channel when emitCommand is called', () => {
464
447
  mockHTMLMediaElement({ ableToPlay: true, readyState: 2 });
@@ -45,7 +45,7 @@ import { RTCUtils } from '../Util/RTCUtils';
45
45
 
46
46
 
47
47
  export interface PixelStreamingOverrides {
48
- /** The DOM elment where Pixel Streaming video and user input event handlers are attached to.
48
+ /** The DOM element where Pixel Streaming video and user input event handlers are attached to.
49
49
  * You can give an existing DOM element here. If not given, the library will create a new div element
50
50
  * that is not attached anywhere. In this case you can later get access to this new element and
51
51
  * attach it to your web page. */
@@ -121,7 +121,7 @@ export class PixelStreaming {
121
121
  this._setupWebRtcTCPRelayDetection = this._setupWebRtcTCPRelayDetection.bind(this)
122
122
 
123
123
  // Add event listener for the webRtcConnected event
124
- this._eventEmitter.addEventListener("webRtcConnected", (webRtcConnectedEvent: WebRtcConnectedEvent) => {
124
+ this._eventEmitter.addEventListener("webRtcConnected", (_: WebRtcConnectedEvent) => {
125
125
 
126
126
  // Bind to the stats received event
127
127
  this._eventEmitter.addEventListener("statsReceived", this._setupWebRtcTCPRelayDetection);
@@ -146,7 +146,7 @@ export class PixelStreaming {
146
146
  this.config._addOnSettingChangedListener(
147
147
  Flags.IsQualityController,
148
148
  (wantsQualityController: boolean) => {
149
- // If the setting has been set to true (either programatically or the user has flicked the toggle)
149
+ // If the setting has been set to true (either programmatically or the user has flicked the toggle)
150
150
  // and we aren't currently quality controller, send the request
151
151
  if (
152
152
  wantsQualityController === true &&
@@ -640,13 +640,13 @@ export class PixelStreaming {
640
640
  // Sets up to emit the webrtc tcp relay detect event
641
641
  _setupWebRtcTCPRelayDetection(statsReceivedEvent: StatsReceivedEvent) {
642
642
  // Get the active candidate pair
643
- let activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();
643
+ const activeCandidatePair = statsReceivedEvent.data.aggregatedStats.getActiveCandidatePair();
644
644
 
645
645
  // Check if the active candidate pair is not null
646
646
  if (activeCandidatePair != null) {
647
647
 
648
648
  // Get the local candidate assigned to the active candidate pair
649
- let localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)
649
+ const localCandidate = statsReceivedEvent.data.aggregatedStats.localCandidates.find((candidate) => candidate.id == activeCandidatePair.localCandidateId, null)
650
650
 
651
651
  // Check if the local candidate is not null, candidate type is relay and the relay protocol is tcp
652
652
  if (localCandidate != null && localCandidate.candidateType == 'relay' && localCandidate.relayProtocol == 'tcp') {
@@ -92,11 +92,12 @@ export class CoordinateConverter {
92
92
  this.videoElement = this.videoElementProvider.getVideoElement();
93
93
 
94
94
  if (this.videoElementParent && this.videoElement) {
95
- const playerAspectRatio =
96
- this.videoElementParent.clientHeight /
97
- this.videoElementParent.clientWidth;
98
- const videoAspectRatio =
99
- this.videoElement.videoHeight / this.videoElement.videoWidth;
95
+ const playerWidth = this.videoElementParent.clientWidth || 1;
96
+ const playerHeight = this.videoElementParent.clientHeight || 1;
97
+ const videoWidth = this.videoElement.videoWidth || 1;
98
+ const videoHeight = this.videoElement.videoHeight || 1;
99
+ const playerAspectRatio = playerHeight / playerWidth;
100
+ const videoAspectRatio = videoHeight / videoWidth;
100
101
  if (playerAspectRatio > videoAspectRatio) {
101
102
  Logger.Log(
102
103
  Logger.GetStackTrace(),
@@ -1,5 +1,5 @@
1
1
  export class RTCUtils {
2
- static isVideoTransciever(transceiver : RTCRtpTransceiver | undefined) : boolean {
2
+ static isVideoTransceiver(transceiver : RTCRtpTransceiver | undefined) : boolean {
3
3
  return this.canTransceiverReceiveVideo(transceiver) || this.canTransceiverSendVideo(transceiver);
4
4
  }
5
5
 
@@ -19,7 +19,7 @@ export class RTCUtils {
19
19
  transceiver.sender.track.kind === 'video';
20
20
  }
21
21
 
22
- static isAudioTransciever(transceiver : RTCRtpTransceiver | undefined) : boolean {
22
+ static isAudioTransceiver(transceiver : RTCRtpTransceiver | undefined) : boolean {
23
23
  return this.canTransceiverReceiveAudio(transceiver) || this.canTransceiverSendAudio(transceiver);
24
24
  }
25
25
 
@@ -31,6 +31,12 @@ export class StreamController {
31
31
  'handleOnTrack ' + JSON.stringify(rtcTrackEvent.streams),
32
32
  6
33
33
  );
34
+ // Do not add the track if the ID is `probator` as this is special track created by mediasoup for bitrate probing.
35
+ // Refer to https://github.com/EpicGamesExt/PixelStreamingInfrastructure/pull/86 for more details.
36
+ if (rtcTrackEvent.streams.length < 1 || rtcTrackEvent.streams[0].id == 'probator') {
37
+ return;
38
+ }
39
+
34
40
  const videoElement = this.videoElementProvider.getVideoElement();
35
41
 
36
42
  if (rtcTrackEvent.track) {
@@ -222,6 +222,7 @@ export class WebRtcPlayerController {
222
222
  const message = MessageHelpers.createMessage(Messages.listStreamers);
223
223
  this.protocol.sendMessage(message);
224
224
  }
225
+ this.reconnectAttempt = 0;
225
226
  });
226
227
  this.protocol.transport.addListener('error', () => {
227
228
  // dont really need to do anything here since the close event should follow.
@@ -706,6 +707,15 @@ export class WebRtcPlayerController {
706
707
  data
707
708
  )
708
709
  );
710
+ this.streamMessageController.registerMessageHandler(
711
+ MessageDirection.ToStreamer,
712
+ 'XREyeViews',
713
+ (data: Array<number | string>) =>
714
+ this.sendMessageController.sendMessageToStreamer(
715
+ 'XREyeViews',
716
+ data
717
+ )
718
+ );
709
719
  this.streamMessageController.registerMessageHandler(
710
720
  MessageDirection.ToStreamer,
711
721
  'XRHMDTransform',
@@ -742,6 +752,15 @@ export class WebRtcPlayerController {
742
752
  data
743
753
  )
744
754
  );
755
+ this.streamMessageController.registerMessageHandler(
756
+ MessageDirection.ToStreamer,
757
+ 'XRButtonTouchReleased',
758
+ (data: Array<number | string>) =>
759
+ this.sendMessageController.sendMessageToStreamer(
760
+ 'XRButtonTouchReleased',
761
+ data
762
+ )
763
+ );
745
764
  this.streamMessageController.registerMessageHandler(
746
765
  MessageDirection.ToStreamer,
747
766
  'XRButtonPressed',
@@ -1324,7 +1343,7 @@ export class WebRtcPlayerController {
1324
1343
  6
1325
1344
  );
1326
1345
 
1327
- let wantedStreamerId: string = null;
1346
+ let wantedStreamerId: string = '';
1328
1347
 
1329
1348
  // get the current selected streamer id option
1330
1349
  const streamerIDOption = this.config.getSettingOption(OptionParameters.StreamerId);
@@ -1342,7 +1361,7 @@ export class WebRtcPlayerController {
1342
1361
  settingOptions
1343
1362
  );
1344
1363
 
1345
- let autoSelectedStreamerId: string = null;
1364
+ let autoSelectedStreamerId: string = '';
1346
1365
  const waitForStreamer = this.config.isFlagEnabled(Flags.WaitForStreamer);
1347
1366
  const reconnectLimit = this.config.getNumericSettingValue(NumericParameters.MaxReconnectAttempts);
1348
1367
  const reconnectDelay = this.config.getNumericSettingValue(NumericParameters.StreamerAutoJoinInterval);