remoterigs-raspberrypi 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/index.js +3 -0
- package/package.json +20 -0
- package/src/common/code.js +62 -0
- package/src/common/debug.js +77 -0
- package/src/common/signalr.js +189 -0
- package/src/common/webrtc-streamer-wrapper.js +483 -0
- package/src/component/component.js +358 -0
- package/src/component/input/input.js +26 -0
- package/src/component/pin/pin.js +92 -0
- package/src/component/property/property.js +32 -0
- package/src/component/status/status-number.js +7 -0
- package/src/component/status/status-state.js +27 -0
- package/src/component/status/status.js +41 -0
- package/src/config.json.default +8 -0
- package/src/models/models.js +1045 -0
- package/src/rig.js +65 -0
- package/src/server.js +87 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { LogSettingType, VideoType, WebRTCMessage } from '../models/models.js';
|
|
4
|
+
import SignalR from './signalr.js';
|
|
5
|
+
import Debug from './debug.js';
|
|
6
|
+
export default class WebRTCStreamerWrapper {
|
|
7
|
+
constructor(component, clientRTCConfigurationViewModel, videoType) {
|
|
8
|
+
this.component = component;
|
|
9
|
+
this.clientRTCConfigurationViewModel = clientRTCConfigurationViewModel;
|
|
10
|
+
this.videoType = videoType;
|
|
11
|
+
this.webrtcStreamerProcess = null;
|
|
12
|
+
this.streamerStarted = false;
|
|
13
|
+
// ICE candidate polling
|
|
14
|
+
this.iceCandidatePollers = new Map();
|
|
15
|
+
this.sentCandidates = new Map(); // Track verzonden candidates per peer
|
|
16
|
+
this.ICE_POLLING_INTERVAL = 1000; // Verlaagd naar 1 seconde (was 100ms)
|
|
17
|
+
this.MAX_POLL_ATTEMPTS = 30; // Stop na 30 seconden
|
|
18
|
+
this.STREAMER_PORT = 8000;
|
|
19
|
+
this.STREAMER_BASE_URL = `http://0.0.0.0:${this.STREAMER_PORT}`;
|
|
20
|
+
this.candidateBuffer = new Map();
|
|
21
|
+
this.remoteDescriptionSet = new Set();
|
|
22
|
+
this.streamerClient = axios.create({
|
|
23
|
+
baseURL: this.STREAMER_BASE_URL,
|
|
24
|
+
timeout: 5000,
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json'
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async Start() {
|
|
31
|
+
this.component.SetStatusState("State", "Starting");
|
|
32
|
+
this.Log("Starting webrtc-streamer...");
|
|
33
|
+
await this.StartWebRTCStreamer();
|
|
34
|
+
await this.WaitForStreamer();
|
|
35
|
+
this.Log("webrtc-streamer is ready");
|
|
36
|
+
}
|
|
37
|
+
async Stop() {
|
|
38
|
+
// Stop alle ICE polling
|
|
39
|
+
for (const [peerId, _] of this.iceCandidatePollers) {
|
|
40
|
+
this.StopIceCandidatePolling(peerId);
|
|
41
|
+
}
|
|
42
|
+
// Stop webrtc-streamer process
|
|
43
|
+
if (this.webrtcStreamerProcess) {
|
|
44
|
+
this.webrtcStreamerProcess.kill();
|
|
45
|
+
this.webrtcStreamerProcess = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async PutWebRTCMessage(message) {
|
|
49
|
+
if (this.streamerStarted) {
|
|
50
|
+
this.Log("PutWebRTCMessage username: " + message.username + " messageType: " + message.messageType);
|
|
51
|
+
if (message.messageType == "getIceServers") {
|
|
52
|
+
await this.GetIceServers(message);
|
|
53
|
+
}
|
|
54
|
+
else if (message.messageType == "call") {
|
|
55
|
+
await this.Call(message);
|
|
56
|
+
}
|
|
57
|
+
else if (message.messageType == "createOffer") {
|
|
58
|
+
await this.CreateOffer(message);
|
|
59
|
+
}
|
|
60
|
+
else if (message.messageType == "setAnswer") {
|
|
61
|
+
await this.SetAnswer(message);
|
|
62
|
+
this.remoteDescriptionSet.add(message.sessionId);
|
|
63
|
+
await this.FlushBufferedCandidates(message.sessionId);
|
|
64
|
+
}
|
|
65
|
+
else if (message.messageType == "onReceiveCall") {
|
|
66
|
+
// SDP answer ontvangen, markeer als 'set' en flush buffer
|
|
67
|
+
this.remoteDescriptionSet.add(message.sessionId);
|
|
68
|
+
await this.FlushBufferedCandidates(message.sessionId);
|
|
69
|
+
}
|
|
70
|
+
else if (message.messageType == "addIceCandidate") {
|
|
71
|
+
// Buffer als SDP answer nog niet ontvangen
|
|
72
|
+
if (!this.remoteDescriptionSet.has(message.sessionId)) {
|
|
73
|
+
this.Log(`Buffering ICE candidate for session ${message.sessionId}`);
|
|
74
|
+
if (!this.candidateBuffer.has(message.sessionId)) {
|
|
75
|
+
this.candidateBuffer.set(message.sessionId, []);
|
|
76
|
+
}
|
|
77
|
+
this.candidateBuffer.get(message.sessionId).push(message.message);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
await this.AddIceCandidate(message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (message.messageType == "hangup") {
|
|
84
|
+
await this.Hangup(message);
|
|
85
|
+
// Opruimen na hangup
|
|
86
|
+
this.remoteDescriptionSet.delete(message.sessionId);
|
|
87
|
+
this.candidateBuffer.delete(message.sessionId);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.Log("IGNORED! PutWebRTCMessage username: " + message.username + " messageType: " + message.messageType);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async StartWebRTCStreamer() {
|
|
95
|
+
try {
|
|
96
|
+
const response = await this.streamerClient.get('/api/version');
|
|
97
|
+
this.Log("webrtc-streamer is already running: " + JSON.stringify(response));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.Log("webrtc-streamer not running, starting it...");
|
|
102
|
+
}
|
|
103
|
+
const args = [
|
|
104
|
+
"-H", `0.0.0.0:${this.STREAMER_PORT}`,
|
|
105
|
+
"-s",
|
|
106
|
+
//"--stun", this.clientRTCConfigurationViewModel.stunServer,
|
|
107
|
+
"--turn", this.clientRTCConfigurationViewModel.turnServer
|
|
108
|
+
];
|
|
109
|
+
const command = "webrtc-streamer";
|
|
110
|
+
this.webrtcStreamerProcess = spawn(command, args);
|
|
111
|
+
if (this.webrtcStreamerProcess != null) {
|
|
112
|
+
if (this.webrtcStreamerProcess.stdout != null) {
|
|
113
|
+
this.webrtcStreamerProcess.stdout.on("data", (data) => {
|
|
114
|
+
this.Log(`webrtc-streamer stdout: ${data}`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (this.webrtcStreamerProcess.stderr != null) {
|
|
118
|
+
this.webrtcStreamerProcess.stderr.on("data", (data) => {
|
|
119
|
+
this.Log(`webrtc-streamer stderr: ${data}`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
this.webrtcStreamerProcess.on("error", (err) => {
|
|
123
|
+
this.Error(`webrtc-streamer error: ${JSON.stringify(err)}`);
|
|
124
|
+
});
|
|
125
|
+
this.webrtcStreamerProcess.on("close", (code) => {
|
|
126
|
+
this.Log(`webrtc-streamer exited with code ${code}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
this.Log("Spawned webrtc-streamer process: " + command);
|
|
130
|
+
}
|
|
131
|
+
async WaitForStreamer() {
|
|
132
|
+
const maxRetries = 20;
|
|
133
|
+
const retryDelay = 500;
|
|
134
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
135
|
+
var response = null;
|
|
136
|
+
var skip = false;
|
|
137
|
+
try {
|
|
138
|
+
response = await this.streamerClient.get('/api/version');
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
//this.Log("WaitForStreamer err: " + err);
|
|
142
|
+
skip = true;
|
|
143
|
+
}
|
|
144
|
+
if (response?.data && !skip) {
|
|
145
|
+
this.streamerStarted = true;
|
|
146
|
+
this.component.SetStatusState("State", "Started");
|
|
147
|
+
this.Log("WaitForStreamer version: " + response.data);
|
|
148
|
+
//const getMediaList = await this.streamerClient.get('/api/getMediaList');
|
|
149
|
+
//this.Log("WaitForStreamer getMediaList: " + JSON.stringify(getMediaList.data, null, 2));
|
|
150
|
+
const getVideoDeviceList = await this.streamerClient.get('/api/getVideoDeviceList');
|
|
151
|
+
this.Log("WaitForStreamer getVideoDeviceList: " + getVideoDeviceList.data);
|
|
152
|
+
const getAudioDeviceList = await this.streamerClient.get('/api/getAudioDeviceList');
|
|
153
|
+
this.Log("WaitForStreamer getAudioDeviceList: " + getAudioDeviceList.data);
|
|
154
|
+
//const getAudioPlayoutList = await this.streamerClient.get('/api/getAudioPlayoutList');
|
|
155
|
+
//this.Log("WaitForStreamer getAudioPlayoutList: " + getAudioPlayoutList.data);
|
|
156
|
+
//const getPeerConnectionList = await this.streamerClient.get('/api/getPeerConnectionList');
|
|
157
|
+
//this.Log("WaitForStreamer getPeerConnectionList: " + getPeerConnectionList.data);
|
|
158
|
+
//const getStreamList = await this.streamerClient.get('/api/getStreamList');
|
|
159
|
+
//this.Log("WaitForStreamer getStreamList: " + getStreamList.data);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.Log("WaitForStreamer starting wait: " + i + "/" + maxRetries + " - " + retryDelay + "ms");
|
|
163
|
+
var start = new Date().getTime();
|
|
164
|
+
var end = start;
|
|
165
|
+
while (end < start + retryDelay) {
|
|
166
|
+
end = new Date().getTime();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (this.streamerStarted == false) {
|
|
170
|
+
this.component.SetStatusState("State", "Stopped");
|
|
171
|
+
throw new Error("webrtc-streamer failed to start");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async FlushBufferedCandidates(sessionId) {
|
|
175
|
+
const buffer = this.candidateBuffer.get(sessionId);
|
|
176
|
+
if (buffer && buffer.length > 0) {
|
|
177
|
+
this.Log(`Flushing ${buffer.length} buffered ICE candidates for session ${sessionId}`);
|
|
178
|
+
this.candidateBuffer.delete(sessionId);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async GetIceServers(message) {
|
|
182
|
+
try {
|
|
183
|
+
this.Log("Request Ice servers /api/getIceServers");
|
|
184
|
+
const response = await this.streamerClient.get('/api/getIceServers');
|
|
185
|
+
this.Log("Response status: " + response.status);
|
|
186
|
+
this.Log("Response data: " + JSON.stringify(response.data));
|
|
187
|
+
var webrtcMessage = new WebRTCMessage();
|
|
188
|
+
webrtcMessage.componentId = this.component.component.id;
|
|
189
|
+
webrtcMessage.rigId = message.rigId;
|
|
190
|
+
webrtcMessage.webRTCConnectionType = message.webRTCConnectionType;
|
|
191
|
+
webrtcMessage.sessionId = message.sessionId;
|
|
192
|
+
webrtcMessage.username = message.username;
|
|
193
|
+
webrtcMessage.rigOwnerUsername = message.rigOwnerUsername;
|
|
194
|
+
webrtcMessage.messageType = 'getIceServersResponse';
|
|
195
|
+
webrtcMessage.message = JSON.stringify(response.data);
|
|
196
|
+
SignalR.SendWebRTCMessage(webrtcMessage);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
if (err.response) {
|
|
200
|
+
this.Log("Response status: " + err.response.status);
|
|
201
|
+
this.Log("Response data: " + JSON.stringify(err.response.data));
|
|
202
|
+
}
|
|
203
|
+
this.Error("GetIceServers error: " + err.message);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async Call(message) {
|
|
207
|
+
try {
|
|
208
|
+
var devicePath = this.component.GetProperty("devicepath");
|
|
209
|
+
if (devicePath != null && devicePath != "") {
|
|
210
|
+
var audioPath = this.component.GetProperty("audiopath");
|
|
211
|
+
var audioStr = "";
|
|
212
|
+
if (audioPath != null && audioPath != "") {
|
|
213
|
+
audioStr = "&audiourl=" + encodeURIComponent(audioPath);
|
|
214
|
+
}
|
|
215
|
+
var width = this.component.GetProperty("width");
|
|
216
|
+
var widthStr = "";
|
|
217
|
+
if (width != null && width != "") {
|
|
218
|
+
widthStr = "&width=" + width;
|
|
219
|
+
}
|
|
220
|
+
var height = this.component.GetProperty("height");
|
|
221
|
+
var heightStr = "";
|
|
222
|
+
if (height != null && height != "") {
|
|
223
|
+
heightStr = "&height=" + height;
|
|
224
|
+
}
|
|
225
|
+
var bitrate = this.component.GetProperty("bitrate");
|
|
226
|
+
var bitrateStr = "";
|
|
227
|
+
if (bitrate != null && bitrate != "") {
|
|
228
|
+
bitrateStr = "&bitrate=" + bitrate;
|
|
229
|
+
}
|
|
230
|
+
//let options = encodeURIComponent("rtptransport=tcp&width=1280&height=720&fps=30&bitrate=2000000");
|
|
231
|
+
let callurl = "/api/call?peerid=" + message.sessionId +
|
|
232
|
+
"&url=" + encodeURIComponent(devicePath) +
|
|
233
|
+
audioStr +
|
|
234
|
+
"&options=" + encodeURIComponent("rtptransport=tcp" + widthStr + heightStr + bitrateStr);
|
|
235
|
+
//m_func[basePath + "/api/call"] = [this](const struct mg_request_info * req_info, const Json:: Value &in) -> HttpServerRequestHandler::httpFunctionReturn {
|
|
236
|
+
// std::string peerid = getParam(req_info -> query_string, "peerid");
|
|
237
|
+
// std::string url = getParam(req_info -> query_string, "url");
|
|
238
|
+
// std::string audiourl = getParam(req_info -> query_string, "audiourl");
|
|
239
|
+
// std::string options = getParam(req_info -> query_string, "options");
|
|
240
|
+
// return std:: make_tuple(200, std:: map < std:: string, std:: string > (), this -> call(peerid, url, audiourl, options, in));
|
|
241
|
+
//};
|
|
242
|
+
this.Log("Call: " + callurl);
|
|
243
|
+
const response = await this.streamerClient.post(callurl, message.message);
|
|
244
|
+
this.Log("Response status: " + response.status);
|
|
245
|
+
this.Log("Response data: " + JSON.stringify(response.data));
|
|
246
|
+
// Stuur de answer terug naar de client
|
|
247
|
+
var webrtcMessage = new WebRTCMessage();
|
|
248
|
+
webrtcMessage.componentId = this.component.component.id;
|
|
249
|
+
webrtcMessage.rigId = message.rigId;
|
|
250
|
+
webrtcMessage.sessionId = message.sessionId;
|
|
251
|
+
webrtcMessage.webRTCConnectionType = message.webRTCConnectionType;
|
|
252
|
+
webrtcMessage.username = message.username;
|
|
253
|
+
webrtcMessage.rigOwnerUsername = message.rigOwnerUsername;
|
|
254
|
+
webrtcMessage.messageType = 'onReceiveCall';
|
|
255
|
+
webrtcMessage.message = JSON.stringify(response.data);
|
|
256
|
+
SignalR.SendWebRTCMessage(webrtcMessage);
|
|
257
|
+
// Start polling voor ICE candidates van de server
|
|
258
|
+
this.StartIceCandidatePolling(message.sessionId, message.rigId, message.username, message.rigOwnerUsername, message.webRTCConnectionType);
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
this.Error("devicePath is empty");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
if (err.response) {
|
|
266
|
+
this.Log("Response status: " + err.response.status);
|
|
267
|
+
this.Log("Response data: " + JSON.stringify(err.response.data));
|
|
268
|
+
}
|
|
269
|
+
this.Error("Call error: " + err.message);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async CreateOffer(message) {
|
|
273
|
+
//m_func[basePath + "/api/createOffer"] = [this](const struct mg_request_info * req_info, const Json:: Value &in) -> HttpServerRequestHandler::httpFunctionReturn {
|
|
274
|
+
// std::string peerid = getParam(req_info -> query_string, "peerid");
|
|
275
|
+
// std::string url = getParam(req_info -> query_string, "url");
|
|
276
|
+
// std::string audiourl = getParam(req_info -> query_string, "audiourl");
|
|
277
|
+
// std::string options = getParam(req_info -> query_string, "options");
|
|
278
|
+
// return std:: make_tuple(200, std:: map < std:: string, std:: string > (), this -> createOffer(peerid, url, audiourl, options));
|
|
279
|
+
//};
|
|
280
|
+
try {
|
|
281
|
+
var devicePath = this.component.GetProperty("devicepath");
|
|
282
|
+
if (devicePath != null && devicePath != "") {
|
|
283
|
+
var audioPath = this.component.GetProperty("audiopath");
|
|
284
|
+
var audioStr = "";
|
|
285
|
+
if (audioPath != null && audioPath != "") {
|
|
286
|
+
audioStr = "&audiourl=" + encodeURIComponent(audioPath);
|
|
287
|
+
}
|
|
288
|
+
var width = this.component.GetProperty("width");
|
|
289
|
+
var widthStr = "";
|
|
290
|
+
if (width != null && width != "") {
|
|
291
|
+
widthStr = "&width=" + width;
|
|
292
|
+
}
|
|
293
|
+
var height = this.component.GetProperty("height");
|
|
294
|
+
var heightStr = "";
|
|
295
|
+
if (height != null && height != "") {
|
|
296
|
+
heightStr = "&height=" + height;
|
|
297
|
+
}
|
|
298
|
+
var bitrate = this.component.GetProperty("bitrate");
|
|
299
|
+
var bitrateStr = "";
|
|
300
|
+
if (bitrate != null && bitrate != "") {
|
|
301
|
+
bitrateStr = "&bitrate=" + bitrate;
|
|
302
|
+
}
|
|
303
|
+
let createOfferurl = "/api/createOffer?peerid=" + message.sessionId +
|
|
304
|
+
"&url=" + encodeURIComponent(devicePath) +
|
|
305
|
+
audioStr +
|
|
306
|
+
"&options=" + encodeURIComponent("rtptransport=tcp" + widthStr + heightStr + bitrateStr);
|
|
307
|
+
this.Log("createOffer: " + createOfferurl);
|
|
308
|
+
const response = await this.streamerClient.post(createOfferurl, message.message);
|
|
309
|
+
let sdp = response.data.sdp;
|
|
310
|
+
if (this.videoType == VideoType.H264) {
|
|
311
|
+
// Zet H264 (100+101) als eerste in de m=video regel
|
|
312
|
+
sdp = sdp.replace(/m=video (\S+) (\S+) ([\d ]+)/, (_match, port, proto, payloads) => {
|
|
313
|
+
const ids = payloads.trim().split(' ');
|
|
314
|
+
const preferred = ['100', '101'];
|
|
315
|
+
const rest = ids.filter((id) => !preferred.includes(id));
|
|
316
|
+
return `m=video ${port} ${proto} ${[...preferred, ...rest].join(' ')}`;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
this.Log("CreateOffer Response status: " + response.status);
|
|
320
|
+
this.Log("CreateOffer Response SDP:\\r\\n" + JSON.stringify(sdp));
|
|
321
|
+
// Stuur de answer terug naar de client
|
|
322
|
+
var webrtcMessage = new WebRTCMessage();
|
|
323
|
+
webrtcMessage.componentId = this.component.component.id;
|
|
324
|
+
webrtcMessage.rigId = message.rigId;
|
|
325
|
+
webrtcMessage.webRTCConnectionType = message.webRTCConnectionType;
|
|
326
|
+
webrtcMessage.sessionId = message.sessionId;
|
|
327
|
+
webrtcMessage.username = message.username;
|
|
328
|
+
webrtcMessage.rigOwnerUsername = message.rigOwnerUsername;
|
|
329
|
+
webrtcMessage.messageType = 'receiveOffer';
|
|
330
|
+
//webrtcMessage.message = JSON.stringify(response.data);
|
|
331
|
+
webrtcMessage.message = JSON.stringify({ ...response.data, sdp });
|
|
332
|
+
SignalR.SendWebRTCMessage(webrtcMessage);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
this.Error("devicePath is empty");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
if (err.response) {
|
|
340
|
+
this.Log("CreateOffer Response status: " + err.response.status);
|
|
341
|
+
this.Log("CreateOffer Response data: " + JSON.stringify(err.response.data));
|
|
342
|
+
}
|
|
343
|
+
this.Error("CreateOffer Call error: " + err.message);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async SetAnswer(message) {
|
|
347
|
+
//m_func[basePath + "/api/setAnswer"] = [this](const struct mg_request_info * req_info, const Json:: Value &in) -> HttpServerRequestHandler::httpFunctionReturn {
|
|
348
|
+
// std::string peerid = getParam(req_info -> query_string, "peerid");
|
|
349
|
+
// return std:: make_tuple(200, std:: map < std:: string, std:: string > (), this -> setAnswer(peerid, in));
|
|
350
|
+
//};
|
|
351
|
+
try {
|
|
352
|
+
let setAnswerUrl = "/api/setAnswer?peerid=" + message.sessionId;
|
|
353
|
+
this.Log("setAnswer: " + setAnswerUrl + " message: " + message.message);
|
|
354
|
+
var tJSON = JSON.parse(message.message);
|
|
355
|
+
const response = await this.streamerClient.post(setAnswerUrl, tJSON);
|
|
356
|
+
this.Log("SetAnswer Response status: " + response.status);
|
|
357
|
+
this.Log("SetAnswer Response data:\\r\\n" + JSON.stringify(response.data?.sdp));
|
|
358
|
+
this.StartIceCandidatePolling(message.sessionId, message.rigId, message.username, message.rigOwnerUsername, message.webRTCConnectionType);
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
if (err.response) {
|
|
362
|
+
this.Log("SetAnswer Response status: " + err.response.status);
|
|
363
|
+
this.Log("SetAnswer Response data: " + JSON.stringify(err.response.data));
|
|
364
|
+
}
|
|
365
|
+
this.Error("SetAnswer Call error: " + err.message);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
StartIceCandidatePolling(peerId, rigId, username, rigOwnerUsername, webRTCConnectionType) {
|
|
369
|
+
// Stop eventuele bestaande polling voor deze peer
|
|
370
|
+
this.StopIceCandidatePolling(peerId);
|
|
371
|
+
// Initialiseer candidate tracking voor deze peer
|
|
372
|
+
this.sentCandidates.set(peerId, new Set());
|
|
373
|
+
this.Log(`Starting ICE candidate polling for peer: ${peerId} with WebRTC connection type: ${webRTCConnectionType}`);
|
|
374
|
+
let pollAttempts = 0;
|
|
375
|
+
const poller = setInterval(async () => {
|
|
376
|
+
pollAttempts++;
|
|
377
|
+
// Stop na MAX_POLL_ATTEMPTS
|
|
378
|
+
if (pollAttempts > this.MAX_POLL_ATTEMPTS) {
|
|
379
|
+
this.Log(`Stopping ICE polling for peer ${peerId} after ${pollAttempts} attempts`);
|
|
380
|
+
this.StopIceCandidatePolling(peerId);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
const url = '/api/getIceCandidate?peerid=' + peerId;
|
|
385
|
+
const response = await this.streamerClient.get(url);
|
|
386
|
+
// Als er ICE candidates zijn, filter en stuur ze door
|
|
387
|
+
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
|
|
388
|
+
const sentSet = this.sentCandidates.get(peerId);
|
|
389
|
+
const newCandidates = response.data.filter(candidate => {
|
|
390
|
+
// Maak unieke ID voor deze candidate
|
|
391
|
+
const candidateId = this.GetCandidateId(candidate);
|
|
392
|
+
return !sentSet.has(candidateId);
|
|
393
|
+
});
|
|
394
|
+
if (newCandidates.length > 0) {
|
|
395
|
+
this.Log(`Got ${newCandidates.length} NEW ICE candidates for peer ${peerId} (filtered from ${response.data.length} total)`);
|
|
396
|
+
for (const candidate of newCandidates) {
|
|
397
|
+
const candidateId = this.GetCandidateId(candidate);
|
|
398
|
+
sentSet.add(candidateId);
|
|
399
|
+
const webrtcMessage = new WebRTCMessage();
|
|
400
|
+
webrtcMessage.componentId = this.component.component.id;
|
|
401
|
+
webrtcMessage.rigId = rigId;
|
|
402
|
+
webrtcMessage.sessionId = peerId;
|
|
403
|
+
webrtcMessage.webRTCConnectionType = webRTCConnectionType;
|
|
404
|
+
webrtcMessage.username = username;
|
|
405
|
+
webrtcMessage.rigOwnerUsername = rigOwnerUsername;
|
|
406
|
+
webrtcMessage.messageType = 'onReceiveCandidate';
|
|
407
|
+
webrtcMessage.message = JSON.stringify(candidate);
|
|
408
|
+
SignalR.SendWebRTCMessage(webrtcMessage);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
// Negeer 404 fouten (geen candidates beschikbaar)
|
|
415
|
+
if (err.response?.status !== 404) {
|
|
416
|
+
this.Log(`ICE polling error for ${peerId}: ${err.message}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}, this.ICE_POLLING_INTERVAL);
|
|
420
|
+
this.iceCandidatePollers.set(peerId, poller);
|
|
421
|
+
}
|
|
422
|
+
GetCandidateId(candidate) {
|
|
423
|
+
// Maak een unieke ID voor deze candidate gebaseerd op de kandidaat string
|
|
424
|
+
// Dit voorkomt dat we dezelfde candidate meerdere keren versturen
|
|
425
|
+
if (candidate && candidate.candidate) {
|
|
426
|
+
return candidate.candidate;
|
|
427
|
+
}
|
|
428
|
+
return JSON.stringify(candidate);
|
|
429
|
+
}
|
|
430
|
+
StopIceCandidatePolling(peerId) {
|
|
431
|
+
const poller = this.iceCandidatePollers.get(peerId);
|
|
432
|
+
if (poller) {
|
|
433
|
+
clearInterval(poller);
|
|
434
|
+
this.iceCandidatePollers.delete(peerId);
|
|
435
|
+
this.sentCandidates.delete(peerId);
|
|
436
|
+
this.Log(`Stopped ICE candidate polling for peer: ${peerId}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async AddIceCandidate(message) {
|
|
440
|
+
try {
|
|
441
|
+
var url = "/api/addIceCandidate?peerid=" + message.sessionId;
|
|
442
|
+
var messageObj = JSON.parse(message.message);
|
|
443
|
+
if (messageObj.candidate != null && messageObj.candidate != "") {
|
|
444
|
+
this.Log("AddIceCandidate: " + url + " - " + messageObj.candidate);
|
|
445
|
+
await this.streamerClient.post(url, message.message);
|
|
446
|
+
this.Log("ICE candidate added successfully");
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
this.Log("AddIceCandidate: ignoring empty candidate");
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (err) {
|
|
453
|
+
if (err.response) {
|
|
454
|
+
this.Log("AddIceCandidate error Response status: " + err.response.status + " - data: " + JSON.stringify(err.response.data));
|
|
455
|
+
}
|
|
456
|
+
this.Log("AddIceCandidate error: " + err.message);
|
|
457
|
+
this.Log("AddIceCandidate message: " + JSON.stringify(message));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async Hangup(message) {
|
|
461
|
+
try {
|
|
462
|
+
const url = '/api/hangup?peerid=' + message.sessionId;
|
|
463
|
+
this.Log("Hangup: " + url);
|
|
464
|
+
// Stop ICE polling voor deze peer
|
|
465
|
+
this.StopIceCandidatePolling(message.sessionId);
|
|
466
|
+
await this.streamerClient.get(url);
|
|
467
|
+
this.Log("Hangup successful");
|
|
468
|
+
}
|
|
469
|
+
catch (err) {
|
|
470
|
+
this.Error("Hangup error: " + err.message);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
GetName() {
|
|
474
|
+
return "#" + this.component.component.id + " WebcamStreamer";
|
|
475
|
+
}
|
|
476
|
+
Log(message) {
|
|
477
|
+
Debug.Log(LogSettingType.Component, this.component.component.id, this.GetName(), message);
|
|
478
|
+
}
|
|
479
|
+
Error(message) {
|
|
480
|
+
Debug.Error(this.GetName(), message);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
//# sourceMappingURL=webrtc-streamer-wrapper.js.map
|