dashcam 1.3.4-beta → 1.3.6-beta
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/bin/dashcam.js +12 -8
- package/lib/processManager.js +116 -45
- package/lib/recorder.js +13 -1
- package/lib/websocket/server.js +7 -0
- package/package.json +1 -1
package/bin/dashcam.js
CHANGED
|
@@ -109,7 +109,7 @@ async function recordingAction(options, command) {
|
|
|
109
109
|
}, 2000))
|
|
110
110
|
]);
|
|
111
111
|
if (!hasPermissions) {
|
|
112
|
-
log('\
|
|
112
|
+
log('\nCannot start recording without screen recording permission.');
|
|
113
113
|
process.exit(1);
|
|
114
114
|
}
|
|
115
115
|
|
|
@@ -134,13 +134,17 @@ async function recordingAction(options, command) {
|
|
|
134
134
|
|
|
135
135
|
const result = await Promise.race([startRecordingPromise, timeoutPromise]);
|
|
136
136
|
|
|
137
|
-
log(
|
|
137
|
+
log(`Recording started successfully (PID: ${result.pid})`);
|
|
138
138
|
log(`Output: ${result.outputPath}`);
|
|
139
139
|
log('');
|
|
140
140
|
log('Use "dashcam status" to check progress');
|
|
141
141
|
log('Use "dashcam stop" to stop recording and upload');
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
// Force immediate exit - don't wait for event loop to drain
|
|
144
|
+
// This is necessary when called from scripts/automation
|
|
145
|
+
setImmediate(() => {
|
|
146
|
+
process.exit(0);
|
|
147
|
+
});
|
|
144
148
|
} catch (error) {
|
|
145
149
|
logError('Failed to start recording:', error.message);
|
|
146
150
|
process.exit(1);
|
|
@@ -413,7 +417,7 @@ program
|
|
|
413
417
|
|
|
414
418
|
// Wait for upload to complete (background process handles this)
|
|
415
419
|
logger.debug('Waiting for background upload to complete...');
|
|
416
|
-
console.log('
|
|
420
|
+
console.log('Uploading recording...');
|
|
417
421
|
|
|
418
422
|
// Wait up to 2 minutes for upload result to appear
|
|
419
423
|
const maxWaitForUpload = 120000; // 2 minutes
|
|
@@ -430,7 +434,7 @@ program
|
|
|
430
434
|
logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
|
|
431
435
|
|
|
432
436
|
if (uploadResult && uploadResult.shareLink) {
|
|
433
|
-
console.log('
|
|
437
|
+
console.log('Watch your recording:', uploadResult.shareLink);
|
|
434
438
|
// Clean up the result file now that we've read it
|
|
435
439
|
processManager.cleanup();
|
|
436
440
|
process.exit(0);
|
|
@@ -442,7 +446,7 @@ program
|
|
|
442
446
|
(!result.snapshotPath || fs.existsSync(result.snapshotPath));
|
|
443
447
|
|
|
444
448
|
if (!filesExist) {
|
|
445
|
-
console.log('
|
|
449
|
+
console.log('Recording uploaded by background process');
|
|
446
450
|
logger.info('Files were cleaned up by background process');
|
|
447
451
|
process.exit(0);
|
|
448
452
|
}
|
|
@@ -462,7 +466,7 @@ program
|
|
|
462
466
|
snapshotPath: result.snapshotPath
|
|
463
467
|
});
|
|
464
468
|
|
|
465
|
-
console.log('
|
|
469
|
+
console.log('Watch your recording:', uploadResult.shareLink);
|
|
466
470
|
} catch (uploadError) {
|
|
467
471
|
console.error('Upload failed:', uploadError.message);
|
|
468
472
|
console.log('Recording saved locally:', result.outputPath);
|
|
@@ -677,7 +681,7 @@ program
|
|
|
677
681
|
project: options.project
|
|
678
682
|
});
|
|
679
683
|
|
|
680
|
-
console.log('
|
|
684
|
+
console.log('> Upload complete! Share link:', uploadResult.shareLink);
|
|
681
685
|
process.exit(0);
|
|
682
686
|
|
|
683
687
|
} catch (error) {
|
package/lib/processManager.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
5
6
|
import { logger } from './logger.js';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -148,35 +149,57 @@ class ProcessManager {
|
|
|
148
149
|
|
|
149
150
|
try {
|
|
150
151
|
const status = this.readStatus();
|
|
152
|
+
const pid = this.readPid();
|
|
151
153
|
|
|
152
154
|
if (!status || !status.isRecording) {
|
|
153
155
|
logger.warn('No active recording found');
|
|
154
156
|
return false;
|
|
155
157
|
}
|
|
156
158
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
if (!pid || !this.isProcessRunning(pid)) {
|
|
160
|
+
logger.warn('Background process not running');
|
|
161
|
+
this.cleanup({ preserveResult: true });
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
159
164
|
|
|
160
|
-
logger.info('
|
|
165
|
+
logger.info('Sending stop signal to background process', { pid });
|
|
161
166
|
|
|
162
|
-
//
|
|
163
|
-
|
|
167
|
+
// Send SIGTERM to the background process to trigger graceful shutdown
|
|
168
|
+
try {
|
|
169
|
+
process.kill(pid, 'SIGTERM');
|
|
170
|
+
logger.info('Sent SIGTERM to background process');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.error('Failed to send signal to background process', { error });
|
|
173
|
+
throw new Error('Failed to stop background recording process');
|
|
174
|
+
}
|
|
164
175
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
// Wait for the background process to finish and write results
|
|
177
|
+
logger.debug('Waiting for background process to complete...');
|
|
178
|
+
const maxWaitTime = 30000; // 30 seconds
|
|
179
|
+
const startWait = Date.now();
|
|
169
180
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
completedTime: Date.now(),
|
|
174
|
-
pid: process.pid
|
|
175
|
-
});
|
|
181
|
+
while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
|
|
182
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
183
|
+
}
|
|
176
184
|
|
|
177
|
-
this.
|
|
185
|
+
if (this.isProcessRunning(pid)) {
|
|
186
|
+
logger.error('Background process did not exit within timeout, forcing kill');
|
|
187
|
+
try {
|
|
188
|
+
process.kill(pid, 'SIGKILL');
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.error('Failed to force kill background process', { error });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
logger.info('Background process stopped');
|
|
195
|
+
|
|
196
|
+
// Return a minimal result indicating success
|
|
197
|
+
// The upload will be handled by the stop command checking the result file
|
|
198
|
+
return {
|
|
199
|
+
outputPath: status.outputPath,
|
|
200
|
+
duration: Date.now() - status.startTime
|
|
201
|
+
};
|
|
178
202
|
|
|
179
|
-
return result;
|
|
180
203
|
} catch (error) {
|
|
181
204
|
logger.error('Failed to stop recording', { error });
|
|
182
205
|
throw error;
|
|
@@ -191,42 +214,90 @@ class ProcessManager {
|
|
|
191
214
|
throw new Error('Recording already in progress');
|
|
192
215
|
}
|
|
193
216
|
|
|
194
|
-
//
|
|
195
|
-
const
|
|
217
|
+
// Spawn background process
|
|
218
|
+
const backgroundScriptPath = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
|
|
196
219
|
|
|
197
|
-
logger.info('Starting recording
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
fps: parseInt(options.fps) || 10,
|
|
202
|
-
includeAudio: options.audio || false,
|
|
203
|
-
customOutputPath: options.output || null
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
logger.info('Starting recording with options', { recordingOptions });
|
|
220
|
+
logger.info('Starting background recording process', {
|
|
221
|
+
backgroundScriptPath,
|
|
222
|
+
options
|
|
223
|
+
});
|
|
207
224
|
|
|
208
|
-
|
|
225
|
+
// Serialize options to pass to background process
|
|
226
|
+
const optionsJson = JSON.stringify(options);
|
|
209
227
|
|
|
210
|
-
//
|
|
211
|
-
|
|
228
|
+
// Determine node executable path
|
|
229
|
+
const nodePath = process.execPath;
|
|
230
|
+
|
|
231
|
+
// Create log file for background process output
|
|
232
|
+
const logDir = path.join(PROCESS_DIR, 'logs');
|
|
233
|
+
if (!fs.existsSync(logDir)) {
|
|
234
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
235
|
+
}
|
|
236
|
+
const logFile = path.join(logDir, `recording-${Date.now()}.log`);
|
|
237
|
+
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
212
238
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
logger.info('Background process log file', { logFile });
|
|
240
|
+
|
|
241
|
+
// Spawn the background process with proper detachment
|
|
242
|
+
const child = spawn(
|
|
243
|
+
nodePath,
|
|
244
|
+
[backgroundScriptPath, optionsJson],
|
|
245
|
+
{
|
|
246
|
+
detached: true, // Detach from parent on Unix-like systems
|
|
247
|
+
stdio: ['ignore', logStream, logStream], // Redirect output to log file
|
|
248
|
+
windowsHide: true, // Hide console window on Windows
|
|
249
|
+
shell: false // Don't use shell to avoid extra process wrapper
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Unref to allow parent to exit independently
|
|
254
|
+
child.unref();
|
|
255
|
+
|
|
256
|
+
const pid = child.pid;
|
|
257
|
+
|
|
258
|
+
logger.info('Background process spawned', {
|
|
259
|
+
pid,
|
|
260
|
+
logFile,
|
|
261
|
+
detached: true
|
|
219
262
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
263
|
+
|
|
264
|
+
// Wait for status file to be created by background process
|
|
265
|
+
logger.debug('Waiting for background process to write status file...');
|
|
266
|
+
const maxWaitTime = 10000; // 10 seconds
|
|
267
|
+
const startWait = Date.now();
|
|
268
|
+
let statusCreated = false;
|
|
269
|
+
|
|
270
|
+
while (!statusCreated && (Date.now() - startWait) < maxWaitTime) {
|
|
271
|
+
const status = this.readStatus();
|
|
272
|
+
if (status && status.isRecording && status.pid === pid) {
|
|
273
|
+
statusCreated = true;
|
|
274
|
+
logger.debug('Status file created by background process', { status });
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
// Wait a bit before checking again
|
|
278
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!statusCreated) {
|
|
282
|
+
logger.error('Background process did not create status file within timeout');
|
|
283
|
+
throw new Error('Failed to start background recording process - status file not created. Check log: ' + logFile);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Read the status to get output path and start time
|
|
287
|
+
const status = this.readStatus();
|
|
288
|
+
|
|
289
|
+
logger.info('Recording started successfully in background', {
|
|
290
|
+
pid,
|
|
291
|
+
outputPath: status.outputPath,
|
|
292
|
+
startTime: status.startTime,
|
|
293
|
+
logFile
|
|
224
294
|
});
|
|
225
295
|
|
|
226
296
|
return {
|
|
227
|
-
pid
|
|
228
|
-
outputPath:
|
|
229
|
-
startTime:
|
|
297
|
+
pid,
|
|
298
|
+
outputPath: status.outputPath,
|
|
299
|
+
startTime: status.startTime,
|
|
300
|
+
logFile
|
|
230
301
|
};
|
|
231
302
|
}
|
|
232
303
|
|
package/lib/recorder.js
CHANGED
|
@@ -405,9 +405,21 @@ export async function startRecording({
|
|
|
405
405
|
currentRecording = execa(ffmpegPath, args, {
|
|
406
406
|
reject: false,
|
|
407
407
|
all: true, // Capture both stdout and stderr
|
|
408
|
-
stdin: 'pipe' // Enable stdin for sending 'q' to stop recording
|
|
408
|
+
stdin: 'pipe', // Enable stdin for sending 'q' to stop recording
|
|
409
|
+
detached: false,
|
|
410
|
+
windowsHide: true // Hide the console window on Windows
|
|
409
411
|
});
|
|
410
412
|
|
|
413
|
+
// Unref the child process so it doesn't keep the parent alive
|
|
414
|
+
if (currentRecording.pid) {
|
|
415
|
+
try {
|
|
416
|
+
currentRecording.unref();
|
|
417
|
+
} catch (e) {
|
|
418
|
+
// Ignore errors if unref is not available
|
|
419
|
+
logger.debug('Could not unref ffmpeg process');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
411
423
|
logger.info('FFmpeg process spawned', {
|
|
412
424
|
pid: currentRecording.pid,
|
|
413
425
|
args: args.slice(-5), // Log last 5 args including output file
|
package/lib/websocket/server.js
CHANGED
|
@@ -56,6 +56,13 @@ class WSServer {
|
|
|
56
56
|
const ws = await new Promise((resolve) => {
|
|
57
57
|
logger.debug('WebSocketServer: Trying port ' + port);
|
|
58
58
|
const ws = new WebSocketServer({ port, host: '127.0.0.1' });
|
|
59
|
+
|
|
60
|
+
// Unref the server to prevent it from keeping the process alive
|
|
61
|
+
if (ws._server && ws._server.unref) {
|
|
62
|
+
ws._server.unref();
|
|
63
|
+
logger.debug('WebSocketServer: Unreffed server to allow process exit');
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
ws.on('error', () => {
|
|
60
67
|
logger.debug('WebSocketServer: Failed to listen on port ' + port);
|
|
61
68
|
resolve();
|