remoterigs-raspberrypi 0.1.12 → 0.1.14

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "remoterigs-raspberrypi",
3
3
  "type": "module",
4
- "version": "0.1.12",
4
+ "version": "0.1.14",
5
5
  "main": "./index.js",
6
6
  "files": [
7
7
  "index.js",
@@ -1,9 +1,13 @@
1
- import { spawn } from 'child_process';
1
+ import { spawn, spawnSync } from 'child_process';
2
2
  import axios from 'axios';
3
3
  import { LogSettingType, VideoType, WebRTCMessage } from '../models/models.js';
4
4
  import SignalR from './signalr.js';
5
5
  import Debug from './debug.js';
6
6
  export default class WebRTCStreamerWrapper {
7
+ // one webrtc-streamer process for the whole client: every sign in creates new components (and wrappers),
8
+ // the process is only restarted when the TURN credential changed
9
+ static { this.sharedProcess = null; }
10
+ static { this.sharedTurnServer = null; }
7
11
  constructor(component, clientRTCConfigurationViewModel, videoType) {
8
12
  this.component = component;
9
13
  this.clientRTCConfigurationViewModel = clientRTCConfigurationViewModel;
@@ -41,6 +45,10 @@ export default class WebRTCStreamerWrapper {
41
45
  }
42
46
  // Stop webrtc-streamer process
43
47
  if (this.webrtcStreamerProcess) {
48
+ if (WebRTCStreamerWrapper.sharedProcess === this.webrtcStreamerProcess) {
49
+ WebRTCStreamerWrapper.sharedProcess = null;
50
+ WebRTCStreamerWrapper.sharedTurnServer = null;
51
+ }
44
52
  this.webrtcStreamerProcess.kill();
45
53
  this.webrtcStreamerProcess = null;
46
54
  }
@@ -92,42 +100,100 @@ export default class WebRTCStreamerWrapper {
92
100
  }
93
101
  }
94
102
  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;
103
+ const turnServer = this.clientRTCConfigurationViewModel.turnServer ?? "";
104
+ const running = WebRTCStreamerWrapper.sharedProcess;
105
+ if (running != null && running.exitCode === null && running.signalCode === null) {
106
+ if (WebRTCStreamerWrapper.sharedTurnServer === turnServer) {
107
+ this.Log("webrtc-streamer is already running with the current TURN credential");
108
+ this.webrtcStreamerProcess = running;
109
+ return;
110
+ }
111
+ // a new sign in brought a new TURN credential (the old one may have been deleted at Metered)
112
+ this.Log("TURN credential changed, restarting webrtc-streamer...");
113
+ await WebRTCStreamerWrapper.StopProcess(running);
99
114
  }
100
- catch (err) {
115
+ else if (await this.IsStreamerResponding()) {
116
+ // started by someone else: its TURN credential is unknown, so restart it with the current one
117
+ this.Log("webrtc-streamer is running from an unknown streamerProcess, restarting it with the current TURN credential...");
118
+ spawnSync("pkill", ["-x", "webrtc-streamer"]);
119
+ await this.WaitUntilStreamerStopped();
120
+ }
121
+ else {
101
122
  this.Log("webrtc-streamer not running, starting it...");
102
123
  }
103
124
  const args = [
104
125
  "-H", `0.0.0.0:${this.STREAMER_PORT}`,
105
126
  "-s",
106
127
  //"--stun", this.clientRTCConfigurationViewModel.stunServer,
107
- "--turn", this.clientRTCConfigurationViewModel.turnServer
108
128
  ];
129
+ if (turnServer != "") {
130
+ args.push("--turn", turnServer);
131
+ }
132
+ else {
133
+ this.Log("no TURN credential received from the server, starting webrtc-streamer without TURN");
134
+ }
109
135
  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)}`);
136
+ const streamerProcess = spawn(command, args);
137
+ this.webrtcStreamerProcess = streamerProcess;
138
+ WebRTCStreamerWrapper.sharedProcess = streamerProcess;
139
+ WebRTCStreamerWrapper.sharedTurnServer = turnServer;
140
+ if (streamerProcess.stdout != null) {
141
+ streamerProcess.stdout.on("data", (data) => {
142
+ this.Log(`webrtc-streamer stdout: ${data}`);
124
143
  });
125
- this.webrtcStreamerProcess.on("close", (code) => {
126
- this.Log(`webrtc-streamer exited with code ${code}`);
144
+ }
145
+ if (streamerProcess.stderr != null) {
146
+ streamerProcess.stderr.on("data", (data) => {
147
+ this.Log(`webrtc-streamer stderr: ${data}`);
127
148
  });
128
149
  }
150
+ streamerProcess.on("error", (err) => {
151
+ this.Error(`webrtc-streamer error: ${JSON.stringify(err)}`);
152
+ });
153
+ streamerProcess.on("close", (code) => {
154
+ this.Log(`webrtc-streamer exited with code ${code}`);
155
+ if (WebRTCStreamerWrapper.sharedProcess === streamerProcess) {
156
+ WebRTCStreamerWrapper.sharedProcess = null;
157
+ WebRTCStreamerWrapper.sharedTurnServer = null;
158
+ }
159
+ });
129
160
  this.Log("Spawned webrtc-streamer process: " + command);
130
161
  }
162
+ async IsStreamerResponding() {
163
+ try {
164
+ await this.streamerClient.get('/api/version');
165
+ return true;
166
+ }
167
+ catch (err) {
168
+ return false;
169
+ }
170
+ }
171
+ async WaitUntilStreamerStopped() {
172
+ for (let i = 0; i < 20; i++) {
173
+ if (!(await this.IsStreamerResponding())) {
174
+ return;
175
+ }
176
+ await new Promise(resolve => setTimeout(resolve, 250));
177
+ }
178
+ this.Error("webrtc-streamer did not stop within 5 seconds");
179
+ }
180
+ static StopProcess(streamerProcess) {
181
+ return new Promise(resolve => {
182
+ if (streamerProcess.exitCode !== null || streamerProcess.signalCode !== null) {
183
+ resolve();
184
+ return;
185
+ }
186
+ const timer = setTimeout(() => {
187
+ streamerProcess.kill("SIGKILL");
188
+ resolve();
189
+ }, 5000);
190
+ streamerProcess.once("exit", () => {
191
+ clearTimeout(timer);
192
+ resolve();
193
+ });
194
+ streamerProcess.kill();
195
+ });
196
+ }
131
197
  async WaitForStreamer() {
132
198
  const maxRetries = 20;
133
199
  const retryDelay = 500;
@@ -173,9 +239,16 @@ export default class WebRTCStreamerWrapper {
173
239
  }
174
240
  async FlushBufferedCandidates(sessionId) {
175
241
  const buffer = this.candidateBuffer.get(sessionId);
242
+ this.candidateBuffer.delete(sessionId);
176
243
  if (buffer && buffer.length > 0) {
177
244
  this.Log(`Flushing ${buffer.length} buffered ICE candidates for session ${sessionId}`);
178
- this.candidateBuffer.delete(sessionId);
245
+ // the buffered candidates were never added: add them now that the remote description is set
246
+ for (const candidateMessage of buffer) {
247
+ const message = new WebRTCMessage();
248
+ message.sessionId = sessionId;
249
+ message.message = candidateMessage;
250
+ await this.AddIceCandidate(message);
251
+ }
179
252
  }
180
253
  }
181
254
  async GetIceServers(message) {