@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/dist/lib-pixelstreamingfrontend.esm.js +1 -1
- package/dist/lib-pixelstreamingfrontend.js +1 -1
- package/package.json +3 -2
- package/src/Config/Config.ts +1 -1
- package/src/PeerConnectionController/PeerConnectionController.ts +18 -2
- package/src/PixelStreaming/PixelStreaming.test.ts +30 -47
- package/src/PixelStreaming/PixelStreaming.ts +3 -3
- package/src/VideoPlayer/StreamController.ts +6 -0
- package/src/WebRtcPlayer/WebRtcPlayerController.ts +3 -2
- package/src/__test__/mockWebSocket.ts +5 -5
- package/types/PeerConnectionController/PeerConnectionController.d.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epicgames-ps/lib-pixelstreamingfrontend-ue5.5",
|
|
3
|
-
"version": "0.0.
|
|
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.
|
|
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/src/Config/Config.ts
CHANGED
|
@@ -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 &&
|
|
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
|
-
|
|
180
|
+
const statsHandler = (StatsData: RTCStatsReport) => {
|
|
179
181
|
this.aggregatedStats.processStats(StatsData);
|
|
180
|
-
|
|
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 {
|
|
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
|
|
36
|
-
webSocketTriggerFunctions.triggerOnMessage?.(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
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:
|
|
259
|
+
type: Messages.streamerList.typeName,
|
|
277
260
|
ids: streamerIdList
|
|
278
261
|
}),
|
|
279
262
|
autoSelectedStreamerId: streamerId,
|
|
280
|
-
wantedStreamerId:
|
|
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:
|
|
284
|
+
type: Messages.streamerList.typeName,
|
|
302
285
|
ids: extendedStreamerIdList
|
|
303
286
|
}),
|
|
304
|
-
autoSelectedStreamerId:
|
|
305
|
-
wantedStreamerId:
|
|
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
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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", (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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?:
|
|
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
|
|
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
|