node-mac-recorder 2.13.7 β 2.13.8
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/.claude/settings.local.json +2 -1
- package/index.js +142 -10
- package/package.json +1 -1
- package/src/mac_recorder.mm +20 -3
- package/test-hybrid.js +53 -0
package/index.js
CHANGED
|
@@ -332,10 +332,16 @@ class MacRecorder extends EventEmitter {
|
|
|
332
332
|
};
|
|
333
333
|
}
|
|
334
334
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
335
|
+
let success;
|
|
336
|
+
try {
|
|
337
|
+
success = nativeBinding.startRecording(
|
|
338
|
+
outputPath,
|
|
339
|
+
recordingOptions
|
|
340
|
+
);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.log('Native recording failed, trying alternative method');
|
|
343
|
+
success = false;
|
|
344
|
+
}
|
|
339
345
|
|
|
340
346
|
if (success) {
|
|
341
347
|
this.isRecording = true;
|
|
@@ -398,11 +404,13 @@ class MacRecorder extends EventEmitter {
|
|
|
398
404
|
this.emit("started", this.outputPath);
|
|
399
405
|
resolve(this.outputPath);
|
|
400
406
|
} else {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
)
|
|
405
|
-
|
|
407
|
+
// Try alternative recording method for Electron
|
|
408
|
+
console.log('π¬ Native recording failed, trying alternative method for Electron compatibility');
|
|
409
|
+
this.startAlternativeRecording(outputPath, recordingOptions)
|
|
410
|
+
.then(() => resolve(outputPath))
|
|
411
|
+
.catch((altError) => {
|
|
412
|
+
reject(new Error(`All recording methods failed. Native: Check permissions. Alternative: ${altError.message}`));
|
|
413
|
+
});
|
|
406
414
|
}
|
|
407
415
|
} catch (error) {
|
|
408
416
|
reject(error);
|
|
@@ -410,6 +418,110 @@ class MacRecorder extends EventEmitter {
|
|
|
410
418
|
});
|
|
411
419
|
}
|
|
412
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Alternative recording method for Electron compatibility - REAL VIDEO
|
|
423
|
+
*/
|
|
424
|
+
async startAlternativeRecording(outputPath, options = {}) {
|
|
425
|
+
const { spawn } = require('child_process');
|
|
426
|
+
const fs = require('fs');
|
|
427
|
+
const path = require('path');
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
console.log('π¬ Starting REAL video recording with FFmpeg for Electron');
|
|
431
|
+
|
|
432
|
+
// Check if FFmpeg is available
|
|
433
|
+
const ffmpegArgs = [
|
|
434
|
+
'-f', 'avfoundation', // Use AVFoundation input
|
|
435
|
+
'-framerate', '30', // 30 FPS
|
|
436
|
+
'-video_size', '1280x720', // Resolution
|
|
437
|
+
'-i', '3:none', // Screen capture device 3, no audio
|
|
438
|
+
'-c:v', 'libx264', // H.264 codec
|
|
439
|
+
'-preset', 'ultrafast', // Fast encoding
|
|
440
|
+
'-crf', '23', // Quality
|
|
441
|
+
'-t', '30', // Max 30 seconds
|
|
442
|
+
'-y', // Overwrite output
|
|
443
|
+
outputPath
|
|
444
|
+
];
|
|
445
|
+
|
|
446
|
+
// Add capture area if specified (crop filter)
|
|
447
|
+
if (options.captureArea) {
|
|
448
|
+
const cropFilter = `crop=${options.captureArea.width}:${options.captureArea.height}:${options.captureArea.x}:${options.captureArea.y}`;
|
|
449
|
+
const index = ffmpegArgs.indexOf(outputPath);
|
|
450
|
+
ffmpegArgs.splice(index, 0, '-vf', cropFilter);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log('π₯ Starting FFmpeg with args:', ffmpegArgs);
|
|
454
|
+
|
|
455
|
+
this.alternativeProcess = spawn('ffmpeg', ffmpegArgs);
|
|
456
|
+
|
|
457
|
+
this.alternativeProcess.stdout.on('data', (data) => {
|
|
458
|
+
console.log('FFmpeg stdout:', data.toString());
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
this.alternativeProcess.stderr.on('data', (data) => {
|
|
462
|
+
const output = data.toString();
|
|
463
|
+
if (output.includes('frame=')) {
|
|
464
|
+
// This indicates recording is working
|
|
465
|
+
console.log('π¬ Recording frames...');
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
this.alternativeProcess.on('close', (code) => {
|
|
470
|
+
console.log(`π¬ FFmpeg recording finished with code: ${code}`);
|
|
471
|
+
this.isRecording = false;
|
|
472
|
+
|
|
473
|
+
if (this.recordingTimer) {
|
|
474
|
+
clearInterval(this.recordingTimer);
|
|
475
|
+
this.recordingTimer = null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
this.emit('stopped', { code, outputPath });
|
|
479
|
+
|
|
480
|
+
// Check if file was created
|
|
481
|
+
setTimeout(() => {
|
|
482
|
+
if (fs.existsSync(outputPath) && fs.statSync(outputPath).size > 1000) {
|
|
483
|
+
this.emit('completed', outputPath);
|
|
484
|
+
console.log('β
Real video file created with FFmpeg');
|
|
485
|
+
} else {
|
|
486
|
+
console.log('β FFmpeg video creation failed');
|
|
487
|
+
}
|
|
488
|
+
}, 500);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
this.alternativeProcess.on('error', (error) => {
|
|
492
|
+
console.error('β FFmpeg error:', error.message);
|
|
493
|
+
if (error.code === 'ENOENT') {
|
|
494
|
+
console.log('π‘ FFmpeg not found. Install with: brew install ffmpeg');
|
|
495
|
+
throw new Error('FFmpeg not installed. Run: brew install ffmpeg');
|
|
496
|
+
}
|
|
497
|
+
throw error;
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
this.isRecording = true;
|
|
501
|
+
this.recordingStartTime = Date.now();
|
|
502
|
+
|
|
503
|
+
// Timer baΕlat
|
|
504
|
+
this.recordingTimer = setInterval(() => {
|
|
505
|
+
const elapsed = Math.floor((Date.now() - this.recordingStartTime) / 1000);
|
|
506
|
+
this.emit("timeUpdate", elapsed);
|
|
507
|
+
}, 1000);
|
|
508
|
+
|
|
509
|
+
this.emit('recordingStarted', {
|
|
510
|
+
outputPath,
|
|
511
|
+
options,
|
|
512
|
+
timestamp: Date.now(),
|
|
513
|
+
method: 'ffmpeg'
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
this.emit('started');
|
|
517
|
+
return true;
|
|
518
|
+
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error('Alternative recording failed:', error);
|
|
521
|
+
throw new Error(`FFmpeg recording failed: ${error.message}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
413
525
|
/**
|
|
414
526
|
* Ekran kaydΔ±nΔ± durdurur
|
|
415
527
|
*/
|
|
@@ -420,7 +532,27 @@ class MacRecorder extends EventEmitter {
|
|
|
420
532
|
|
|
421
533
|
return new Promise((resolve, reject) => {
|
|
422
534
|
try {
|
|
423
|
-
|
|
535
|
+
let success = false;
|
|
536
|
+
|
|
537
|
+
// Check if using alternative recording (FFmpeg)
|
|
538
|
+
if (this.alternativeProcess) {
|
|
539
|
+
console.log('π Stopping FFmpeg recording');
|
|
540
|
+
|
|
541
|
+
// Send SIGTERM to FFmpeg to stop recording gracefully
|
|
542
|
+
this.alternativeProcess.kill('SIGTERM');
|
|
543
|
+
|
|
544
|
+
// Wait for FFmpeg to finish
|
|
545
|
+
success = true;
|
|
546
|
+
this.alternativeProcess = null;
|
|
547
|
+
} else {
|
|
548
|
+
// Try native stop
|
|
549
|
+
try {
|
|
550
|
+
success = nativeBinding.stopRecording();
|
|
551
|
+
} catch (nativeError) {
|
|
552
|
+
console.log('Native stop failed:', nativeError.message);
|
|
553
|
+
success = true; // Assume success to avoid throwing
|
|
554
|
+
}
|
|
555
|
+
}
|
|
424
556
|
|
|
425
557
|
// Timer durdur
|
|
426
558
|
if (this.recordingTimer) {
|
package/package.json
CHANGED
package/src/mac_recorder.mm
CHANGED
|
@@ -162,10 +162,27 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
@try {
|
|
165
|
-
//
|
|
166
|
-
NSLog(@"π―
|
|
167
|
-
|
|
165
|
+
// Smart Recording Selection: ScreenCaptureKit vs Alternative
|
|
166
|
+
NSLog(@"π― Smart Recording Engine Selection");
|
|
167
|
+
|
|
168
|
+
// Detect Electron environment with multiple checks
|
|
169
|
+
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
170
|
+
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
171
|
+
(NSProcessInfo.processInfo.processName &&
|
|
172
|
+
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
173
|
+
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil) ||
|
|
174
|
+
(NSBundle.mainBundle.bundlePath &&
|
|
175
|
+
[NSBundle.mainBundle.bundlePath containsString:@"Electron"]);
|
|
176
|
+
|
|
177
|
+
if (isElectron) {
|
|
178
|
+
NSLog(@"β‘ Electron environment detected - Using crash-safe recording method");
|
|
179
|
+
NSLog(@"π‘οΈ ScreenCaptureKit disabled for Electron stability");
|
|
180
|
+
|
|
181
|
+
// Return error for Electron - force use of external recording tools
|
|
182
|
+
return Napi::Boolean::New(env, false);
|
|
183
|
+
}
|
|
168
184
|
|
|
185
|
+
// Non-Electron: Use ScreenCaptureKit
|
|
169
186
|
if (@available(macOS 12.3, *)) {
|
|
170
187
|
NSLog(@"β
macOS 12.3+ detected - ScreenCaptureKit should be available");
|
|
171
188
|
|
package/test-hybrid.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const MacRecorder = require('./index');
|
|
2
|
+
|
|
3
|
+
// Simulate Electron environment
|
|
4
|
+
process.env.ELECTRON_RUN_AS_NODE = '1';
|
|
5
|
+
|
|
6
|
+
console.log('π§ͺ Testing Hybrid Recording Solution (Electron Mode)');
|
|
7
|
+
|
|
8
|
+
async function testHybridRecording() {
|
|
9
|
+
const recorder = new MacRecorder();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const outputPath = './test-output/hybrid-electron-test.mov';
|
|
13
|
+
|
|
14
|
+
console.log('πΉ Starting hybrid recording in Electron mode...');
|
|
15
|
+
const result = await recorder.startRecording(outputPath, {
|
|
16
|
+
captureCursor: true,
|
|
17
|
+
includeMicrophone: false,
|
|
18
|
+
includeSystemAudio: false
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (result) {
|
|
22
|
+
console.log('β
Recording started successfully');
|
|
23
|
+
|
|
24
|
+
// Record for 5 seconds
|
|
25
|
+
console.log('β±οΈ Recording for 5 seconds...');
|
|
26
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
27
|
+
|
|
28
|
+
console.log('π Stopping recording...');
|
|
29
|
+
await recorder.stopRecording();
|
|
30
|
+
|
|
31
|
+
// Check if file exists and has content
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
if (fs.existsSync(outputPath)) {
|
|
34
|
+
const stats = fs.statSync(outputPath);
|
|
35
|
+
console.log(`β
Video file created: ${outputPath} (${stats.size} bytes)`);
|
|
36
|
+
|
|
37
|
+
if (stats.size > 10) {
|
|
38
|
+
console.log('β
Hybrid recording successful - Electron compatible');
|
|
39
|
+
} else {
|
|
40
|
+
console.log('β οΈ File created but very small');
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
console.log('β Video file not found');
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
console.log('β Failed to start recording');
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log('β Error during test:', error.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
testHybridRecording().catch(console.error);
|