@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.
- package/.eslintrc.js +1 -1
- package/.prettierrc.json +1 -0
- package/dist/lib-pixelstreamingfrontend.esm.js +1 -1
- package/dist/lib-pixelstreamingfrontend.js +1 -1
- package/package.json +6 -5
- package/src/AFK/AFKController.ts +10 -32
- package/src/Config/Config.ts +179 -201
- package/src/Config/SettingBase.ts +61 -2
- package/src/Config/SettingFlag.ts +10 -48
- package/src/Config/SettingNumber.ts +10 -28
- package/src/Config/SettingOption.ts +13 -46
- package/src/Config/SettingText.ts +9 -37
- package/src/DataChannel/DataChannelController.ts +6 -26
- package/src/DataChannel/DataChannelLatencyTestController.ts +38 -33
- package/src/DataChannel/DataChannelLatencyTestResults.ts +8 -10
- package/src/DataChannel/DataChannelSender.ts +5 -15
- package/src/DataChannel/LatencyTestResults.ts +5 -15
- package/src/FreezeFrame/FreezeFrame.ts +7 -19
- package/src/FreezeFrame/FreezeFrameController.ts +3 -14
- package/src/Inputs/GamepadController.ts +123 -221
- package/src/Inputs/GamepadTypes.ts +23 -0
- package/src/Inputs/IInputController.ts +17 -0
- package/src/Inputs/InputClassesFactory.ts +38 -45
- package/src/Inputs/KeyCodes.ts +114 -0
- package/src/Inputs/KeyboardController.ts +49 -232
- package/src/Inputs/MouseController.ts +71 -297
- package/src/Inputs/MouseControllerHovering.ts +118 -0
- package/src/Inputs/MouseControllerLocked.ts +194 -0
- package/src/Inputs/TouchController.ts +49 -105
- package/src/Inputs/TouchControllerFake.ts +132 -0
- package/src/Inputs/XRGamepadController.ts +35 -44
- package/src/PeerConnectionController/AggregatedStats.ts +26 -54
- package/src/PeerConnectionController/CandidatePairStats.ts +1 -1
- package/src/PeerConnectionController/CandidateStat.ts +1 -1
- package/src/PeerConnectionController/PeerConnectionController.ts +217 -164
- package/src/PixelStreaming/PixelStreaming.ts +174 -226
- package/src/UI/OnScreenKeyboard.ts +14 -9
- package/src/UeInstanceMessage/ResponseController.ts +6 -15
- package/src/UeInstanceMessage/SendMessageController.ts +16 -18
- package/src/UeInstanceMessage/StreamMessageController.ts +3 -12
- package/src/UeInstanceMessage/ToStreamerMessagesController.ts +3 -9
- package/src/Util/EventEmitter.ts +17 -22
- package/src/Util/FileUtil.ts +11 -34
- package/src/Util/IURLSearchParams.ts +25 -0
- package/src/Util/InputCoordTranslator.ts +73 -0
- package/src/Util/RTCUtils.ts +23 -15
- package/src/VideoPlayer/StreamController.ts +6 -23
- package/src/VideoPlayer/VideoPlayer.ts +9 -30
- package/src/WebRtcPlayer/WebRtcPlayerController.ts +328 -690
- package/src/WebXR/WebXRController.ts +82 -94
- package/src/pixelstreamingfrontend.ts +6 -10
- package/types/AFK/AFKController.d.ts +0 -1
- package/types/Config/Config.d.ts +6 -5
- package/types/Config/SettingBase.d.ts +13 -0
- package/types/Config/SettingFlag.d.ts +1 -10
- package/types/Config/SettingNumber.d.ts +1 -5
- package/types/Config/SettingOption.d.ts +1 -10
- package/types/Config/SettingText.d.ts +1 -9
- package/types/DataChannel/DataChannelLatencyTestController.d.ts +1 -1
- package/types/Inputs/GamepadController.d.ts +22 -46
- package/types/Inputs/GamepadTypes.d.ts +7 -0
- package/types/Inputs/IInputController.d.ts +16 -0
- package/types/Inputs/InputClassesFactory.d.ts +7 -8
- package/types/Inputs/KeyCodes.d.ts +5 -0
- package/types/Inputs/KeyboardController.d.ts +17 -45
- package/types/Inputs/MouseController.d.ts +33 -68
- package/types/Inputs/MouseControllerHovering.d.ts +26 -0
- package/types/Inputs/MouseControllerLocked.d.ts +31 -0
- package/types/Inputs/TouchController.d.ts +19 -44
- package/types/Inputs/TouchControllerFake.d.ts +29 -0
- package/types/Inputs/XRGamepadController.d.ts +0 -7
- package/types/PeerConnectionController/PeerConnectionController.d.ts +10 -1
- package/types/PixelStreaming/PixelStreaming.d.ts +14 -2
- package/types/UI/OnScreenKeyboard.d.ts +2 -2
- package/types/Util/EventEmitter.d.ts +1 -1
- package/types/Util/IURLSearchParams.d.ts +9 -0
- package/types/Util/InputCoordTranslator.d.ts +29 -0
- package/types/VideoPlayer/StreamController.d.ts +0 -2
- package/types/WebRtcPlayer/WebRtcPlayerController.d.ts +19 -17
- package/types/pixelstreamingfrontend.d.ts +1 -1
- package/src/Inputs/FakeTouchController.ts +0 -199
- package/src/Inputs/HoveringMouseEvents.ts +0 -192
- package/src/Inputs/IMouseEvents.ts +0 -64
- package/src/Inputs/ITouchController.ts +0 -29
- package/src/Inputs/LockedMouseEvents.ts +0 -287
- package/src/Util/CoordinateConverter.ts +0 -290
- package/src/Util/EventListenerTracker.ts +0 -29
- package/types/Inputs/FakeTouchController.d.ts +0 -61
- package/types/Inputs/HoveringMouseEvents.d.ts +0 -56
- package/types/Inputs/IMouseEvents.d.ts +0 -53
- package/types/Inputs/ITouchController.d.ts +0 -24
- package/types/Inputs/LockedMouseEvents.d.ts +0 -80
- package/types/Util/CoordinateConverter.d.ts +0 -100
- 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.
|
|
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
|
-
|
|
53
|
-
this.peerConnection.
|
|
54
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
+
|
|
142
|
+
// Add our list of preferred codecs, in order of preference
|
|
166
143
|
this.config.setOptionSettingOptions(
|
|
167
144
|
OptionParameters.PreferredCodec,
|
|
168
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
380
|
-
|
|
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
|
|
383
|
-
|
|
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
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
463
|
+
async setupVideoSender(hasVideoReceiver: boolean) {
|
|
464
|
+
// set the media send options
|
|
465
|
+
const mediaSendOptions: MediaStreamConstraints = {
|
|
466
|
+
video: true
|
|
467
|
+
};
|
|
461
468
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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);
|