dashcam 1.3.31 → 1.4.1-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.
@@ -4,7 +4,7 @@
4
4
  * This script runs detached from the parent process to handle long-running recordings
5
5
  */
6
6
 
7
- import { startRecording, stopRecording } from '../lib/recorder.js';
7
+ import { startRecording } from '../lib/recorder.js';
8
8
  import { logger, setVerbose } from '../lib/logger.js';
9
9
  import fs from 'fs';
10
10
  import path from 'path';
@@ -161,56 +161,13 @@ async function runBackgroundRecording() {
161
161
  }
162
162
  isShuttingDown = true;
163
163
 
164
- logger.info(`Received ${signal}, stopping background recording...`);
165
- console.log('[Background] Received stop signal, stopping recording...');
164
+ logger.info(`Received ${signal}, background process will be killed`);
165
+ console.log('[Background] Received stop signal, process will be terminated...');
166
166
 
167
- try {
168
- // Stop the recording
169
- const stopResult = await stopRecording();
170
-
171
- if (stopResult) {
172
- logger.info('Recording stopped successfully', {
173
- outputPath: stopResult.outputPath,
174
- duration: stopResult.duration
175
- });
176
- console.log('[Background] Recording stopped successfully:', {
177
- outputPath: stopResult.outputPath,
178
- duration: stopResult.duration
179
- });
180
-
181
- // Write recording result for stop command to upload
182
- console.log('[Background] Writing recording result for stop command...');
183
- writeRecordingResult({
184
- outputPath: stopResult.outputPath,
185
- duration: stopResult.duration,
186
- clientStartDate: stopResult.clientStartDate,
187
- apps: stopResult.apps,
188
- logs: stopResult.logs,
189
- gifPath: stopResult.gifPath,
190
- snapshotPath: stopResult.snapshotPath,
191
- // Include options so stop command can use them for upload
192
- title: options.title,
193
- description: options.description,
194
- project: options.project || options.k
195
- });
196
- console.log('[Background] Recording result written successfully');
197
- }
198
-
199
- // Update status to indicate recording stopped
200
- writeStatus({
201
- isRecording: false,
202
- completedTime: Date.now(),
203
- pid: process.pid
204
- });
205
-
206
- console.log('[Background] Background process exiting successfully');
207
- logger.info('Background process exiting successfully');
208
- process.exit(0);
209
- } catch (error) {
210
- console.error('[Background] Error during shutdown:', error.message);
211
- logger.error('Error during shutdown:', error);
212
- process.exit(1);
213
- }
167
+ // Don't try to stop recording here - the main process will handle cleanup
168
+ // after killing this process. Just exit.
169
+ logger.info('Background process exiting');
170
+ process.exit(0);
214
171
  };
215
172
 
216
173
  // Register signal handlers
package/bin/dashcam.js CHANGED
@@ -137,7 +137,7 @@ async function recordingAction(options, command) {
137
137
 
138
138
  // Add timeout to prevent hanging
139
139
  const startRecordingPromise = processManager.startRecording({
140
- fps: parseInt(options.fps) || 30,
140
+ fps: parseInt(options.fps) || 10,
141
141
  audio: options.audio,
142
142
  output: options.output,
143
143
  title: options.title,
@@ -241,7 +241,7 @@ program
241
241
  .command('record')
242
242
  .description('Start a recording terminal to be included in your dashcam video recording')
243
243
  .option('-a, --audio', 'Include audio in the recording')
244
- .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
244
+ .option('-f, --fps <fps>', 'Frames per second (default: 10)', '10')
245
245
  .option('-o, --output <path>', 'Custom output path')
246
246
  .option('-t, --title <title>', 'Title for the recording')
247
247
  .option('-d, --description <description>', 'Description for the recording (supports markdown)')
@@ -484,9 +484,36 @@ program
484
484
  logger.error('Failed to read temp file info', { error });
485
485
  }
486
486
 
487
- const tempFile = tempFileInfo?.tempFile || result.outputPath.replace('.webm', '-temp.webm');
487
+ // Fallback: look for temp files in the same directory
488
+ let tempFile = tempFileInfo?.tempFile;
489
+ if (!tempFile || !fs.existsSync(tempFile)) {
490
+ // Try to find any temp-*.webm file in the output directory
491
+ const outputDir = path.dirname(result.outputPath);
492
+ if (fs.existsSync(outputDir)) {
493
+ const files = fs.readdirSync(outputDir);
494
+ const tempFiles = files.filter(f => f.startsWith('temp-') && f.endsWith('.webm'));
495
+ if (tempFiles.length > 0) {
496
+ // Use the most recent temp file
497
+ tempFiles.sort().reverse();
498
+ tempFile = path.join(outputDir, tempFiles[0]);
499
+ logger.info('Found temp file in output directory', { tempFile });
500
+ }
501
+ }
502
+ }
503
+
488
504
  const outputPath = result.outputPath;
489
505
 
506
+ // Verify we have a temp file to work with
507
+ if (!tempFile) {
508
+ console.error('No temp file found for recording');
509
+ logger.error('Temp file missing', {
510
+ tempFileInfo,
511
+ outputDir: path.dirname(result.outputPath),
512
+ expectedPattern: 'temp-*.webm'
513
+ });
514
+ process.exit(1);
515
+ }
516
+
490
517
  // Fix the video with FFmpeg (handle incomplete recordings)
491
518
  logger.info('Fixing video with FFmpeg', { tempFile, outputPath });
492
519
  if (fs.existsSync(tempFile)) {
@@ -502,14 +529,129 @@ program
502
529
  logger.warn('Temp file not found, using output path directly', { tempFile });
503
530
  }
504
531
 
505
- // We killed the process, so we don't have apps/logs data
506
- // The recording is still on disk and can be uploaded without metadata
532
+ // Verify the output file exists before proceeding
533
+ if (!fs.existsSync(outputPath)) {
534
+ console.error('Recording file not found after processing');
535
+ logger.error('Output file missing', { outputPath, tempFile, tempFileExists: fs.existsSync(tempFile) });
536
+ process.exit(1);
537
+ }
538
+
539
+ logger.info('Output file ready for upload', {
540
+ outputPath,
541
+ size: fs.statSync(outputPath).size
542
+ });
543
+
544
+ // Generate paths for additional assets
545
+ const basePath = outputPath.substring(0, outputPath.lastIndexOf('.'));
546
+ const gifPath = `${basePath}.gif`;
547
+ const snapshotPath = `${basePath}.png`;
548
+
549
+ // Create GIF and snapshot (non-blocking, don't fail if these fail)
550
+ console.log('Creating preview assets...');
551
+ const { createGif, createSnapshot } = await import('../lib/ffmpeg.js');
552
+
553
+ // Add timeout wrapper to prevent hanging on large files
554
+ const withTimeout = (promise, timeoutMs, name) => {
555
+ return Promise.race([
556
+ promise,
557
+ new Promise((_, reject) =>
558
+ setTimeout(() => reject(new Error(`${name} timed out after ${timeoutMs}ms`)), timeoutMs)
559
+ )
560
+ ]);
561
+ };
562
+
563
+ try {
564
+ await Promise.all([
565
+ withTimeout(
566
+ createGif(outputPath, gifPath),
567
+ 30000, // 30 second timeout
568
+ 'GIF creation'
569
+ ).catch(err => {
570
+ logger.warn('Failed to create GIF', { error: err.message });
571
+ }),
572
+ withTimeout(
573
+ createSnapshot(outputPath, snapshotPath, 0),
574
+ 10000, // 10 second timeout
575
+ 'Snapshot creation'
576
+ ).catch(err => {
577
+ logger.warn('Failed to create snapshot', { error: err.message });
578
+ })
579
+ ]);
580
+ logger.debug('Preview assets created successfully');
581
+ } catch (error) {
582
+ logger.warn('Failed to create preview assets', { error: error.message });
583
+ }
584
+
585
+ // Collect logs and app tracking data
586
+ console.log('Collecting tracking data...');
587
+
588
+ // Get app tracking data
589
+ const { applicationTracker } = await import('../lib/applicationTracker.js');
590
+ const appTrackingResults = applicationTracker.stop();
591
+ logger.info('Collected app tracking results', {
592
+ apps: appTrackingResults.apps?.length || 0,
593
+ icons: appTrackingResults.icons?.length || 0,
594
+ events: appTrackingResults.events?.length || 0
595
+ });
596
+
597
+ // Get log tracking data - read directly from files since background process was killed
598
+ const recordingDir = path.dirname(outputPath);
599
+ let logTrackingResults = [];
600
+
601
+ try {
602
+ // Check for CLI logs file
603
+ const cliLogsFile = path.join(recordingDir, 'dashcam_logs_cli.jsonl');
604
+ if (fs.existsSync(cliLogsFile)) {
605
+ const { jsonl } = await import('../lib/utilities/jsonl.js');
606
+ const cliLogs = jsonl.read(cliLogsFile);
607
+ if (cliLogs && cliLogs.length > 0) {
608
+ logTrackingResults.push({
609
+ type: 'cli',
610
+ name: 'CLI Logs',
611
+ fileLocation: cliLogsFile,
612
+ count: cliLogs.length,
613
+ trimmedFileLocation: cliLogsFile
614
+ });
615
+ logger.info('Found CLI logs', { count: cliLogs.length, file: cliLogsFile });
616
+ }
617
+ }
618
+
619
+ // Check for web logs file
620
+ const webLogsFile = path.join(recordingDir, 'dashcam_logs_web_events.jsonl');
621
+ if (fs.existsSync(webLogsFile)) {
622
+ const { jsonl } = await import('../lib/utilities/jsonl.js');
623
+ const webLogs = jsonl.read(webLogsFile);
624
+ if (webLogs && webLogs.length > 0) {
625
+ logTrackingResults.push({
626
+ type: 'web',
627
+ name: 'Web Logs',
628
+ fileLocation: webLogsFile,
629
+ count: webLogs.length,
630
+ trimmedFileLocation: webLogsFile
631
+ });
632
+ logger.info('Found web logs', { count: webLogs.length, file: webLogsFile });
633
+ }
634
+ }
635
+
636
+ logger.info('Collected log tracking results', {
637
+ trackers: logTrackingResults.length,
638
+ totalEvents: logTrackingResults.reduce((sum, result) => sum + result.count, 0)
639
+ });
640
+ } catch (error) {
641
+ logger.warn('Failed to collect log tracking results', { error: error.message });
642
+ }
643
+
644
+ // The recording is on disk and can be uploaded with full metadata
507
645
  const recordingResult = {
508
646
  outputPath,
647
+ gifPath,
648
+ snapshotPath,
509
649
  duration: result.duration,
650
+ fileSize: fs.statSync(outputPath).size,
510
651
  clientStartDate: activeStatus.startTime,
511
- apps: [],
512
- logs: [],
652
+ apps: appTrackingResults.apps,
653
+ icons: appTrackingResults.icons,
654
+ logs: logTrackingResults,
513
655
  title: activeStatus?.options?.title,
514
656
  description: activeStatus?.options?.description,
515
657
  project: activeStatus?.options?.project
@@ -532,6 +674,7 @@ program
532
674
  duration: recordingResult.duration,
533
675
  clientStartDate: recordingResult.clientStartDate,
534
676
  apps: recordingResult.apps,
677
+ icons: recordingResult.icons,
535
678
  logs: recordingResult.logs,
536
679
  gifPath: recordingResult.gifPath,
537
680
  snapshotPath: recordingResult.snapshotPath
@@ -749,7 +892,7 @@ program
749
892
  if (!targetFile) {
750
893
  console.error('Please provide a file path or use --recover option');
751
894
  console.log('Examples:');
752
- console.log(' dashcam upload /path/to/recording.webm');
895
+ console.log(' dashcam upload /path/to/recording.mp4');
753
896
  console.log(' dashcam upload --recover');
754
897
  process.exit(1);
755
898
  }
package/lib/ffmpeg.js CHANGED
@@ -72,8 +72,8 @@ export async function createGif(inputVideoPath, outputGifPath) {
72
72
  throw new Error('Video file is not ready or is corrupted');
73
73
  }
74
74
 
75
- const gifFps = 4;
76
- const gifDuration = 10;
75
+ const gifFps = 2; // Reduced from 4 to 2 fps for faster generation
76
+ const gifDuration = 5; // Reduced from 10 to 5 seconds for faster generation
77
77
  const gifFrames = Math.ceil(gifDuration * gifFps);
78
78
 
79
79
  // Get video duration in seconds
@@ -94,8 +94,8 @@ export async function createGif(inputVideoPath, outputGifPath) {
94
94
  stdout
95
95
  });
96
96
 
97
- // Fallback: Single-pass GIF creation at fixed rate
98
- const filters = `fps=1/3,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5`;
97
+ // Fallback: Fast GIF creation with reduced quality
98
+ const filters = `fps=2,scale=480:-1:flags=fast_bilinear,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=none`;
99
99
 
100
100
  await execa(ffmpegPath, [
101
101
  '-i', inputVideoPath,
@@ -111,8 +111,12 @@ export async function createGif(inputVideoPath, outputGifPath) {
111
111
 
112
112
  const extractedFramesInterval = videoDuration / gifFrames;
113
113
 
114
- // Single-pass GIF creation with palette for better quality and performance
115
- const filters = `fps=1/${extractedFramesInterval},scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=128[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5`;
114
+ // Fast GIF creation with reduced quality for speed
115
+ // - Smaller scale (480 vs 640)
116
+ // - Fewer colors (64 vs 128)
117
+ // - Faster scaling algorithm (fast_bilinear vs lanczos)
118
+ // - No dithering for faster processing
119
+ const filters = `fps=1/${extractedFramesInterval},scale=480:-1:flags=fast_bilinear,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=none`;
116
120
 
117
121
  await execa(ffmpegPath, [
118
122
  '-i', inputVideoPath,
package/lib/recorder.js CHANGED
@@ -3,7 +3,7 @@ import { logger, logFunctionCall } from './logger.js';
3
3
  import { createGif, createSnapshot } from './ffmpeg.js';
4
4
  import { applicationTracker } from './applicationTracker.js';
5
5
  import { logsTrackerManager, trimLogs } from './logs/index.js';
6
- import { getFfmpegPath } from './binaries.js';
6
+ import { getFfmpegPath, getFfprobePath } from './binaries.js';
7
7
  import path from 'path';
8
8
  import os from 'os';
9
9
  import fs from 'fs';
@@ -27,7 +27,7 @@ async function fixVideoContainer(inputVideoPath, outputVideoPath) {
27
27
  '-i', inputVideoPath,
28
28
  '-vcodec', 'copy', // Copy video stream without re-encoding
29
29
  '-acodec', 'copy', // Copy audio stream without re-encoding
30
- '-movflags', 'faststart', // Enable fast start for web playback
30
+ '-f', 'webm', // Force WebM format
31
31
  outputVideoPath,
32
32
  '-y', // Overwrite output file
33
33
  '-hide_banner'
@@ -320,8 +320,9 @@ export async function startRecording({
320
320
 
321
321
  // Construct FFmpeg command arguments
322
322
  const platformArgs = await getPlatformArgs({ fps, includeAudio });
323
+
323
324
  const outputArgs = [
324
- '-c:v', 'libvpx-vp9', // Use VP9 codec for better quality and compression
325
+ '-c:v', 'libvpx', // Use VP9 codec for better quality and compression
325
326
  '-quality', 'realtime', // Use realtime quality preset for faster encoding
326
327
  '-cpu-used', '8', // Maximum speed (0-8, higher = faster but lower quality)
327
328
  '-deadline', 'realtime',// Realtime encoding mode for lowest latency
@@ -340,7 +341,7 @@ export async function startRecording({
340
341
 
341
342
  if (includeAudio) {
342
343
  outputArgs.push(
343
- '-c:a', 'libopus', // Opus audio codec for WebM
344
+ '-c:a', 'libvorbis', // Vorbis audio codec for WebM
344
345
  '-b:a', '128k'
345
346
  );
346
347
  }
@@ -535,6 +536,9 @@ export async function stopRecording() {
535
536
  }
536
537
 
537
538
  try {
539
+ const platform = os.platform();
540
+ const isWindows = platform === 'win32';
541
+
538
542
  // First try to gracefully stop FFmpeg by sending 'q'
539
543
  if (currentRecording && currentRecording.stdin) {
540
544
  logger.debug('Sending quit signal to FFmpeg...');
@@ -545,18 +549,36 @@ export async function stopRecording() {
545
549
  // Wait for FFmpeg to finish gracefully with realtime encoding
546
550
  const gracefulTimeout = setTimeout(() => {
547
551
  if (currentRecording && !currentRecording.killed) {
548
- logger.warn('FFmpeg did not exit gracefully after 10s, sending SIGTERM...');
549
- // If still running, try SIGTERM
550
- process.kill(currentRecording.pid, 'SIGTERM');
552
+ logger.warn('FFmpeg did not exit gracefully after 10s, forcing termination...');
553
+ // Use platform-appropriate termination
554
+ if (isWindows) {
555
+ // On Windows, use taskkill for clean termination
556
+ try {
557
+ execa.sync('taskkill', ['/PID', currentRecording.pid.toString(), '/T']);
558
+ } catch (e) {
559
+ logger.warn('taskkill failed, ffmpeg may have already exited');
560
+ }
561
+ } else {
562
+ // On Unix, use SIGTERM
563
+ process.kill(currentRecording.pid, 'SIGTERM');
564
+ }
551
565
  }
552
566
  }, 10000); // Wait longer for graceful shutdown
553
567
 
554
- // Wait up to 20 seconds for SIGTERM to work
568
+ // Wait up to 20 seconds for termination to work
555
569
  const hardKillTimeout = setTimeout(() => {
556
570
  if (currentRecording && !currentRecording.killed) {
557
- logger.error('FFmpeg still running after SIGTERM, using SIGKILL...');
558
- // If still not dead, use SIGKILL as last resort
559
- process.kill(currentRecording.pid, 'SIGKILL');
571
+ logger.error('FFmpeg still running after termination, forcing kill...');
572
+ // Use platform-appropriate forced kill
573
+ if (isWindows) {
574
+ try {
575
+ execa.sync('taskkill', ['/F', '/PID', currentRecording.pid.toString(), '/T']);
576
+ } catch (e) {
577
+ logger.warn('taskkill /F failed, ffmpeg may have already exited');
578
+ }
579
+ } else {
580
+ process.kill(currentRecording.pid, 'SIGKILL');
581
+ }
560
582
  }
561
583
  }, 20000); // Longer timeout to avoid killing prematurely
562
584
 
@@ -570,20 +592,39 @@ export async function stopRecording() {
570
592
  clearTimeout(hardKillTimeout);
571
593
 
572
594
  // Wait for filesystem to sync and file to be written
595
+ // This is critical on Windows where file writes may be buffered
596
+
597
+ // Windows needs extra time for file system sync
598
+ const extraSyncTime = isWindows ? 3000 : 1000;
599
+ logger.debug(`Waiting ${extraSyncTime}ms for file system to sync...`);
600
+ await new Promise(resolve => setTimeout(resolve, extraSyncTime));
601
+
573
602
  // Poll for the file to exist with content
574
603
  logger.debug('Waiting for temp file to be written...');
575
- const maxWaitTime = 10000; // Wait up to 10 seconds
604
+ const maxWaitTime = 15000; // Increased from 10s to 15s for Windows
576
605
  const startWait = Date.now();
577
606
  let tempFileReady = false;
607
+ let lastSize = 0;
608
+ let stableCount = 0;
578
609
 
579
610
  while (!tempFileReady && (Date.now() - startWait) < maxWaitTime) {
580
611
  const tempFile = currentTempFile;
581
612
  if (tempFile && fs.existsSync(tempFile)) {
582
613
  const stats = fs.statSync(tempFile);
583
614
  if (stats.size > 0) {
584
- logger.debug('Temp file is ready', { size: stats.size });
585
- tempFileReady = true;
586
- break;
615
+ // Check if file size is stable (hasn't changed)
616
+ if (stats.size === lastSize) {
617
+ stableCount++;
618
+ // Consider ready after size is stable for 2 consecutive checks
619
+ if (stableCount >= 2) {
620
+ logger.debug('Temp file is ready and stable', { size: stats.size });
621
+ tempFileReady = true;
622
+ break;
623
+ }
624
+ } else {
625
+ stableCount = 0;
626
+ }
627
+ lastSize = stats.size;
587
628
  }
588
629
  }
589
630
  // Wait a bit before checking again
@@ -591,7 +632,7 @@ export async function stopRecording() {
591
632
  }
592
633
 
593
634
  if (!tempFileReady) {
594
- logger.warn('Temp file not ready after waiting, proceeding anyway');
635
+ logger.warn('Temp file not confirmed stable after waiting, proceeding anyway', { lastSize });
595
636
  }
596
637
 
597
638
  // Read temp file path from disk (for cross-process access)
@@ -640,6 +681,63 @@ export async function stopRecording() {
640
681
  path: tempFile
641
682
  });
642
683
 
684
+ // Validate that the file is readable by ffprobe before attempting re-mux
685
+ logger.debug('Validating temp file integrity with ffprobe...');
686
+ const ffprobePath = await getFfprobePath();
687
+ try {
688
+ const { exitCode, stderr } = await execa(ffprobePath, [
689
+ '-v', 'error',
690
+ '-select_streams', 'v:0',
691
+ '-show_entries', 'stream=codec_type',
692
+ '-of', 'default=noprint_wrappers=1:nokey=1',
693
+ tempFile
694
+ ], { reject: false });
695
+
696
+ if (exitCode !== 0) {
697
+ logger.warn('Temp file validation failed', { exitCode, stderr });
698
+ // File may be corrupted, but we'll try to process it anyway
699
+ // Skip re-muxing and just copy the file
700
+ logger.warn('Skipping re-mux due to validation failure, copying file directly');
701
+ fs.copyFileSync(tempFile, outputPath);
702
+
703
+ // Clean up temp file
704
+ try {
705
+ fs.unlinkSync(tempFile);
706
+ } catch (e) {
707
+ logger.debug('Failed to delete temp file:', e);
708
+ }
709
+
710
+ // Skip the re-mux process below
711
+ const result = {
712
+ outputPath,
713
+ gifPath: `${outputPath.substring(0, outputPath.lastIndexOf('.'))}.gif`,
714
+ snapshotPath: `${outputPath.substring(0, outputPath.lastIndexOf('.'))}.png`,
715
+ duration: Date.now() - recordingStartTime,
716
+ fileSize: fs.existsSync(outputPath) ? fs.statSync(outputPath).size : 0,
717
+ clientStartDate: recordingStartTime,
718
+ apps: applicationTracker.stop().apps,
719
+ icons: applicationTracker.stop().icons,
720
+ logs: await logsTrackerManager.stop({ recorderId: path.basename(outputPath).replace('.webm', ''), screenId: '1' })
721
+ };
722
+
723
+ currentRecording = null;
724
+ recordingStartTime = null;
725
+ currentTempFile = null;
726
+
727
+ if (fs.existsSync(TEMP_FILE_INFO_PATH)) {
728
+ fs.unlinkSync(TEMP_FILE_INFO_PATH);
729
+ }
730
+
731
+ logExit();
732
+ return result;
733
+ }
734
+
735
+ logger.debug('Temp file validation passed');
736
+ } catch (error) {
737
+ logger.warn('Failed to validate temp file', { error: error.message });
738
+ // Continue with re-mux attempt anyway
739
+ }
740
+
643
741
  // Re-mux to ensure proper container metadata (duration, seekability, etc.)
644
742
  logger.debug('Re-muxing temp file to fix container metadata...');
645
743
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.3.31",
3
+ "version": "1.4.1-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {
package/test_workflow.sh CHANGED
@@ -34,12 +34,11 @@ echo "✅ File tracking configured"
34
34
  echo ""
35
35
  echo "4. Starting dashcam recording in background..."
36
36
  # Start recording and redirect output to a log file so we can still monitor it
37
- ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
38
- RECORD_PID=$!
37
+ ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1
39
38
 
40
39
  # Wait for recording to initialize and log tracker to start
41
- echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
42
- sleep 1
40
+ echo "Waiting for recording to initialize..."
41
+ sleep 2
43
42
 
44
43
  # Write first event after log tracker is fully ready
45
44
  RECORDING_START=$(date +%s)
@@ -48,11 +47,12 @@ echo "🔴 EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
47
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49
48
  echo "[EVENT 1] 🔴 Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
49
 
51
- # Verify recording is actually running
52
- if ps -p $RECORD_PID > /dev/null; then
50
+ # Verify background recording process is actually running
51
+ if pgrep -f "dashcam-background.js" > /dev/null; then
53
52
  echo "✅ Recording started successfully"
54
53
  else
55
54
  echo "❌ Recording process died, check /tmp/dashcam-recording.log"
55
+ cat /tmp/dashcam-recording.log
56
56
  exit 1
57
57
  fi
58
58