dashcam 1.0.1-beta.26 → 1.0.1-beta.27
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 +50 -1
- package/lib/processManager.js +81 -94
- package/package.json +1 -1
- package/test_workflow.sh +9 -5
package/bin/dashcam.js
CHANGED
|
@@ -136,7 +136,56 @@ async function recordingAction(options, command) {
|
|
|
136
136
|
log('Use "dashcam status" to check progress');
|
|
137
137
|
log('Use "dashcam stop" to stop recording and upload');
|
|
138
138
|
|
|
139
|
-
process
|
|
139
|
+
// Keep the process alive so recording continues
|
|
140
|
+
// Set up graceful shutdown handlers
|
|
141
|
+
const handleShutdown = async (signal) => {
|
|
142
|
+
log(`\nReceived ${signal}, stopping recording...`);
|
|
143
|
+
try {
|
|
144
|
+
const result = await processManager.stopActiveRecording();
|
|
145
|
+
|
|
146
|
+
if (result) {
|
|
147
|
+
log('Recording stopped successfully');
|
|
148
|
+
log('Uploading recording...');
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const uploadResult = await upload(result.outputPath, {
|
|
152
|
+
title: options.title || 'Dashcam Recording',
|
|
153
|
+
description: description,
|
|
154
|
+
project: options.project || options.k,
|
|
155
|
+
duration: result.duration,
|
|
156
|
+
clientStartDate: result.clientStartDate,
|
|
157
|
+
apps: result.apps,
|
|
158
|
+
icons: result.icons,
|
|
159
|
+
gifPath: result.gifPath,
|
|
160
|
+
snapshotPath: result.snapshotPath
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Write upload result for stop command to read
|
|
164
|
+
processManager.writeUploadResult({
|
|
165
|
+
shareLink: uploadResult.shareLink,
|
|
166
|
+
replayId: uploadResult.replay?.id
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
log('📹 Watch your recording:', uploadResult.shareLink);
|
|
170
|
+
} catch (uploadError) {
|
|
171
|
+
logError('Upload failed:', uploadError.message);
|
|
172
|
+
log('Recording saved locally:', result.outputPath);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
process.exit(0);
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logError('Failed to stop recording:', error.message);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
process.on('SIGINT', handleShutdown);
|
|
184
|
+
process.on('SIGTERM', handleShutdown);
|
|
185
|
+
|
|
186
|
+
// Keep process alive indefinitely until stopped
|
|
187
|
+
await new Promise(() => {}); // Wait forever
|
|
188
|
+
|
|
140
189
|
} catch (error) {
|
|
141
190
|
logError('Failed to start recording:', error.message);
|
|
142
191
|
process.exit(1);
|
package/lib/processManager.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
1
|
import fs from 'fs';
|
|
3
2
|
import path from 'path';
|
|
4
3
|
import os from 'os';
|
|
5
|
-
import { fileURLToPath } from 'url';
|
|
6
4
|
import { logger } from './logger.js';
|
|
7
5
|
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
6
|
// Use a fixed directory in the user's home directory for cross-process communication
|
|
12
7
|
const PROCESS_DIR = path.join(os.homedir(), '.dashcam-cli');
|
|
13
8
|
const PID_FILE = path.join(PROCESS_DIR, 'recording.pid');
|
|
@@ -156,48 +151,66 @@ class ProcessManager {
|
|
|
156
151
|
return false;
|
|
157
152
|
}
|
|
158
153
|
|
|
159
|
-
//
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const maxWaitTime = 120000; // 2 minutes max to allow for upload
|
|
166
|
-
const startWait = Date.now();
|
|
167
|
-
|
|
168
|
-
while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
|
|
169
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (this.isProcessRunning(pid)) {
|
|
173
|
-
logger.warn('Process did not stop within timeout, forcing termination');
|
|
174
|
-
process.kill(pid, 'SIGKILL');
|
|
175
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// The background process handles stopRecording() internally via SIGINT
|
|
179
|
-
// We just need to return the basic result from the status file
|
|
180
|
-
if (status) {
|
|
181
|
-
logger.info('Background recording stopped, returning status', {
|
|
182
|
-
outputPath: status.outputPath,
|
|
183
|
-
duration: Date.now() - status.startTime
|
|
184
|
-
});
|
|
154
|
+
// Check if this is the same process (direct recording)
|
|
155
|
+
if (pid === process.pid) {
|
|
156
|
+
logger.info('Stopping recording in current process');
|
|
157
|
+
|
|
158
|
+
// Import recorder module
|
|
159
|
+
const { stopRecording: stopRecorderRecording } = await import('./recorder.js');
|
|
185
160
|
|
|
186
|
-
|
|
187
|
-
const result =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
};
|
|
161
|
+
// Stop recording directly
|
|
162
|
+
const result = await stopRecorderRecording();
|
|
163
|
+
|
|
164
|
+
// Update status to indicate recording stopped
|
|
165
|
+
this.writeStatus({
|
|
166
|
+
isRecording: false,
|
|
167
|
+
completedTime: Date.now(),
|
|
168
|
+
pid: process.pid
|
|
169
|
+
});
|
|
196
170
|
|
|
197
171
|
this.cleanup({ preserveResult: true });
|
|
198
172
|
return result;
|
|
199
173
|
} else {
|
|
200
|
-
|
|
174
|
+
// Different process - send signal (for backward compatibility if needed)
|
|
175
|
+
logger.info('Stopping active recording process', { pid });
|
|
176
|
+
process.kill(pid, 'SIGINT');
|
|
177
|
+
|
|
178
|
+
// Wait for the process to actually finish
|
|
179
|
+
const maxWaitTime = 120000; // 2 minutes max
|
|
180
|
+
const startWait = Date.now();
|
|
181
|
+
|
|
182
|
+
while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
|
|
183
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.isProcessRunning(pid)) {
|
|
187
|
+
logger.warn('Process did not stop within timeout, forcing termination');
|
|
188
|
+
process.kill(pid, 'SIGKILL');
|
|
189
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (status) {
|
|
193
|
+
logger.info('Recording stopped, returning status', {
|
|
194
|
+
outputPath: status.outputPath,
|
|
195
|
+
duration: Date.now() - status.startTime
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
|
|
199
|
+
const result = {
|
|
200
|
+
outputPath: status.outputPath,
|
|
201
|
+
gifPath: `${basePath}.gif`,
|
|
202
|
+
snapshotPath: `${basePath}.png`,
|
|
203
|
+
duration: Date.now() - status.startTime,
|
|
204
|
+
clientStartDate: status.startTime,
|
|
205
|
+
apps: [],
|
|
206
|
+
logs: []
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
this.cleanup({ preserveResult: true });
|
|
210
|
+
return result;
|
|
211
|
+
} else {
|
|
212
|
+
throw new Error('No status information available for active recording');
|
|
213
|
+
}
|
|
201
214
|
}
|
|
202
215
|
} catch (error) {
|
|
203
216
|
logger.error('Failed to stop recording', { error });
|
|
@@ -213,67 +226,41 @@ class ProcessManager {
|
|
|
213
226
|
throw new Error('Recording already in progress');
|
|
214
227
|
}
|
|
215
228
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Get the path to the CLI binary
|
|
220
|
-
const binPath = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
|
|
221
|
-
|
|
222
|
-
logger.debug('Background process path', { binPath, exists: fs.existsSync(binPath) });
|
|
223
|
-
|
|
224
|
-
// Create log files for background process
|
|
225
|
-
const logDir = PROCESS_DIR;
|
|
226
|
-
const stdoutLog = path.join(logDir, 'background-stdout.log');
|
|
227
|
-
const stderrLog = path.join(logDir, 'background-stderr.log');
|
|
228
|
-
|
|
229
|
-
const stdoutFd = fs.openSync(stdoutLog, 'a');
|
|
230
|
-
const stderrFd = fs.openSync(stderrLog, 'a');
|
|
231
|
-
|
|
232
|
-
// Spawn a detached process that will handle the recording
|
|
233
|
-
const backgroundProcess = spawn(process.execPath, [
|
|
234
|
-
binPath,
|
|
235
|
-
JSON.stringify(options)
|
|
236
|
-
], {
|
|
237
|
-
detached: true,
|
|
238
|
-
stdio: ['ignore', stdoutFd, stderrFd], // Log stdout and stderr
|
|
239
|
-
env: {
|
|
240
|
-
...process.env,
|
|
241
|
-
DASHCAM_BACKGROUND: 'true'
|
|
242
|
-
}
|
|
243
|
-
});
|
|
229
|
+
// Import recorder module
|
|
230
|
+
const { startRecording: startRecorderRecording } = await import('./recorder.js');
|
|
244
231
|
|
|
245
|
-
|
|
246
|
-
fs.closeSync(stdoutFd);
|
|
247
|
-
fs.closeSync(stderrFd);
|
|
248
|
-
|
|
249
|
-
// Get the background process PID before unreffing
|
|
250
|
-
const backgroundPid = backgroundProcess.pid;
|
|
232
|
+
logger.info('Starting recording directly');
|
|
251
233
|
|
|
252
|
-
//
|
|
253
|
-
|
|
234
|
+
// Start recording using the recorder module
|
|
235
|
+
const recordingOptions = {
|
|
236
|
+
fps: parseInt(options.fps) || 10,
|
|
237
|
+
includeAudio: options.audio || false,
|
|
238
|
+
customOutputPath: options.output || null
|
|
239
|
+
};
|
|
254
240
|
|
|
255
|
-
|
|
256
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
241
|
+
const result = await startRecorderRecording(recordingOptions);
|
|
257
242
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
243
|
+
// Write status to track the recording
|
|
244
|
+
this.writeStatus({
|
|
245
|
+
isRecording: true,
|
|
246
|
+
startTime: result.startTime,
|
|
247
|
+
options,
|
|
248
|
+
pid: process.pid,
|
|
249
|
+
outputPath: result.outputPath
|
|
250
|
+
});
|
|
264
251
|
|
|
265
|
-
// Write PID file so other commands can find the
|
|
266
|
-
this.writePid(
|
|
252
|
+
// Write PID file so other commands can find the recording process
|
|
253
|
+
this.writePid(process.pid);
|
|
267
254
|
|
|
268
|
-
logger.info('
|
|
269
|
-
pid:
|
|
270
|
-
outputPath:
|
|
255
|
+
logger.info('Recording started successfully', {
|
|
256
|
+
pid: process.pid,
|
|
257
|
+
outputPath: result.outputPath
|
|
271
258
|
});
|
|
272
259
|
|
|
273
260
|
return {
|
|
274
|
-
pid:
|
|
275
|
-
outputPath:
|
|
276
|
-
startTime:
|
|
261
|
+
pid: process.pid,
|
|
262
|
+
outputPath: result.outputPath,
|
|
263
|
+
startTime: result.startTime
|
|
277
264
|
};
|
|
278
265
|
}
|
|
279
266
|
|
package/package.json
CHANGED
package/test_workflow.sh
CHANGED
|
@@ -33,13 +33,16 @@ echo "✅ File tracking configured"
|
|
|
33
33
|
# 4. Start dashcam recording in background
|
|
34
34
|
echo ""
|
|
35
35
|
echo "4. Starting dashcam recording in background..."
|
|
36
|
-
|
|
36
|
+
echo "⚠️ NOTE: Recording will run in the background using nohup"
|
|
37
|
+
echo ""
|
|
38
|
+
|
|
39
|
+
# Use nohup to properly detach the process and keep it running
|
|
37
40
|
./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
|
|
38
41
|
RECORD_PID=$!
|
|
39
42
|
|
|
40
43
|
# Wait for recording to initialize and log tracker to start
|
|
41
44
|
echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
|
|
42
|
-
sleep
|
|
45
|
+
sleep 3
|
|
43
46
|
|
|
44
47
|
# Write first event after log tracker is fully ready
|
|
45
48
|
RECORDING_START=$(date +%s)
|
|
@@ -48,11 +51,12 @@ echo "🔴 EVENT 1: Recording START at $(date '+%H:%M:%S')"
|
|
|
48
51
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
49
52
|
echo "[EVENT 1] 🔴 Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
|
|
50
53
|
|
|
51
|
-
# Verify recording is actually running
|
|
52
|
-
if
|
|
54
|
+
# Verify recording is actually running by checking status
|
|
55
|
+
if ./bin/dashcam.js status | grep -q "Recording in progress"; then
|
|
53
56
|
echo "✅ Recording started successfully"
|
|
54
57
|
else
|
|
55
|
-
echo "❌ Recording
|
|
58
|
+
echo "❌ Recording failed to start, check /tmp/dashcam-recording.log"
|
|
59
|
+
cat /tmp/dashcam-recording.log
|
|
56
60
|
exit 1
|
|
57
61
|
fi
|
|
58
62
|
|