@epicgames-ps/lib-pixelstreamingfrontend-ue5.5 0.0.5
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 +48 -0
- package/.eslintignore +8 -0
- package/.eslintrc.js +8 -0
- package/.prettierignore +0 -0
- package/.prettierrc.json +6 -0
- package/dist/lib-pixelstreamingfrontend.esm.js +1 -0
- package/dist/lib-pixelstreamingfrontend.js +1 -0
- package/jest.config.js +18 -0
- package/package.json +48 -0
- package/readme.md +15 -0
- package/src/AFK/AFKController.test.ts +162 -0
- package/src/AFK/AFKController.ts +158 -0
- package/src/Config/Config.test.ts +222 -0
- package/src/Config/Config.ts +970 -0
- package/src/Config/SettingBase.ts +65 -0
- package/src/Config/SettingFlag.ts +99 -0
- package/src/Config/SettingNumber.ts +111 -0
- package/src/Config/SettingOption.ts +124 -0
- package/src/Config/SettingText.ts +82 -0
- package/src/DataChannel/DataChannelController.ts +138 -0
- package/src/DataChannel/DataChannelLatencyTestController.ts +129 -0
- package/src/DataChannel/DataChannelLatencyTestResults.ts +67 -0
- package/src/DataChannel/DataChannelSender.ts +59 -0
- package/src/DataChannel/InitialSettings.ts +61 -0
- package/src/DataChannel/LatencyTestResults.ts +76 -0
- package/src/FreezeFrame/FreezeFrame.ts +114 -0
- package/src/FreezeFrame/FreezeFrameController.ts +114 -0
- package/src/Inputs/FakeTouchController.ts +199 -0
- package/src/Inputs/GamepadController.ts +314 -0
- package/src/Inputs/GamepadTypes.ts +10 -0
- package/src/Inputs/HoveringMouseEvents.ts +192 -0
- package/src/Inputs/IMouseEvents.ts +64 -0
- package/src/Inputs/ITouchController.ts +29 -0
- package/src/Inputs/InputClassesFactory.ts +140 -0
- package/src/Inputs/KeyboardController.ts +354 -0
- package/src/Inputs/LockedMouseEvents.ts +287 -0
- package/src/Inputs/MouseButtons.ts +25 -0
- package/src/Inputs/MouseController.ts +362 -0
- package/src/Inputs/SpecialKeyCodes.ts +16 -0
- package/src/Inputs/TouchController.ts +208 -0
- package/src/Inputs/XRGamepadController.ts +126 -0
- package/src/PeerConnectionController/AggregatedStats.ts +311 -0
- package/src/PeerConnectionController/CandidatePairStats.ts +17 -0
- package/src/PeerConnectionController/CandidateStat.ts +13 -0
- package/src/PeerConnectionController/CodecStats.ts +19 -0
- package/src/PeerConnectionController/DataChannelStats.ts +17 -0
- package/src/PeerConnectionController/InboundRTPStats.ts +154 -0
- package/src/PeerConnectionController/InboundTrackStats.ts +34 -0
- package/src/PeerConnectionController/OutBoundRTPStats.ts +26 -0
- package/src/PeerConnectionController/PeerConnectionController.ts +563 -0
- package/src/PeerConnectionController/SessionStats.ts +10 -0
- package/src/PeerConnectionController/StreamStats.ts +11 -0
- package/src/PixelStreaming/PixelStreaming.test.ts +626 -0
- package/src/PixelStreaming/PixelStreaming.ts +851 -0
- package/src/UI/OnScreenKeyboard.ts +97 -0
- package/src/UeInstanceMessage/ResponseController.ts +47 -0
- package/src/UeInstanceMessage/SendMessageController.ts +154 -0
- package/src/UeInstanceMessage/StreamMessageController.ts +233 -0
- package/src/UeInstanceMessage/ToStreamerMessagesController.ts +62 -0
- package/src/Util/CoordinateConverter.ts +289 -0
- package/src/Util/EventEmitter.ts +611 -0
- package/src/Util/EventListenerTracker.ts +29 -0
- package/src/Util/FileUtil.ts +140 -0
- package/src/Util/RTCUtils.ts +41 -0
- package/src/Util/WebGLUtils.ts +49 -0
- package/src/Util/WebXRUtils.ts +25 -0
- package/src/VideoPlayer/StreamController.ts +89 -0
- package/src/VideoPlayer/VideoPlayer.ts +246 -0
- package/src/WebRtcPlayer/WebRtcPlayerController.ts +2158 -0
- package/src/WebXR/WebXRController.ts +319 -0
- package/src/__test__/mockMediaStream.ts +124 -0
- package/src/__test__/mockRTCPeerConnection.ts +347 -0
- package/src/__test__/mockRTCRtpReceiver.ts +22 -0
- package/src/__test__/mockWebSocket.ts +136 -0
- package/src/pixelstreamingfrontend.ts +46 -0
- package/tsconfig.jest.json +8 -0
- package/tsconfig.json +24 -0
- package/types/AFK/AFKController.d.ts +39 -0
- package/types/Config/Config.d.ts +218 -0
- package/types/Config/SettingBase.d.ts +30 -0
- package/types/Config/SettingFlag.d.ts +33 -0
- package/types/Config/SettingNumber.d.ts +45 -0
- package/types/Config/SettingOption.d.ts +43 -0
- package/types/Config/SettingText.d.ts +29 -0
- package/types/DataChannel/DataChannelController.d.ts +59 -0
- package/types/DataChannel/DataChannelLatencyTestController.d.ts +26 -0
- package/types/DataChannel/DataChannelLatencyTestResults.d.ts +46 -0
- package/types/DataChannel/DataChannelSender.d.ts +21 -0
- package/types/DataChannel/InitialSettings.d.ts +44 -0
- package/types/DataChannel/LatencyTestResults.d.ts +31 -0
- package/types/FreezeFrame/FreezeFrame.d.ts +36 -0
- package/types/FreezeFrame/FreezeFrameController.d.ts +37 -0
- package/types/Inputs/FakeTouchController.d.ts +61 -0
- package/types/Inputs/GamepadController.d.ts +85 -0
- package/types/Inputs/GamepadTypes.d.ts +8 -0
- package/types/Inputs/HoveringMouseEvents.d.ts +56 -0
- package/types/Inputs/IMouseEvents.d.ts +53 -0
- package/types/Inputs/ITouchController.d.ts +24 -0
- package/types/Inputs/InputClassesFactory.d.ts +54 -0
- package/types/Inputs/KeyboardController.d.ts +62 -0
- package/types/Inputs/LockedMouseEvents.d.ts +80 -0
- package/types/Inputs/MouseButtons.d.ts +22 -0
- package/types/Inputs/MouseController.d.ts +75 -0
- package/types/Inputs/SpecialKeyCodes.d.ts +14 -0
- package/types/Inputs/TouchController.d.ts +53 -0
- package/types/Inputs/XRGamepadController.d.ts +15 -0
- package/types/PeerConnectionController/AggregatedStats.d.ts +77 -0
- package/types/PeerConnectionController/CandidatePairStats.d.ts +15 -0
- package/types/PeerConnectionController/CandidateStat.d.ts +11 -0
- package/types/PeerConnectionController/CodecStats.d.ts +14 -0
- package/types/PeerConnectionController/DataChannelStats.d.ts +15 -0
- package/types/PeerConnectionController/InboundRTPStats.d.ts +141 -0
- package/types/PeerConnectionController/InboundTrackStats.d.ts +32 -0
- package/types/PeerConnectionController/OutBoundRTPStats.d.ts +23 -0
- package/types/PeerConnectionController/PeerConnectionController.d.ts +132 -0
- package/types/PeerConnectionController/SessionStats.d.ts +8 -0
- package/types/PeerConnectionController/StreamStats.d.ts +9 -0
- package/types/PixelStreaming/PixelStreaming.d.ts +259 -0
- package/types/UI/OnScreenKeyboard.d.ts +31 -0
- package/types/UeInstanceMessage/ResponseController.d.ts +19 -0
- package/types/UeInstanceMessage/SendMessageController.d.ts +18 -0
- package/types/UeInstanceMessage/StreamMessageController.d.ts +29 -0
- package/types/UeInstanceMessage/ToStreamerMessagesController.d.ts +32 -0
- package/types/Util/CoordinateConverter.d.ts +100 -0
- package/types/Util/EventEmitter.d.ts +422 -0
- package/types/Util/EventListenerTracker.d.ts +14 -0
- package/types/Util/FileUtil.d.ts +32 -0
- package/types/Util/RTCUtils.d.ts +8 -0
- package/types/Util/WebGLUtils.d.ts +4 -0
- package/types/Util/WebXRUtils.d.ts +9 -0
- package/types/VideoPlayer/StreamController.d.ts +24 -0
- package/types/VideoPlayer/VideoPlayer.d.ts +78 -0
- package/types/WebRtcPlayer/WebRtcPlayerController.d.ts +377 -0
- package/types/WebXR/WebXRController.d.ts +26 -0
- package/types/pixelstreamingfrontend.d.ts +22 -0
- package/webpack.common.js +35 -0
- package/webpack.dev.js +35 -0
- package/webpack.prod.js +36 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Outbound Video Stats collected from the RTC Stats Report
|
|
5
|
+
*/
|
|
6
|
+
export class OutBoundVideoStats {
|
|
7
|
+
bytesSent: number;
|
|
8
|
+
id: string;
|
|
9
|
+
localId: string;
|
|
10
|
+
packetsSent: number;
|
|
11
|
+
remoteTimestamp: number;
|
|
12
|
+
timestamp: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Outbound Stats collected from the RTC Stats Report
|
|
17
|
+
*/
|
|
18
|
+
export class OutBoundRTPStats {
|
|
19
|
+
kind: string;
|
|
20
|
+
bytesSent: number;
|
|
21
|
+
id: string;
|
|
22
|
+
localId: string;
|
|
23
|
+
packetsSent: number;
|
|
24
|
+
remoteTimestamp: number;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
}
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
import { Logger } from '@epicgames-ps/lib-pixelstreamingcommon-ue5.5';
|
|
4
|
+
import { Config, OptionParameters, Flags } from '../Config/Config';
|
|
5
|
+
import { AggregatedStats } from './AggregatedStats';
|
|
6
|
+
import { parseRtpParameters, splitSections } from 'sdp';
|
|
7
|
+
import { RTCUtils } from '../Util/RTCUtils';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles the Peer Connection
|
|
11
|
+
*/
|
|
12
|
+
export class PeerConnectionController {
|
|
13
|
+
peerConnection: RTCPeerConnection;
|
|
14
|
+
aggregatedStats: AggregatedStats;
|
|
15
|
+
config: Config;
|
|
16
|
+
preferredCodec: string;
|
|
17
|
+
updateCodecSelection: boolean;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Create a new RTC Peer Connection client
|
|
21
|
+
* @param options - Peer connection Options
|
|
22
|
+
* @param config - The config for our PS experience.
|
|
23
|
+
*/
|
|
24
|
+
constructor(
|
|
25
|
+
options: RTCConfiguration,
|
|
26
|
+
config: Config,
|
|
27
|
+
preferredCodec: string
|
|
28
|
+
) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.createPeerConnection(options, preferredCodec);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
createPeerConnection(options: RTCConfiguration, preferredCodec: string) {
|
|
34
|
+
// Set the ICE transport to relay if TURN enabled
|
|
35
|
+
if (this.config.isFlagEnabled(Flags.ForceTURN)) {
|
|
36
|
+
options.iceTransportPolicy = 'relay';
|
|
37
|
+
Logger.Log(
|
|
38
|
+
Logger.GetStackTrace(),
|
|
39
|
+
'Forcing TURN usage by setting ICE Transport Policy in peer connection config.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// build a new peer connection with the options
|
|
44
|
+
this.peerConnection = new RTCPeerConnection(options);
|
|
45
|
+
this.peerConnection.onsignalingstatechange = (ev: Event) =>
|
|
46
|
+
this.handleSignalStateChange(ev);
|
|
47
|
+
this.peerConnection.oniceconnectionstatechange = (ev: Event) =>
|
|
48
|
+
this.handleIceConnectionStateChange(ev);
|
|
49
|
+
this.peerConnection.onicegatheringstatechange = (ev: Event) =>
|
|
50
|
+
this.handleIceGatheringStateChange(ev);
|
|
51
|
+
this.peerConnection.ontrack = (ev: RTCTrackEvent) =>
|
|
52
|
+
this.handleOnTrack(ev);
|
|
53
|
+
this.peerConnection.onicecandidate = (ev: RTCPeerConnectionIceEvent) =>
|
|
54
|
+
this.handleIceCandidate(ev);
|
|
55
|
+
this.peerConnection.ondatachannel = (ev: RTCDataChannelEvent) =>
|
|
56
|
+
this.handleDataChannel(ev);
|
|
57
|
+
this.aggregatedStats = new AggregatedStats();
|
|
58
|
+
this.preferredCodec = preferredCodec;
|
|
59
|
+
this.updateCodecSelection = true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create an offer for the Web RTC handshake and send the offer to the signaling server via websocket
|
|
64
|
+
* @param offerOptions - RTC Offer Options
|
|
65
|
+
*/
|
|
66
|
+
async createOffer(offerOptions: RTCOfferOptions, config: Config) {
|
|
67
|
+
Logger.Log(Logger.GetStackTrace(), 'Create Offer', 6);
|
|
68
|
+
|
|
69
|
+
const isLocalhostConnection =
|
|
70
|
+
location.hostname === 'localhost' ||
|
|
71
|
+
location.hostname === '127.0.0.1';
|
|
72
|
+
const isHttpsConnection = location.protocol === 'https:';
|
|
73
|
+
let useMic = config.isFlagEnabled(Flags.UseMic);
|
|
74
|
+
if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
|
|
75
|
+
useMic = false;
|
|
76
|
+
Logger.Error(
|
|
77
|
+
Logger.GetStackTrace(),
|
|
78
|
+
'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
|
|
79
|
+
);
|
|
80
|
+
Logger.Error(
|
|
81
|
+
Logger.GetStackTrace(),
|
|
82
|
+
"For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.setupTransceiversAsync(useMic).finally(() => {
|
|
87
|
+
this.peerConnection
|
|
88
|
+
?.createOffer(offerOptions)
|
|
89
|
+
.then((offer: RTCSessionDescriptionInit) => {
|
|
90
|
+
this.showTextOverlayConnecting();
|
|
91
|
+
offer.sdp = this.mungeSDP(offer.sdp, useMic);
|
|
92
|
+
this.peerConnection?.setLocalDescription(offer);
|
|
93
|
+
this.onSendWebRTCOffer(offer);
|
|
94
|
+
})
|
|
95
|
+
.catch(() => {
|
|
96
|
+
this.showTextOverlaySetupFailure();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
*
|
|
103
|
+
*/
|
|
104
|
+
async receiveOffer(offer: RTCSessionDescriptionInit, config: Config) {
|
|
105
|
+
Logger.Log(Logger.GetStackTrace(), 'Receive Offer', 6);
|
|
106
|
+
|
|
107
|
+
this.peerConnection?.setRemoteDescription(offer).then(() => {
|
|
108
|
+
const isLocalhostConnection =
|
|
109
|
+
location.hostname === 'localhost' ||
|
|
110
|
+
location.hostname === '127.0.0.1';
|
|
111
|
+
const isHttpsConnection = location.protocol === 'https:';
|
|
112
|
+
let useMic = config.isFlagEnabled(Flags.UseMic);
|
|
113
|
+
if (useMic && !(isLocalhostConnection || isHttpsConnection)) {
|
|
114
|
+
useMic = false;
|
|
115
|
+
Logger.Error(
|
|
116
|
+
Logger.GetStackTrace(),
|
|
117
|
+
'Microphone access in the browser will not work if you are not on HTTPS or localhost. Disabling mic access.'
|
|
118
|
+
);
|
|
119
|
+
Logger.Error(
|
|
120
|
+
Logger.GetStackTrace(),
|
|
121
|
+
"For testing you can enable HTTP microphone access Chrome by visiting chrome://flags/ and enabling 'unsafely-treat-insecure-origin-as-secure'"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
this.setupTransceiversAsync(useMic).finally(() => {
|
|
126
|
+
this.peerConnection
|
|
127
|
+
?.createAnswer()
|
|
128
|
+
.then((Answer: RTCSessionDescriptionInit) => {
|
|
129
|
+
Answer.sdp = this.mungeSDP(Answer.sdp, useMic);
|
|
130
|
+
return this.peerConnection?.setLocalDescription(Answer);
|
|
131
|
+
})
|
|
132
|
+
.then(() => {
|
|
133
|
+
this.onSendWebRTCAnswer(
|
|
134
|
+
this.peerConnection?.currentLocalDescription
|
|
135
|
+
);
|
|
136
|
+
})
|
|
137
|
+
.catch(() => {
|
|
138
|
+
Logger.Error(
|
|
139
|
+
Logger.GetStackTrace(),
|
|
140
|
+
'createAnswer() failed'
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
|
|
147
|
+
this.config.setOptionSettingOptions(
|
|
148
|
+
OptionParameters.PreferredCodec,
|
|
149
|
+
this.parseAvailableCodecs(offer).filter((value) =>
|
|
150
|
+
this.config
|
|
151
|
+
.getSettingOption(OptionParameters.PreferredCodec)
|
|
152
|
+
.options.includes(value)
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Set the Remote Descriptor from the signaling server to the RTC Peer Connection
|
|
159
|
+
* @param answer - RTC Session Descriptor from the Signaling Server
|
|
160
|
+
*/
|
|
161
|
+
receiveAnswer(answer: RTCSessionDescriptionInit) {
|
|
162
|
+
this.peerConnection?.setRemoteDescription(answer);
|
|
163
|
+
// Ugly syntax, but this achieves the intersection of the browser supported list and the UE supported list
|
|
164
|
+
this.config.setOptionSettingOptions(
|
|
165
|
+
OptionParameters.PreferredCodec,
|
|
166
|
+
this.parseAvailableCodecs(answer).filter((value) =>
|
|
167
|
+
this.config
|
|
168
|
+
.getSettingOption(OptionParameters.PreferredCodec)
|
|
169
|
+
.options.includes(value)
|
|
170
|
+
)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Generate Aggregated Stats and then fire a onVideo Stats event
|
|
176
|
+
*/
|
|
177
|
+
generateStats() {
|
|
178
|
+
this.peerConnection?.getStats(null).then((StatsData: RTCStatsReport) => {
|
|
179
|
+
this.aggregatedStats.processStats(StatsData);
|
|
180
|
+
this.onVideoStats(this.aggregatedStats);
|
|
181
|
+
|
|
182
|
+
// Update the preferred codec selection based on what was actually negotiated
|
|
183
|
+
if (this.updateCodecSelection && !!this.aggregatedStats.inboundVideoStats.codecId) {
|
|
184
|
+
this.config.setOptionSettingValue(
|
|
185
|
+
OptionParameters.PreferredCodec,
|
|
186
|
+
this.aggregatedStats.codecs.get(
|
|
187
|
+
this.aggregatedStats.inboundVideoStats.codecId
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Close The Peer Connection
|
|
196
|
+
*/
|
|
197
|
+
close() {
|
|
198
|
+
if (this.peerConnection) {
|
|
199
|
+
this.peerConnection.close();
|
|
200
|
+
this.peerConnection = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Modify the Session Descriptor
|
|
206
|
+
* @param sdp - Session Descriptor as a string
|
|
207
|
+
* @param useMic - Is the microphone in use
|
|
208
|
+
* @returns A modified Session Descriptor
|
|
209
|
+
*/
|
|
210
|
+
mungeSDP(sdp: string, useMic: boolean) {
|
|
211
|
+
let mungedSDP = sdp.replace(
|
|
212
|
+
/(a=fmtp:\d+ .*level-asymmetry-allowed=.*)\r\n/gm,
|
|
213
|
+
'$1;x-google-start-bitrate=10000;x-google-max-bitrate=100000\r\n'
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// set max bitrate to highest bitrate Opus supports
|
|
217
|
+
let audioSDP = 'maxaveragebitrate=510000;';
|
|
218
|
+
|
|
219
|
+
if (useMic) {
|
|
220
|
+
// set the max capture rate to 48khz (so we can send high quality audio from mic)
|
|
221
|
+
audioSDP += 'sprop-maxcapturerate=48000;';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Force mono or stereo based on whether ?forceMono was passed or not
|
|
225
|
+
audioSDP += this.config.isFlagEnabled(Flags.ForceMonoAudio)
|
|
226
|
+
? 'stereo=0;'
|
|
227
|
+
: 'stereo=1;';
|
|
228
|
+
|
|
229
|
+
// enable in-band forward error correction for opus audio
|
|
230
|
+
audioSDP += 'useinbandfec=1';
|
|
231
|
+
|
|
232
|
+
// We use the line 'useinbandfec=1' (which Opus uses) to set our Opus specific audio parameters.
|
|
233
|
+
mungedSDP = mungedSDP.replace('useinbandfec=1', audioSDP);
|
|
234
|
+
|
|
235
|
+
return mungedSDP;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* When a Ice Candidate is received add to the RTC Peer Connection
|
|
240
|
+
* @param iceCandidate - RTC Ice Candidate from the Signaling Server
|
|
241
|
+
*/
|
|
242
|
+
handleOnIce(iceCandidate: RTCIceCandidate) {
|
|
243
|
+
Logger.Log(Logger.GetStackTrace(), 'peerconnection handleOnIce', 6);
|
|
244
|
+
|
|
245
|
+
// // if forcing TURN, reject any candidates not relay
|
|
246
|
+
if (this.config.isFlagEnabled(Flags.ForceTURN)) {
|
|
247
|
+
// check if no relay address is found, if so, we are assuming it means no TURN server
|
|
248
|
+
if (iceCandidate.candidate.indexOf('relay') < 0) {
|
|
249
|
+
Logger.Info(
|
|
250
|
+
Logger.GetStackTrace(),
|
|
251
|
+
`Dropping candidate because it was not TURN relay. | Type= ${iceCandidate.type} | Protocol= ${iceCandidate.protocol} | Address=${iceCandidate.address} | Port=${iceCandidate.port} |`,
|
|
252
|
+
6
|
|
253
|
+
);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
this.peerConnection?.addIceCandidate(iceCandidate);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* When the RTC Peer Connection Signaling server state Changes
|
|
263
|
+
* @param state - Signaling Server State Change Event
|
|
264
|
+
*/
|
|
265
|
+
handleSignalStateChange(state: Event) {
|
|
266
|
+
Logger.Log(
|
|
267
|
+
Logger.GetStackTrace(),
|
|
268
|
+
'signaling state change: ' + state,
|
|
269
|
+
6
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Handle when the Ice Connection State Changes
|
|
275
|
+
* @param state - Ice Connection State
|
|
276
|
+
*/
|
|
277
|
+
handleIceConnectionStateChange(state: Event) {
|
|
278
|
+
Logger.Log(
|
|
279
|
+
Logger.GetStackTrace(),
|
|
280
|
+
'ice connection state change: ' + state,
|
|
281
|
+
6
|
|
282
|
+
);
|
|
283
|
+
this.onIceConnectionStateChange(state);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Handle when the Ice Gathering State Changes
|
|
288
|
+
* @param state - Ice Gathering State Change
|
|
289
|
+
*/
|
|
290
|
+
handleIceGatheringStateChange(state: Event) {
|
|
291
|
+
Logger.Log(
|
|
292
|
+
Logger.GetStackTrace(),
|
|
293
|
+
'ice gathering state change: ' + JSON.stringify(state),
|
|
294
|
+
6
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Activates the onTrack method
|
|
300
|
+
* @param event - The webRtc track event
|
|
301
|
+
*/
|
|
302
|
+
handleOnTrack(event: RTCTrackEvent) {
|
|
303
|
+
this.onTrack(event);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Activates the onPeerIceCandidate
|
|
308
|
+
* @param event - The peer ice candidate
|
|
309
|
+
*/
|
|
310
|
+
handleIceCandidate(event: RTCPeerConnectionIceEvent) {
|
|
311
|
+
this.onPeerIceCandidate(event);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Activates the onDataChannel
|
|
316
|
+
* @param event - The peer's data channel
|
|
317
|
+
*/
|
|
318
|
+
handleDataChannel(event: RTCDataChannelEvent) {
|
|
319
|
+
this.onDataChannel(event);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* An override method for onTrack for use outside of the PeerConnectionController
|
|
324
|
+
* @param trackEvent - The webRtc track event
|
|
325
|
+
*/
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
327
|
+
onTrack(trackEvent: RTCTrackEvent) {
|
|
328
|
+
// Default Functionality: Do Nothing
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* An override method for onIceConnectionStateChange for use outside of the PeerConnectionController
|
|
333
|
+
* @param event - The webRtc iceconnectionstatechange event
|
|
334
|
+
*/
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
336
|
+
onIceConnectionStateChange(event: Event) {
|
|
337
|
+
// Default Functionality: Do Nothing
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* An override method for onPeerIceCandidate for use outside of the PeerConnectionController
|
|
342
|
+
* @param peerConnectionIceEvent - The peer ice candidate
|
|
343
|
+
*/
|
|
344
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
345
|
+
onPeerIceCandidate(peerConnectionIceEvent: RTCPeerConnectionIceEvent) {
|
|
346
|
+
// Default Functionality: Do Nothing
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* An override method for onDataChannel for use outside of the PeerConnectionController
|
|
351
|
+
* @param datachannelEvent - The peer's data channel
|
|
352
|
+
*/
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
354
|
+
onDataChannel(datachannelEvent: RTCDataChannelEvent) {
|
|
355
|
+
// Default Functionality: Do Nothing
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Setup tracks on the RTC Peer Connection
|
|
360
|
+
* @param useMic - is mic in use
|
|
361
|
+
*/
|
|
362
|
+
async setupTransceiversAsync(useMic: boolean) {
|
|
363
|
+
const hasTransceivers =
|
|
364
|
+
this.peerConnection?.getTransceivers().length > 0;
|
|
365
|
+
|
|
366
|
+
// Setup a transceiver for getting UE video
|
|
367
|
+
this.peerConnection?.addTransceiver('video', { direction: 'recvonly' });
|
|
368
|
+
|
|
369
|
+
// We can only set preferrec codec on Chrome
|
|
370
|
+
if (RTCRtpReceiver.getCapabilities && this.preferredCodec != '') {
|
|
371
|
+
for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
|
|
372
|
+
if (
|
|
373
|
+
transceiver &&
|
|
374
|
+
transceiver.receiver &&
|
|
375
|
+
transceiver.receiver.track &&
|
|
376
|
+
transceiver.receiver.track.kind === 'video' &&
|
|
377
|
+
// As of 06/2023, FireFox has added RTCRtpReceiver.getCapabilities, but hasn't added the ability to set codec preferences
|
|
378
|
+
transceiver.setCodecPreferences
|
|
379
|
+
) {
|
|
380
|
+
const preferredRTPCodec = this.preferredCodec.split(' ');
|
|
381
|
+
const codecs = [
|
|
382
|
+
{
|
|
383
|
+
mimeType:
|
|
384
|
+
'video/' + preferredRTPCodec[0] /* Name */,
|
|
385
|
+
clockRate: 90000,
|
|
386
|
+
sdpFmtpLine: preferredRTPCodec[1] /* sdpFmtpLine */
|
|
387
|
+
? preferredRTPCodec[1]
|
|
388
|
+
: ''
|
|
389
|
+
}
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
this.config
|
|
393
|
+
.getSettingOption(OptionParameters.PreferredCodec)
|
|
394
|
+
.options.filter((option) => {
|
|
395
|
+
// Remove the preferred codec from the list of possible codecs as we've set it already
|
|
396
|
+
return option != this.preferredCodec;
|
|
397
|
+
})
|
|
398
|
+
.forEach((option) => {
|
|
399
|
+
// Ammend the rest of the browsers supported codecs
|
|
400
|
+
const altCodec = option.split(' ');
|
|
401
|
+
codecs.push({
|
|
402
|
+
mimeType: 'video/' + altCodec[0] /* Name */,
|
|
403
|
+
clockRate: 90000,
|
|
404
|
+
sdpFmtpLine: altCodec[1] /* sdpFmtpLine */
|
|
405
|
+
? altCodec[1]
|
|
406
|
+
: ''
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
for (const codec of codecs) {
|
|
411
|
+
if (codec.sdpFmtpLine === '') {
|
|
412
|
+
// We can't dynamically add members to the codec, so instead remove the field if it's empty
|
|
413
|
+
delete codec.sdpFmtpLine;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
transceiver.setCodecPreferences(codecs);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Setup a transceiver for sending mic audio to UE and receiving audio from UE
|
|
423
|
+
if (!useMic) {
|
|
424
|
+
this.peerConnection?.addTransceiver('audio', {
|
|
425
|
+
direction: 'recvonly'
|
|
426
|
+
});
|
|
427
|
+
} else {
|
|
428
|
+
// set the audio options based on mic usage
|
|
429
|
+
const audioOptions = {
|
|
430
|
+
autoGainControl: false,
|
|
431
|
+
channelCount: 1,
|
|
432
|
+
echoCancellation: false,
|
|
433
|
+
latency: 0,
|
|
434
|
+
noiseSuppression: false,
|
|
435
|
+
sampleRate: 48000,
|
|
436
|
+
sampleSize: 16,
|
|
437
|
+
volume: 1.0
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// set the media send options
|
|
441
|
+
const mediaSendOptions: MediaStreamConstraints = {
|
|
442
|
+
video: false,
|
|
443
|
+
audio: audioOptions
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// Note using mic on android chrome requires SSL or chrome://flags/ "unsafely-treat-insecure-origin-as-secure"
|
|
447
|
+
const stream = await navigator.mediaDevices.getUserMedia(
|
|
448
|
+
mediaSendOptions
|
|
449
|
+
);
|
|
450
|
+
if (stream) {
|
|
451
|
+
if (hasTransceivers) {
|
|
452
|
+
for (const transceiver of this.peerConnection?.getTransceivers() ?? []) {
|
|
453
|
+
if (RTCUtils.canTransceiverReceiveAudio(transceiver)) {
|
|
454
|
+
for (const track of stream.getTracks()) {
|
|
455
|
+
if (track.kind && track.kind == 'audio') {
|
|
456
|
+
transceiver.sender.replaceTrack(track);
|
|
457
|
+
transceiver.direction = 'sendrecv';
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} else {
|
|
463
|
+
for (const track of stream.getTracks()) {
|
|
464
|
+
if (track.kind && track.kind == 'audio') {
|
|
465
|
+
this.peerConnection?.addTransceiver(track, {
|
|
466
|
+
direction: 'sendrecv'
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
this.peerConnection?.addTransceiver('audio', {
|
|
473
|
+
direction: 'recvonly'
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* And override event for when the video stats are fired
|
|
481
|
+
* @param event - Aggregated Stats
|
|
482
|
+
*/
|
|
483
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
484
|
+
onVideoStats(event: AggregatedStats) {
|
|
485
|
+
// Default Functionality: Do Nothing
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Event to send the RTC offer to the Signaling server
|
|
490
|
+
* @param offer - RTC Offer
|
|
491
|
+
*/
|
|
492
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
493
|
+
onSendWebRTCOffer(offer: RTCSessionDescriptionInit) {
|
|
494
|
+
// Default Functionality: Do Nothing
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Event to send the RTC Answer to the Signaling server
|
|
499
|
+
* @param answer - RTC Answer
|
|
500
|
+
*/
|
|
501
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
502
|
+
onSendWebRTCAnswer(answer: RTCSessionDescriptionInit) {
|
|
503
|
+
// Default Functionality: Do Nothing
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* An override for showing the Peer connection connecting Overlay
|
|
508
|
+
*/
|
|
509
|
+
showTextOverlayConnecting() {
|
|
510
|
+
// Default Functionality: Do Nothing
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* An override for showing the Peer connection Failed overlay
|
|
515
|
+
*/
|
|
516
|
+
showTextOverlaySetupFailure() {
|
|
517
|
+
// Default Functionality: Do Nothing
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
parseAvailableCodecs(
|
|
521
|
+
rtcSessionDescription: RTCSessionDescriptionInit
|
|
522
|
+
): Array<string> {
|
|
523
|
+
// No point in updating the available codecs if on FF
|
|
524
|
+
if (!RTCRtpReceiver.getCapabilities)
|
|
525
|
+
return ['Only available on Chrome'];
|
|
526
|
+
|
|
527
|
+
const ueSupportedCodecs: Array<string> = [];
|
|
528
|
+
const sections = splitSections(rtcSessionDescription.sdp);
|
|
529
|
+
// discard the session information as we only want media related info
|
|
530
|
+
sections.shift();
|
|
531
|
+
sections.forEach((mediaSection) => {
|
|
532
|
+
const { codecs } = parseRtpParameters(mediaSection);
|
|
533
|
+
// Filter only for VPX / H26X / AV1
|
|
534
|
+
const matcher = /(VP\d|H26\d|AV1).*/;
|
|
535
|
+
codecs.forEach((c) => {
|
|
536
|
+
const str =
|
|
537
|
+
c.name +
|
|
538
|
+
' ' +
|
|
539
|
+
Object.keys(c.parameters || {})
|
|
540
|
+
.map((p) => p + '=' + c.parameters[p])
|
|
541
|
+
.join(';');
|
|
542
|
+
const match = matcher.exec(str);
|
|
543
|
+
if (match !== null) {
|
|
544
|
+
if (c.name == 'VP9') {
|
|
545
|
+
// UE answers don't specify profile but we know we want profile 0
|
|
546
|
+
c.parameters = {
|
|
547
|
+
'profile-id': '0'
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
const codecStr: string =
|
|
551
|
+
c.name +
|
|
552
|
+
' ' +
|
|
553
|
+
Object.keys(c.parameters || {})
|
|
554
|
+
.map((p) => p + '=' + c.parameters[p])
|
|
555
|
+
.join(';');
|
|
556
|
+
ueSupportedCodecs.push(codecStr);
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
return ueSupportedCodecs;
|
|
562
|
+
}
|
|
563
|
+
}
|