@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.
- package/.cspell.json +35 -0
- package/dist/lib-pixelstreamingfrontend.esm.js +1 -1
- package/dist/lib-pixelstreamingfrontend.js +1 -1
- package/package.json +3 -2
- package/readme.md +1 -1
- package/src/Config/Config.ts +1 -1
- package/src/Inputs/FakeTouchController.ts +1 -1
- package/src/Inputs/GamepadController.ts +2 -2
- package/src/Inputs/IMouseEvents.ts +1 -1
- package/src/Inputs/ITouchController.ts +1 -1
- package/src/Inputs/KeyboardController.ts +2 -2
- package/src/Inputs/LockedMouseEvents.ts +2 -2
- package/src/Inputs/MouseController.ts +1 -1
- package/src/Inputs/TouchController.ts +1 -1
- package/src/Inputs/XRGamepadController.ts +44 -26
- package/src/PeerConnectionController/PeerConnectionController.ts +20 -4
- package/src/PixelStreaming/PixelStreaming.test.ts +34 -51
- package/src/PixelStreaming/PixelStreaming.ts +5 -5
- package/src/Util/CoordinateConverter.ts +6 -5
- package/src/Util/RTCUtils.ts +2 -2
- package/src/VideoPlayer/StreamController.ts +6 -0
- package/src/WebRtcPlayer/WebRtcPlayerController.ts +21 -2
- package/src/WebXR/WebXRController.ts +368 -179
- package/src/__test__/mockWebSocket.ts +5 -5
- package/types/Inputs/GamepadController.d.ts +1 -1
- package/types/Inputs/IMouseEvents.d.ts +1 -1
- package/types/Inputs/ITouchController.d.ts +1 -1
- package/types/Inputs/KeyboardController.d.ts +1 -1
- package/types/Inputs/LockedMouseEvents.d.ts +1 -1
- package/types/Inputs/XRGamepadController.d.ts +8 -1
- package/types/PeerConnectionController/PeerConnectionController.d.ts +2 -0
- package/types/PixelStreaming/PixelStreaming.d.ts +1 -1
- package/types/Util/RTCUtils.d.ts +2 -2
- package/types/WebXR/WebXRController.d.ts +19 -3
- package/src/Util/WebGLUtils.ts +0 -49
- package/src/Util/WebXRUtils.ts +0 -25
- package/types/Util/WebGLUtils.d.ts +0 -4
- 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
|
|
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.
|
|
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
|
|
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`
|
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
|
|
@@ -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
|
|
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
|
|
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
|
-
*
|
|
72
|
+
* Unregister all event handlers.
|
|
73
73
|
*/
|
|
74
74
|
unregisterGamePadEvents() {
|
|
75
75
|
this.gamePadEventListenerTracker.unregisterAll();
|
|
@@ -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
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -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
|
|
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
|
-
//
|
|
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 {
|
|
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
|
|
@@ -135,7 +122,7 @@ describe('PixelStreaming', () => {
|
|
|
135
122
|
expect(webSocketSpyFunctions.constructorSpy).toHaveBeenCalledWith(mockSignallingUrl);
|
|
136
123
|
});
|
|
137
124
|
|
|
138
|
-
it('should
|
|
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
|
|
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
|
|
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:
|
|
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/)
|
|
284
267
|
);
|
|
285
268
|
});
|
|
286
269
|
|
|
287
|
-
it('should not
|
|
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:
|
|
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 });
|
|
@@ -45,7 +45,7 @@ import { RTCUtils } from '../Util/RTCUtils';
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
export interface PixelStreamingOverrides {
|
|
48
|
-
/** The DOM
|
|
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", (
|
|
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
|
|
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
|
-
|
|
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') {
|
|
@@ -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
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
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(),
|
package/src/Util/RTCUtils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export class RTCUtils {
|
|
2
|
-
static
|
|
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
|
|
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 =
|
|
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 =
|
|
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);
|