node-mac-recorder 2.16.6 โ 2.16.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 +3 -50
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +15 -20
- package/src/mac_recorder.mm +104 -70
- package/simple-test.js +0 -38
|
@@ -1,56 +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\")"
|
|
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))\")"
|
|
53
5
|
],
|
|
54
|
-
"deny": []
|
|
6
|
+
"deny": [],
|
|
7
|
+
"ask": []
|
|
55
8
|
}
|
|
56
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
|
@@ -205,9 +205,9 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
205
205
|
NSLog(@"๐ง FORCE_AVFOUNDATION environment variable detected - skipping ScreenCaptureKit");
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
//
|
|
209
|
-
// macOS 14/13
|
|
210
|
-
if (
|
|
208
|
+
// ONLY use ScreenCaptureKit on macOS 15+
|
|
209
|
+
// macOS 14/13 ALWAYS use AVFoundation - NO ScreenCaptureKit attempts
|
|
210
|
+
if (isM15Plus && !forceAVFoundation && !isElectron) {
|
|
211
211
|
NSLog(@"โ
macOS 15+ detected - ScreenCaptureKit available with full compatibility");
|
|
212
212
|
|
|
213
213
|
// Try ScreenCaptureKit with extensive safety measures
|
|
@@ -270,60 +270,43 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
270
270
|
NSLog(@"โ Exception during ScreenCaptureKit startup: %@", sckException.reason);
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
NSLog(@"โ ScreenCaptureKit failed or unsafe");
|
|
273
|
+
NSLog(@"โ ScreenCaptureKit failed or unsafe - will fallback to AVFoundation");
|
|
274
274
|
|
|
275
275
|
} else {
|
|
276
|
-
NSLog(@"โ ScreenCaptureKit availability check failed");
|
|
277
|
-
NSLog(@"โ ScreenCaptureKit not available");
|
|
276
|
+
NSLog(@"โ ScreenCaptureKit availability check failed - will fallback to AVFoundation");
|
|
278
277
|
}
|
|
279
278
|
} @catch (NSException *availabilityException) {
|
|
280
|
-
NSLog(@"โ Exception during ScreenCaptureKit availability check: %@", availabilityException.reason);
|
|
281
|
-
return Napi::Boolean::New(env, false);
|
|
279
|
+
NSLog(@"โ Exception during ScreenCaptureKit availability check - will fallback to AVFoundation: %@", availabilityException.reason);
|
|
282
280
|
}
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
NSLog(@"
|
|
286
|
-
NSLog(@"โญ๏ธ Skipping ScreenCaptureKit completely, jumping to AVFoundation");
|
|
287
|
-
goto useAVFoundation;
|
|
288
|
-
} else if (isM13Plus && !isElectron) {
|
|
289
|
-
// macOS 13 - directly use AVFoundation, skip all ScreenCaptureKit logic
|
|
290
|
-
NSLog(@"๐ฏ macOS 13 detected - directly using AVFoundation (limited features)");
|
|
291
|
-
NSLog(@"โญ๏ธ Skipping ScreenCaptureKit completely, jumping to AVFoundation");
|
|
292
|
-
goto useAVFoundation;
|
|
281
|
+
|
|
282
|
+
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
283
|
+
NSLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
293
284
|
} else {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
285
|
+
// macOS 14/13 or forced AVFoundation - ALWAYS use AVFoundation
|
|
286
|
+
if (isElectron) {
|
|
287
|
+
NSLog(@"โ Electron environment - Recording not supported");
|
|
288
|
+
return Napi::Boolean::New(env, false);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (isM15Plus) {
|
|
292
|
+
NSLog(@"๐ฏ macOS 15+ with FORCE_AVFOUNDATION - using AVFoundation");
|
|
293
|
+
} else if (isM14Plus) {
|
|
294
|
+
NSLog(@"๐ฏ macOS 14 detected - using AVFoundation (primary method)");
|
|
295
|
+
} else if (isM13Plus) {
|
|
296
|
+
NSLog(@"๐ฏ macOS 13 detected - using AVFoundation (limited features)");
|
|
297
|
+
} else {
|
|
298
|
+
NSLog(@"โ macOS version too old (< 13.0) - Not supported");
|
|
299
|
+
return Napi::Boolean::New(env, false);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// DIRECT AVFoundation - NO fallback logic needed
|
|
303
|
+
NSLog(@"โญ๏ธ Using AVFoundation directly - no ScreenCaptureKit attempts");
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
//
|
|
307
|
-
|
|
308
|
-
NSLog(@"๐ ScreenCaptureKit failed on macOS 15+ - attempting AVFoundation fallback");
|
|
309
|
-
} else if (isM14Plus) {
|
|
310
|
-
NSLog(@"๐ฅ Using AVFoundation for macOS 14 compatibility (primary method)");
|
|
311
|
-
} else if (isM13Plus) {
|
|
312
|
-
NSLog(@"๐ฅ Using AVFoundation for macOS 13 compatibility (primary method, limited features)");
|
|
313
|
-
} else {
|
|
314
|
-
NSLog(@"โ Unsupported macOS version for AVFoundation");
|
|
315
|
-
return Napi::Boolean::New(env, false);
|
|
316
|
-
}
|
|
306
|
+
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
307
|
+
NSLog(@"๐ฅ Starting AVFoundation recording...");
|
|
317
308
|
|
|
318
309
|
@try {
|
|
319
|
-
NSLog(@"๐ง Attempting AVFoundation recording...");
|
|
320
|
-
NSLog(@"๐ง Output path: %s", outputPath.c_str());
|
|
321
|
-
NSLog(@"๐ง Display ID: %u", displayID);
|
|
322
|
-
NSLog(@"๐ง Cursor: %s, Mic: %s, System Audio: %s",
|
|
323
|
-
captureCursor ? "YES" : "NO",
|
|
324
|
-
includeMicrophone ? "YES" : "NO",
|
|
325
|
-
includeSystemAudio ? "YES" : "NO");
|
|
326
|
-
|
|
327
310
|
// Import AVFoundation recording functions (if available)
|
|
328
311
|
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
329
312
|
CGDirectDisplayID displayID,
|
|
@@ -334,13 +317,11 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
334
317
|
bool includeSystemAudio,
|
|
335
318
|
NSString* audioDeviceId);
|
|
336
319
|
|
|
337
|
-
NSLog(@"๐ง Calling startAVFoundationRecording...");
|
|
338
320
|
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
339
321
|
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
340
|
-
NSLog(@"๐ง AVFoundation result: %s", avResult ? "SUCCESS" : "FAILED");
|
|
341
322
|
|
|
342
323
|
if (avResult) {
|
|
343
|
-
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation
|
|
324
|
+
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation");
|
|
344
325
|
NSLog(@"โ
AVFoundation recording started successfully");
|
|
345
326
|
g_isRecording = true;
|
|
346
327
|
return Napi::Boolean::New(env, true);
|
|
@@ -929,40 +910,93 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
|
|
|
929
910
|
Napi::Env env = info.Env();
|
|
930
911
|
|
|
931
912
|
@try {
|
|
913
|
+
// Determine which framework will be used (same logic as startRecording)
|
|
914
|
+
NSOperatingSystemVersion osVersion = [[NSProcessInfo processInfo] operatingSystemVersion];
|
|
915
|
+
BOOL isM15Plus = (osVersion.majorVersion >= 15);
|
|
916
|
+
BOOL isM14Plus = (osVersion.majorVersion >= 14);
|
|
917
|
+
BOOL isM13Plus = (osVersion.majorVersion >= 13);
|
|
918
|
+
|
|
919
|
+
// Check for force AVFoundation flag
|
|
920
|
+
BOOL forceAVFoundation = (getenv("FORCE_AVFOUNDATION") != NULL);
|
|
921
|
+
|
|
922
|
+
// Electron detection
|
|
923
|
+
BOOL isElectron = (NSBundle.mainBundle.bundleIdentifier &&
|
|
924
|
+
[NSBundle.mainBundle.bundleIdentifier containsString:@"electron"]) ||
|
|
925
|
+
(NSProcessInfo.processInfo.processName &&
|
|
926
|
+
[NSProcessInfo.processInfo.processName containsString:@"Electron"]) ||
|
|
927
|
+
(NSProcessInfo.processInfo.environment[@"ELECTRON_RUN_AS_NODE"] != nil);
|
|
928
|
+
|
|
929
|
+
NSLog(@"๐ Permission check for macOS %ld.%ld.%ld",
|
|
930
|
+
(long)osVersion.majorVersion, (long)osVersion.minorVersion, (long)osVersion.patchVersion);
|
|
931
|
+
|
|
932
|
+
// Determine which framework will be used
|
|
933
|
+
BOOL willUseScreenCaptureKit = (isM15Plus && !forceAVFoundation && !isElectron);
|
|
934
|
+
BOOL willUseAVFoundation = (!willUseScreenCaptureKit && (isM13Plus || isM14Plus));
|
|
935
|
+
|
|
936
|
+
if (willUseScreenCaptureKit) {
|
|
937
|
+
NSLog(@"๐ฏ Will use ScreenCaptureKit - checking ScreenCaptureKit permissions");
|
|
938
|
+
} else if (willUseAVFoundation) {
|
|
939
|
+
NSLog(@"๐ฏ Will use AVFoundation - checking AVFoundation permissions");
|
|
940
|
+
} else {
|
|
941
|
+
NSLog(@"โ No compatible recording framework available");
|
|
942
|
+
return Napi::Boolean::New(env, false);
|
|
943
|
+
}
|
|
944
|
+
|
|
932
945
|
// Check screen recording permission
|
|
933
|
-
bool hasScreenPermission =
|
|
934
|
-
|
|
935
|
-
if (@available(macOS
|
|
936
|
-
//
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
1, 1,
|
|
940
|
-
kCVPixelFormatType_32BGRA,
|
|
941
|
-
nil,
|
|
942
|
-
^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
|
|
943
|
-
// Empty handler
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
+
bool hasScreenPermission = false;
|
|
947
|
+
|
|
948
|
+
if (@available(macOS 14.0, *)) {
|
|
949
|
+
// macOS 14+ - Use CGRequestScreenCaptureAccess for both frameworks
|
|
950
|
+
hasScreenPermission = CGRequestScreenCaptureAccess();
|
|
951
|
+
NSLog(@"๐ Screen recording permission: %s", hasScreenPermission ? "GRANTED" : "DENIED");
|
|
946
952
|
|
|
947
|
-
if (
|
|
948
|
-
|
|
953
|
+
if (!hasScreenPermission) {
|
|
954
|
+
NSLog(@"โ Screen recording permission DENIED");
|
|
955
|
+
NSLog(@"๐ Please go to System Settings > Privacy & Security > Screen Recording");
|
|
956
|
+
NSLog(@"๐ and enable permission for your application");
|
|
957
|
+
}
|
|
958
|
+
} else if (@available(macOS 10.15, *)) {
|
|
959
|
+
// macOS 10.15-13 - Try to create a minimal display image to test permissions
|
|
960
|
+
CGImageRef testImage = CGDisplayCreateImage(CGMainDisplayID());
|
|
961
|
+
if (testImage) {
|
|
949
962
|
hasScreenPermission = true;
|
|
963
|
+
CGImageRelease(testImage);
|
|
964
|
+
NSLog(@"๐ Screen recording permission: GRANTED");
|
|
950
965
|
} else {
|
|
951
966
|
hasScreenPermission = false;
|
|
967
|
+
NSLog(@"โ Screen recording permission: DENIED");
|
|
968
|
+
NSLog(@"๐ Please grant screen recording permission in System Preferences");
|
|
952
969
|
}
|
|
970
|
+
} else {
|
|
971
|
+
// macOS < 10.15 - No permission system for screen recording
|
|
972
|
+
hasScreenPermission = true;
|
|
973
|
+
NSLog(@"๐ Screen recording: No permission system (macOS < 10.15)");
|
|
953
974
|
}
|
|
954
975
|
|
|
955
|
-
// Check audio permission
|
|
976
|
+
// Check audio permission based on framework
|
|
956
977
|
bool hasAudioPermission = true;
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
hasAudioPermission =
|
|
978
|
+
|
|
979
|
+
if (willUseScreenCaptureKit) {
|
|
980
|
+
// ScreenCaptureKit handles audio permissions internally
|
|
981
|
+
hasAudioPermission = true;
|
|
982
|
+
NSLog(@"๐ Audio permission: Handled internally by ScreenCaptureKit");
|
|
983
|
+
|
|
984
|
+
} else if (willUseAVFoundation) {
|
|
985
|
+
// For AVFoundation, we don't enforce audio permissions
|
|
986
|
+
// Recording can continue without audio if needed
|
|
987
|
+
hasAudioPermission = true;
|
|
988
|
+
NSLog(@"๐ Audio permission: Will be requested during recording by AVFoundation");
|
|
961
989
|
}
|
|
962
990
|
|
|
991
|
+
NSLog(@"๐ Final permission status:");
|
|
992
|
+
NSLog(@" Framework: %s", willUseScreenCaptureKit ? "ScreenCaptureKit" : "AVFoundation");
|
|
993
|
+
NSLog(@" Screen Recording: %s", hasScreenPermission ? "GRANTED" : "DENIED");
|
|
994
|
+
NSLog(@" Audio: %s", hasAudioPermission ? "READY" : "NOT READY");
|
|
995
|
+
|
|
963
996
|
return Napi::Boolean::New(env, hasScreenPermission && hasAudioPermission);
|
|
964
997
|
|
|
965
998
|
} @catch (NSException *exception) {
|
|
999
|
+
NSLog(@"โ Exception in permission check: %@", exception.reason);
|
|
966
1000
|
return Napi::Boolean::New(env, false);
|
|
967
1001
|
}
|
|
968
1002
|
}
|
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();
|