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

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.
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.0.12",
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
+
@@ -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
@@ -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
 
@@ -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
@@ -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
 
@@ -273,11 +256,11 @@ 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/)
@@ -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 });
@@ -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);
@@ -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') {
@@ -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.
@@ -1324,7 +1325,7 @@ export class WebRtcPlayerController {
1324
1325
  6
1325
1326
  );
1326
1327
 
1327
- let wantedStreamerId: string = null;
1328
+ let wantedStreamerId: string = '';
1328
1329
 
1329
1330
  // get the current selected streamer id option
1330
1331
  const streamerIDOption = this.config.getSettingOption(OptionParameters.StreamerId);
@@ -1342,7 +1343,7 @@ export class WebRtcPlayerController {
1342
1343
  settingOptions
1343
1344
  );
1344
1345
 
1345
- let autoSelectedStreamerId: string = null;
1346
+ let autoSelectedStreamerId: string = '';
1346
1347
  const waitForStreamer = this.config.isFlagEnabled(Flags.WaitForStreamer);
1347
1348
  const reconnectLimit = this.config.getNumericSettingValue(NumericParameters.MaxReconnectAttempts);
1348
1349
  const reconnectDelay = this.config.getNumericSettingValue(NumericParameters.StreamerAutoJoinInterval);
@@ -1,3 +1,5 @@
1
+ import { BaseMessage } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
2
+
1
3
  export interface MockWebSocketSpyFunctions {
2
4
  constructorSpy: null | ((url: string) => void);
3
5
  openSpy: null | ((event: Event) => void);
@@ -12,7 +14,7 @@ export interface MockWebSocketTriggerFunctions {
12
14
  triggerOnOpen: null | (() => void);
13
15
  triggerOnError: null | (() => void);
14
16
  triggerOnClose: null | ((closeReason?: CloseEventInit) => void);
15
- triggerOnMessage: null | ((message?: object) => void);
17
+ triggerOnMessage: null | ((message?: BaseMessage) => void);
16
18
  triggerOnMessageBinary: null | ((message?: Blob) => void);
17
19
  triggerRemoteClose: null | ((code?: number, reason?: string) => void);
18
20
  }
@@ -88,10 +90,8 @@ export class MockWebSocketImpl extends WebSocket {
88
90
  this.close(code, reason);
89
91
  }
90
92
 
91
- triggerOnMessage(message?: object) {
92
- const data = message
93
- ? JSON.stringify(message)
94
- : JSON.stringify({ type: 'test' });
93
+ triggerOnMessage(message: BaseMessage) {
94
+ const data = JSON.stringify(message);
95
95
  const event = new MessageEvent('message', { data });
96
96
  this.onmessage?.(event);
97
97
  spyFunctions.messageSpy?.(event);
@@ -9,6 +9,8 @@ export declare class PeerConnectionController {
9
9
  config: Config;
10
10
  preferredCodec: string;
11
11
  updateCodecSelection: boolean;
12
+ videoTrack: MediaStreamTrack;
13
+ audioTrack: MediaStreamTrack;
12
14
  /**
13
15
  * Create a new RTC Peer Connection client
14
16
  * @param options - Peer connection Options