node-mac-recorder 2.16.7 โ 2.16.9
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 +3 -51
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +15 -20
- package/src/mac_recorder.mm +91 -40
- package/simple-test.js +0 -38
|
@@ -1,57 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(node
|
|
5
|
-
"Bash(timeout:*)",
|
|
6
|
-
"Bash(rm:*)",
|
|
7
|
-
"Bash(python3:*)",
|
|
8
|
-
"Bash(grep:*)",
|
|
9
|
-
"Bash(cat:*)",
|
|
10
|
-
"Bash(brew install:*)",
|
|
11
|
-
"Bash(open:*)",
|
|
12
|
-
"Bash(curl:*)",
|
|
13
|
-
"Bash(system_profiler:*)",
|
|
14
|
-
"Bash(mkdir:*)",
|
|
15
|
-
"Bash(/dev/null)",
|
|
16
|
-
"Bash(ls:*)",
|
|
17
|
-
"Bash(touch:*)",
|
|
18
|
-
"Bash(git add:*)",
|
|
19
|
-
"Bash(git commit:*)",
|
|
20
|
-
"Bash(find:*)",
|
|
21
|
-
"WebFetch(domain:developer.apple.com)",
|
|
22
|
-
"WebFetch(domain:github.com)",
|
|
23
|
-
"WebFetch(domain:nonstrict.eu)",
|
|
24
|
-
"Bash(cp:*)",
|
|
25
|
-
"Bash(git checkout:*)",
|
|
26
|
-
"Bash(ELECTRON_VERSION=25.0.0 node -e \"\nconsole.log(''ELECTRON_VERSION env:'', process.env.ELECTRON_VERSION);\nconsole.log(''getenv result would be:'', process.env.ELECTRON_VERSION || ''null'');\n\")",
|
|
27
|
-
"Bash(ELECTRON_VERSION=25.0.0 node test-env-detection.js)",
|
|
28
|
-
"Bash(ELECTRON_VERSION=25.0.0 node test-native-call.js)",
|
|
29
|
-
"Bash(chmod:*)",
|
|
30
|
-
"Bash(ffprobe:*)",
|
|
31
|
-
"Bash(ffmpeg:*)",
|
|
32
|
-
"WebSearch",
|
|
33
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Testing with proper permissions and Electron env'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function test() {\n try {\n const outputPath = ''./test-output/proper-test.mov'';\n console.log(''๐น Starting recording...'');\n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 2 seconds'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Test completed'');\n } else {\n console.log(''โ Recording start failed'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntest();\n\")",
|
|
34
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''''๐ Debugging frame writing...'''');\nconst MacRecorder = require(''''./index'''');\nconst recorder = new MacRecorder();\n\nasync function debugFrameWriting() {\n try {\n const outputPath = ''''./test-output/frame-debug.mov'''';\n console.log(''''๐น Starting debug test...'''');\n \n const success = await recorder.startRecording(outputPath);\n \n if (success) {\n console.log(''''โฑ๏ธ Recording for 2 seconds...'''');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''''๐ Stopping...'''');\n await recorder.stopRecording();\n \n // Wait for finalization\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n } else {\n console.log(''''โ Failed to start'''');\n }\n } catch (error) {\n console.log(''''โ Error:'''', error);\n }\n}\n\ndebugFrameWriting();\n\")",
|
|
35
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Testing with proper permissions and Electron env'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function test() {\n try {\n const outputPath = ''./test-output/crash-test.mov'';\n console.log(''๐น Starting recording...'');\n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Test completed without crash'');\n } else {\n console.log(''โ Recording start failed'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntest();\n\")",
|
|
36
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Debugging frame writing...'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function debugFrameWriting() {\n try {\n const outputPath = ''./test-output/frame-debug.mov'';\n console.log(''๐น Starting debug test...'');\n \n const success = await recorder.startRecording(outputPath);\n \n if (success) {\n console.log(''โฑ๏ธ Recording for 2 seconds...'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''๐ Stopping...'');\n await recorder.stopRecording();\n \n // Wait for finalization\n await new Promise(resolve => setTimeout(resolve, 1000));\n \n } else {\n console.log(''โ Failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error);\n }\n}\n\ndebugFrameWriting();\n\")",
|
|
37
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐งช Testing without AVFoundation - Pure ScreenCaptureKit'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testPureScreenCaptureKit() {\n try {\n const outputPath = ''./test-output/pure-sck.mov'';\n console.log(''๐น Starting pure ScreenCaptureKit test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: false\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 2 seconds'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Pure ScreenCaptureKit test completed successfully!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntestPureScreenCaptureKit();\n\")",
|
|
38
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ต Testing audio + crop area recording'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testAudioCropRecording() {\n try {\n const outputPath = ''./test-output/audio-crop-test.mov'';\n console.log(''๐น Starting recording with audio and crop area...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: false,\n includeMicrophone: true,\n includeSystemAudio: true,\n captureArea: {\n x: 100, \n y: 100,\n width: 800,\n height: 600\n }\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Audio + crop test completed successfully!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestAudioCropRecording();\n\")",
|
|
39
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐งช Testing crash fix with null path protection'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testCrashFix() {\n try {\n const outputPath = ''./test-output/crash-fix-test.mov'';\n console.log(''๐น Starting crash fix test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true,\n includeSystemAudio: true,\n captureArea: {\n x: 100,\n y: 100,\n width: 800,\n height: 600\n }\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Crash fix test completed successfully!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntestCrashFix();\n\")",
|
|
40
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Final stability test with all features'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-stability-test.mov'';\n console.log(''๐น Starting final stability test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true,\n includeSystemAudio: true,\n captureArea: {\n x: 200,\n y: 200,\n width: 600,\n height: 400\n }\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''๐ FINAL TEST COMPLETED SUCCESSFULLY - NO CRASH!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")",
|
|
41
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ต Testing system audio only recording'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testSystemAudio() {\n try {\n const outputPath = ''./test-output/system-audio-only.mov'';\n console.log(''๐น Starting system audio only recording...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: false,\n includeMicrophone: false, // Mikrophone DEVRE DIลI \n includeSystemAudio: true, // Sadece sistem sesi AรIK\n });\n \n if (success) {\n console.log(''โ
System audio recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
System audio test completed!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestSystemAudio();\n\")",
|
|
42
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Final stability test with all features'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-stability-test.mov'';\n console.log(''๐น Starting final stability test...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true,\n captureArea: {\n x: 200,\n y: 200,\n width: 600,\n height: 400\n }\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 4 seconds'');\n await new Promise(resolve => setTimeout(resolve, 4000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''๐ FINAL TEST COMPLETED SUCCESSFULLY - NO CRASH!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")",
|
|
43
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ต Testing both microphone and system audio'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testBothAudio() {\n try {\n const outputPath = ''./test-output/both-audio-test.mov'';\n console.log(''๐น Starting recording with both audio sources...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: true, // Mikrophone AรIK\n includeSystemAudio: true // Sistem sesi AรIK\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 5 seconds'');\n await new Promise(resolve => setTimeout(resolve, 5000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Both audio test completed!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\ntestBothAudio();\n\")",
|
|
44
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ค Testing microphone only'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testMicrophoneOnly() {\n try {\n const outputPath = ''./test-output/microphone-only-test.mov'';\n console.log(''๐น Starting microphone only recording...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: false,\n includeMicrophone: true, // Sadece mikrofon AรIK\n includeSystemAudio: false // Sistem sesi KAPALI\n });\n \n if (success) {\n console.log(''โ
Microphone recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐ Stopping microphone recording...'');\n await recorder.stopRecording();\n console.log(''โ
Microphone only test completed!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestMicrophoneOnly();\n\")",
|
|
45
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐ Full stress test - All features + Both audio sources'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function fullStressTest() {\n try {\n const outputPath = ''./test-output/full-stress-test.mov'';\n console.log(''๐น Starting full featured recording (stress test)...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true, // Cursor kayฤฑt\n includeMicrophone: true, // Mikrofon kayฤฑt\n includeSystemAudio: true, // Sistem sesi kayฤฑt\n captureArea: { // Crop area\n x: 300,\n y: 300,\n width: 800,\n height: 500\n }\n });\n \n if (success) {\n console.log(''โ
Full stress test started - waiting 6 seconds'');\n await new Promise(resolve => setTimeout(resolve, 6000));\n \n console.log(''๐ Stopping full stress test...'');\n await recorder.stopRecording();\n console.log(''๐ FULL STRESS TEST COMPLETED WITHOUT CRASH!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''Stack:'', error.stack);\n }\n}\n\nfullStressTest();\n\")",
|
|
46
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐งช Testing version detection and fallback system'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testVersionDetection() {\n try {\n const outputPath = ''./test-output/version-detection-test.mov'';\n console.log(''๐น Starting recording with version detection...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
Recording started - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Version detection test completed successfully!'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestVersionDetection();\n\")",
|
|
47
|
-
"Bash(ELECTRON_RUN_AS_NODE=1 node -e \"\nconsole.log(''๐งช Testing Electron safety measures'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testElectronSafety() {\n try {\n const outputPath = ''./test-output/electron-safe-test.mov'';\n console.log(''๐น Starting recording in Electron environment...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
Recording started safely in Electron - waiting 3 seconds'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n console.log(''๐ Stopping recording...'');\n await recorder.stopRecording();\n console.log(''โ
Electron safety test completed successfully!'');\n } else {\n console.log(''โ ๏ธ Recording failed to start (expected in some cases)'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestElectronSafety();\n\")",
|
|
48
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing AVFoundation fallback path'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testAVFoundationPath() {\n try {\n const outputPath = ''./test-output/force-avfoundation-test.mov'';\n console.log(''๐น Testing AVFoundation (force mode)...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
Recording started'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n await recorder.stopRecording();\n console.log(''โ
AVFoundation test completed!'');\n } else {\n console.log(''โ Recording failed'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestAVFoundationPath();\n\")",
|
|
49
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing AVFoundation fallback path (macOS 14 simulation)'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testAVFoundationPath() {\n try {\n const outputPath = ''./test-output/force-avfoundation-test.mov'';\n console.log(''๐น Testing AVFoundation (simulating macOS 14)...'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
Recording started with AVFoundation'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n await recorder.stopRecording();\n console.log(''โ
AVFoundation test completed successfully!'');\n console.log(''This is how it should work on macOS 14'');\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestAVFoundationPath();\n\")",
|
|
50
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing AVFoundation with detailed debugging'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testWithDebugging() {\n try {\n const outputPath = ''./test-output/debug-test.mov'';\n console.log(''๐น Starting AVFoundation test with debugging...'');\n console.log(''Expected to see detailed AVFoundation logs in console'');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
Recording started'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n \n await recorder.stopRecording();\n console.log(''โ
Test completed'');\n } else {\n console.log(''โ Recording failed - check console logs for AVFoundation details'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestWithDebugging();\n\")",
|
|
51
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing macOS 13/14 path (forced AVFoundation)'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testMacOS1314Path() {\n try {\n const outputPath = ''./test-output/macos1314-test.mov'';\n console.log(''๐น Starting test for macOS 13/14 path...'');\n console.log(''This simulates the exact path macOS 13/14 users will take'');\n console.log('''');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
SUCCESS: Recording started on macOS 13/14 path!'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n await recorder.stopRecording();\n console.log(''โ
Recording completed successfully!'');\n console.log(''๐ Output:'', outputPath);\n } else {\n console.log(''โ Recording failed to start'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n console.log(''This error would be what macOS 13/14 users see'');\n }\n}\n\ntestMacOS1314Path();\n\")",
|
|
52
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐งช Testing macOS 14 Direct AVFoundation Path'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function testDirectAVFoundation() {\n try {\n const outputPath = ''./test-output/direct-avfoundation-test.mov'';\n console.log(''๐น Testing direct AVFoundation path (macOS 14 simulation)...'');\n console.log(''Expected logs:'');\n console.log('' ๐ฏ macOS 14 detected - directly using AVFoundation'');\n console.log('' โญ๏ธ Skipping ScreenCaptureKit completely, jumping to AVFoundation'');\n console.log('' ๐ฌ AVFoundation: Starting recording initialization'');\n console.log('''');\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''โ
SUCCESS: Direct AVFoundation path works!'');\n await new Promise(resolve => setTimeout(resolve, 3000));\n \n await recorder.stopRecording();\n console.log(''โ
Recording completed successfully!'');\n console.log('''');\n console.log(''๐ This is exactly how macOS 14 will work:'');\n console.log(''โ
No ScreenCaptureKit attempts'');\n console.log(''โ
Direct AVFoundation usage'');\n console.log(''โ
No permission errors'');\n } else {\n console.log(''โ Recording failed'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\ntestDirectAVFoundation();\n\")",
|
|
53
|
-
"Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐ฅ FINAL TEST: macOS 14 Direct AVFoundation (NO ScreenCaptureKit)'');\nconst MacRecorder = require(''./index'');\nconst recorder = new MacRecorder();\n\nasync function finalTest() {\n try {\n const outputPath = ''./test-output/final-macos14-test.mov'';\n \n const success = await recorder.startRecording(outputPath, {\n captureCursor: true,\n includeMicrophone: false,\n includeSystemAudio: true\n });\n \n if (success) {\n console.log(''๐ SUCCESS: macOS 14 AVFoundation works!'');\n await new Promise(resolve => setTimeout(resolve, 2000));\n await recorder.stopRecording();\n console.log(''โ
Test completed - macOS 14 is FIXED!'');\n } else {\n console.log(''โ Still failed'');\n }\n } catch (error) {\n console.log(''โ Error:'', error.message);\n }\n}\n\nfinalTest();\n\")"
|
|
4
|
+
"Bash(FORCE_AVFOUNDATION=1 node -e \"const MacRecorder = require(''./index.js''); const recorder = new MacRecorder(); recorder.checkPermissions().then(result => console.log(''Permission result (macOS 14 simulation):'', result))\")"
|
|
54
5
|
],
|
|
55
|
-
"deny": []
|
|
6
|
+
"deny": [],
|
|
7
|
+
"ask": []
|
|
56
8
|
}
|
|
57
9
|
}
|
package/package.json
CHANGED
|
@@ -17,14 +17,14 @@ static int64_t g_avFrameNumber = 0;
|
|
|
17
17
|
static CMTime g_avStartTime;
|
|
18
18
|
|
|
19
19
|
// AVFoundation screen recording implementation
|
|
20
|
-
bool startAVFoundationRecording(const std::string& outputPath,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
extern "C" bool startAVFoundationRecording(const std::string& outputPath,
|
|
21
|
+
CGDirectDisplayID displayID,
|
|
22
|
+
uint32_t windowID,
|
|
23
|
+
CGRect captureRect,
|
|
24
|
+
bool captureCursor,
|
|
25
|
+
bool includeMicrophone,
|
|
26
|
+
bool includeSystemAudio,
|
|
27
|
+
NSString* audioDeviceId) {
|
|
28
28
|
|
|
29
29
|
if (g_avIsRecording) {
|
|
30
30
|
NSLog(@"โ AVFoundation recording already in progress");
|
|
@@ -36,10 +36,7 @@ bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
36
36
|
|
|
37
37
|
// Create output URL
|
|
38
38
|
NSString *outputPathStr = [NSString stringWithUTF8String:outputPath.c_str()];
|
|
39
|
-
NSLog(@"๐ฌ AVFoundation: Output path string: %@", outputPathStr);
|
|
40
|
-
|
|
41
39
|
NSURL *outputURL = [NSURL fileURLWithPath:outputPathStr];
|
|
42
|
-
NSLog(@"๐ฌ AVFoundation: Output URL: %@", outputURL);
|
|
43
40
|
|
|
44
41
|
// Remove existing file
|
|
45
42
|
NSError *removeError = nil;
|
|
@@ -49,23 +46,16 @@ bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
49
46
|
}
|
|
50
47
|
|
|
51
48
|
// Create asset writer
|
|
52
|
-
NSLog(@"๐ฌ AVFoundation: Creating AVAssetWriter...");
|
|
53
49
|
NSError *error = nil;
|
|
54
50
|
g_avWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeQuickTimeMovie error:&error];
|
|
55
51
|
if (!g_avWriter || error) {
|
|
56
52
|
NSLog(@"โ AVFoundation: Failed to create AVAssetWriter: %@", error);
|
|
57
|
-
NSLog(@"โ AVFoundation: Output URL was: %@", outputURL);
|
|
58
53
|
return false;
|
|
59
54
|
}
|
|
60
|
-
NSLog(@"โ
AVFoundation: AVAssetWriter created successfully");
|
|
61
55
|
|
|
62
56
|
// Get display dimensions
|
|
63
|
-
NSLog(@"๐ฌ AVFoundation: Getting display dimensions for display ID %u", displayID);
|
|
64
57
|
CGRect displayBounds = CGDisplayBounds(displayID);
|
|
65
|
-
NSLog(@"๐ฌ AVFoundation: Display bounds: %.0f x %.0f", displayBounds.size.width, displayBounds.size.height);
|
|
66
|
-
|
|
67
58
|
CGSize recordingSize = captureRect.size.width > 0 ? captureRect.size : displayBounds.size;
|
|
68
|
-
NSLog(@"๐ฌ AVFoundation: Recording size: %.0f x %.0f", recordingSize.width, recordingSize.height);
|
|
69
59
|
|
|
70
60
|
// Video settings
|
|
71
61
|
NSDictionary *videoSettings = @{
|
|
@@ -118,6 +108,11 @@ bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
118
108
|
dispatch_queue_t captureQueue = dispatch_queue_create("AVFoundationCaptureQueue", DISPATCH_QUEUE_SERIAL);
|
|
119
109
|
g_avTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, captureQueue);
|
|
120
110
|
|
|
111
|
+
if (!g_avTimer) {
|
|
112
|
+
NSLog(@"โ Failed to create dispatch timer");
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
121
116
|
uint64_t interval = NSEC_PER_SEC / 15; // 15 FPS
|
|
122
117
|
dispatch_source_set_timer(g_avTimer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, interval / 10);
|
|
123
118
|
|
|
@@ -191,7 +186,7 @@ bool startAVFoundationRecording(const std::string& outputPath,
|
|
|
191
186
|
}
|
|
192
187
|
}
|
|
193
188
|
|
|
194
|
-
bool stopAVFoundationRecording() {
|
|
189
|
+
extern "C" bool stopAVFoundationRecording() {
|
|
195
190
|
if (!g_avIsRecording) {
|
|
196
191
|
return true;
|
|
197
192
|
}
|
|
@@ -233,6 +228,6 @@ bool stopAVFoundationRecording() {
|
|
|
233
228
|
}
|
|
234
229
|
}
|
|
235
230
|
|
|
236
|
-
bool isAVFoundationRecording() {
|
|
231
|
+
extern "C" bool isAVFoundationRecording() {
|
|
237
232
|
return g_avIsRecording;
|
|
238
233
|
}
|
package/src/mac_recorder.mm
CHANGED
|
@@ -74,7 +74,13 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
74
74
|
return env.Null();
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// IMPORTANT: Clean up any stale recording state before starting
|
|
78
|
+
// This fixes the issue where macOS 14/13 users get "recording already in progress"
|
|
79
|
+
NSLog(@"๐งน Cleaning up any previous recording state...");
|
|
80
|
+
cleanupRecording();
|
|
81
|
+
|
|
77
82
|
if (g_isRecording) {
|
|
83
|
+
NSLog(@"โ ๏ธ Still recording after cleanup - forcing stop");
|
|
78
84
|
return Napi::Boolean::New(env, false);
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -270,24 +276,27 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
270
276
|
NSLog(@"โ Exception during ScreenCaptureKit startup: %@", sckException.reason);
|
|
271
277
|
}
|
|
272
278
|
|
|
273
|
-
NSLog(@"โ ScreenCaptureKit failed or unsafe");
|
|
279
|
+
NSLog(@"โ ScreenCaptureKit failed or unsafe - will fallback to AVFoundation");
|
|
274
280
|
|
|
275
281
|
} else {
|
|
276
|
-
NSLog(@"โ ScreenCaptureKit availability check failed");
|
|
277
|
-
NSLog(@"โ ScreenCaptureKit not available");
|
|
282
|
+
NSLog(@"โ ScreenCaptureKit availability check failed - will fallback to AVFoundation");
|
|
278
283
|
}
|
|
279
284
|
} @catch (NSException *availabilityException) {
|
|
280
|
-
NSLog(@"โ Exception during ScreenCaptureKit availability check: %@", availabilityException.reason);
|
|
281
|
-
return Napi::Boolean::New(env, false);
|
|
285
|
+
NSLog(@"โ Exception during ScreenCaptureKit availability check - will fallback to AVFoundation: %@", availabilityException.reason);
|
|
282
286
|
}
|
|
287
|
+
|
|
288
|
+
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
289
|
+
NSLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
283
290
|
} else {
|
|
284
|
-
// macOS 14/13 or
|
|
291
|
+
// macOS 14/13 or forced AVFoundation - ALWAYS use AVFoundation
|
|
285
292
|
if (isElectron) {
|
|
286
293
|
NSLog(@"โ Electron environment - Recording not supported");
|
|
287
294
|
return Napi::Boolean::New(env, false);
|
|
288
295
|
}
|
|
289
296
|
|
|
290
|
-
if (
|
|
297
|
+
if (isM15Plus) {
|
|
298
|
+
NSLog(@"๐ฏ macOS 15+ with FORCE_AVFOUNDATION - using AVFoundation");
|
|
299
|
+
} else if (isM14Plus) {
|
|
291
300
|
NSLog(@"๐ฏ macOS 14 detected - using AVFoundation (primary method)");
|
|
292
301
|
} else if (isM13Plus) {
|
|
293
302
|
NSLog(@"๐ฏ macOS 13 detected - using AVFoundation (limited features)");
|
|
@@ -296,23 +305,14 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
296
305
|
return Napi::Boolean::New(env, false);
|
|
297
306
|
}
|
|
298
307
|
|
|
299
|
-
// DIRECT AVFoundation - NO fallback logic
|
|
308
|
+
// DIRECT AVFoundation - NO fallback logic needed
|
|
300
309
|
NSLog(@"โญ๏ธ Using AVFoundation directly - no ScreenCaptureKit attempts");
|
|
301
310
|
}
|
|
302
311
|
|
|
303
312
|
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
304
|
-
useAVFoundation:
|
|
305
313
|
NSLog(@"๐ฅ Starting AVFoundation recording...");
|
|
306
314
|
|
|
307
315
|
@try {
|
|
308
|
-
NSLog(@"๐ง Attempting AVFoundation recording...");
|
|
309
|
-
NSLog(@"๐ง Output path: %s", outputPath.c_str());
|
|
310
|
-
NSLog(@"๐ง Display ID: %u", displayID);
|
|
311
|
-
NSLog(@"๐ง Cursor: %s, Mic: %s, System Audio: %s",
|
|
312
|
-
captureCursor ? "YES" : "NO",
|
|
313
|
-
includeMicrophone ? "YES" : "NO",
|
|
314
|
-
includeSystemAudio ? "YES" : "NO");
|
|
315
|
-
|
|
316
316
|
// Import AVFoundation recording functions (if available)
|
|
317
317
|
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
318
318
|
CGDirectDisplayID displayID,
|
|
@@ -323,13 +323,11 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
323
323
|
bool includeSystemAudio,
|
|
324
324
|
NSString* audioDeviceId);
|
|
325
325
|
|
|
326
|
-
NSLog(@"๐ง Calling startAVFoundationRecording...");
|
|
327
326
|
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
328
327
|
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
329
|
-
NSLog(@"๐ง AVFoundation result: %s", avResult ? "SUCCESS" : "FAILED");
|
|
330
328
|
|
|
331
329
|
if (avResult) {
|
|
332
|
-
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation
|
|
330
|
+
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation");
|
|
333
331
|
NSLog(@"โ
AVFoundation recording started successfully");
|
|
334
332
|
g_isRecording = true;
|
|
335
333
|
return Napi::Boolean::New(env, true);
|
|
@@ -918,40 +916,93 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
|
|
|
918
916
|
Napi::Env env = info.Env();
|
|
919
917
|
|
|
920
918
|
@try {
|
|
919
|
+
// Determine which framework will be used (same logic as startRecording)
|
|
920
|
+
NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
|
|
921
|
+
BOOL isM15Plus = (osVersion.majorVersion >= 15);
|
|
922
|
+
BOOL isM14Plus = (osVersion.majorVersion >= 14);
|
|
923
|
+
BOOL isM13Plus = (osVersion.majorVersion >= 13);
|
|
924
|
+
|
|
925
|
+
// Check for force AVFoundation flag
|
|
926
|
+
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL);
|
|
927
|
+
|
|
928
|
+
// Electron detection
|
|
929
|
+
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
930
|
+
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
931
|
+
(NSProcessInfo.processInfo.processName &&
|
|
932
|
+
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
933
|
+
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
934
|
+
|
|
935
|
+
NSLog(@"๐ Permission check for macOS %ld.%ld.%ld",
|
|
936
|
+
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
937
|
+
|
|
938
|
+
// Determine which framework will be used
|
|
939
|
+
BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation && !isElectron);
|
|
940
|
+
BOOL willUseAVFoundation = (!willUseScreenCaptureKit && (isM13Plus || isM14Plus));
|
|
941
|
+
|
|
942
|
+
if (willUseScreenCaptureKit) {
|
|
943
|
+
NSLog(@"๐ฏ Will use ScreenCaptureKit - checking ScreenCaptureKit permissions");
|
|
944
|
+
} else if (willUseAVFoundation) {
|
|
945
|
+
NSLog(@"๐ฏ Will use AVFoundation - checking AVFoundation permissions");
|
|
946
|
+
} else {
|
|
947
|
+
NSLog(@"โ No compatible recording framework available");
|
|
948
|
+
return Napi::Boolean::New(env, false);
|
|
949
|
+
}
|
|
950
|
+
|
|
921
951
|
// Check screen recording permission
|
|
922
|
-
bool hasScreenPermission =
|
|
923
|
-
|
|
924
|
-
if (@available(macOS
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
1, 1,
|
|
929
|
-
kCVPixelFormatType_32BGRA,
|
|
930
|
-
nil,
|
|
931
|
-
^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
|
|
932
|
-
// Empty handler
|
|
933
|
-
}
|
|
934
|
-
);
|
|
952
|
+
bool hasScreenPermission = false;
|
|
953
|
+
|
|
954
|
+
if (@available(macOS 14.0, *)) {
|
|
955
|
+
// macOS 14+ - Use CGRequestScreenCaptureAccess for both frameworks
|
|
956
|
+
hasScreenPermission = CGRequestScreenCaptureAccess();
|
|
957
|
+
NSLog(@"๐ Screen recording permission: %s", hasScreenPermission ? "GRANTED" : "DENIED");
|
|
935
958
|
|
|
936
|
-
if (
|
|
937
|
-
|
|
959
|
+
if (!hasScreenPermission) {
|
|
960
|
+
NSLog(@"โ Screen recording permission DENIED");
|
|
961
|
+
NSLog(@"๐ Please go to System Settings > Privacy & Security > Screen Recording");
|
|
962
|
+
NSLog(@"๐ and enable permission for your application");
|
|
963
|
+
}
|
|
964
|
+
} else if (@available(macOS 10.15, *)) {
|
|
965
|
+
// macOS 10.15-13 - Try to create a minimal display image to test permissions
|
|
966
|
+
CGImageRef testImage = CGDisplayCreateImage(CGMainDisplayID());
|
|
967
|
+
if (testImage) {
|
|
938
968
|
hasScreenPermission = true;
|
|
969
|
+
CGImageRelease(testImage);
|
|
970
|
+
NSLog(@"๐ Screen recording permission: GRANTED");
|
|
939
971
|
} else {
|
|
940
972
|
hasScreenPermission = false;
|
|
973
|
+
NSLog(@"โ Screen recording permission: DENIED");
|
|
974
|
+
NSLog(@"๐ Please grant screen recording permission in System Preferences");
|
|
941
975
|
}
|
|
976
|
+
} else {
|
|
977
|
+
// macOS < 10.15 - No permission system for screen recording
|
|
978
|
+
hasScreenPermission = true;
|
|
979
|
+
NSLog(@"๐ Screen recording: No permission system (macOS < 10.15)");
|
|
942
980
|
}
|
|
943
981
|
|
|
944
|
-
// Check audio permission
|
|
982
|
+
// Check audio permission based on framework
|
|
945
983
|
bool hasAudioPermission = true;
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
hasAudioPermission =
|
|
984
|
+
|
|
985
|
+
if (willUseScreenCaptureKit) {
|
|
986
|
+
// ScreenCaptureKit handles audio permissions internally
|
|
987
|
+
hasAudioPermission = true;
|
|
988
|
+
NSLog(@"๐ Audio permission: Handled internally by ScreenCaptureKit");
|
|
989
|
+
|
|
990
|
+
} else if (willUseAVFoundation) {
|
|
991
|
+
// For AVFoundation, we don't enforce audio permissions
|
|
992
|
+
// Recording can continue without audio if needed
|
|
993
|
+
hasAudioPermission = true;
|
|
994
|
+
NSLog(@"๐ Audio permission: Will be requested during recording by AVFoundation");
|
|
950
995
|
}
|
|
951
996
|
|
|
997
|
+
NSLog(@"๐ Final permission status:");
|
|
998
|
+
NSLog(@" Framework: %s", willUseScreenCaptureKit ? "ScreenCaptureKit" : "AVFoundation");
|
|
999
|
+
NSLog(@" Screen Recording: %s", hasScreenPermission ? "GRANTED" : "DENIED");
|
|
1000
|
+
NSLog(@" Audio: %s", hasAudioPermission ? "READY" : "NOT READY");
|
|
1001
|
+
|
|
952
1002
|
return Napi::Boolean::New(env, hasScreenPermission && hasAudioPermission);
|
|
953
1003
|
|
|
954
1004
|
} @catch (NSException *exception) {
|
|
1005
|
+
NSLog(@"โ Exception in permission check: %@", exception.reason);
|
|
955
1006
|
return Napi::Boolean::New(env, false);
|
|
956
1007
|
}
|
|
957
1008
|
}
|
package/simple-test.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const WindowSelector = require('./window-selector');
|
|
4
|
-
|
|
5
|
-
async function simpleTest() {
|
|
6
|
-
const selector = new WindowSelector();
|
|
7
|
-
|
|
8
|
-
console.log('๐ Starting simple window selector test...');
|
|
9
|
-
console.log('Move your cursor around - you should see overlay highlighting windows');
|
|
10
|
-
console.log('Press Ctrl+C to exit\n');
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
selector.on('windowEntered', (window) => {
|
|
14
|
-
console.log(`โก๏ธ Entered: ${window.appName} - "${window.title}"`);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
selector.on('windowLeft', (window) => {
|
|
18
|
-
console.log(`โฌ
๏ธ Left: ${window.appName} - "${window.title}"`);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await selector.startSelection();
|
|
22
|
-
|
|
23
|
-
// Keep running until Ctrl+C
|
|
24
|
-
process.on('SIGINT', async () => {
|
|
25
|
-
console.log('\n๐ Stopping...');
|
|
26
|
-
await selector.cleanup();
|
|
27
|
-
process.exit(0);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Prevent the process from exiting
|
|
31
|
-
setInterval(() => {}, 1000);
|
|
32
|
-
|
|
33
|
-
} catch (error) {
|
|
34
|
-
console.error('โ Error:', error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
simpleTest();
|