node-mac-recorder 2.16.7 โ 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 -51
- package/package.json +1 -1
- package/src/avfoundation_recorder.mm +15 -20
- package/src/mac_recorder.mm +85 -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
|
@@ -270,24 +270,27 @@ 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
|
}
|
|
281
|
+
|
|
282
|
+
// If we reach here, ScreenCaptureKit failed, so fall through to AVFoundation
|
|
283
|
+
NSLog(@"โญ๏ธ ScreenCaptureKit failed - falling back to AVFoundation");
|
|
283
284
|
} else {
|
|
284
|
-
// macOS 14/13 or
|
|
285
|
+
// macOS 14/13 or forced AVFoundation - ALWAYS use AVFoundation
|
|
285
286
|
if (isElectron) {
|
|
286
287
|
NSLog(@"โ Electron environment - Recording not supported");
|
|
287
288
|
return Napi::Boolean::New(env, false);
|
|
288
289
|
}
|
|
289
290
|
|
|
290
|
-
if (
|
|
291
|
+
if (isM15Plus) {
|
|
292
|
+
NSLog(@"๐ฏ macOS 15+ with FORCE_AVFOUNDATION - using AVFoundation");
|
|
293
|
+
} else if (isM14Plus) {
|
|
291
294
|
NSLog(@"๐ฏ macOS 14 detected - using AVFoundation (primary method)");
|
|
292
295
|
} else if (isM13Plus) {
|
|
293
296
|
NSLog(@"๐ฏ macOS 13 detected - using AVFoundation (limited features)");
|
|
@@ -296,23 +299,14 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
296
299
|
return Napi::Boolean::New(env, false);
|
|
297
300
|
}
|
|
298
301
|
|
|
299
|
-
// DIRECT AVFoundation - NO fallback logic
|
|
302
|
+
// DIRECT AVFoundation - NO fallback logic needed
|
|
300
303
|
NSLog(@"โญ๏ธ Using AVFoundation directly - no ScreenCaptureKit attempts");
|
|
301
304
|
}
|
|
302
305
|
|
|
303
306
|
// AVFoundation recording (either fallback from ScreenCaptureKit or direct)
|
|
304
|
-
useAVFoundation:
|
|
305
307
|
NSLog(@"๐ฅ Starting AVFoundation recording...");
|
|
306
308
|
|
|
307
309
|
@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
310
|
// Import AVFoundation recording functions (if available)
|
|
317
311
|
extern bool startAVFoundationRecording(const std::string& outputPath,
|
|
318
312
|
CGDirectDisplayID displayID,
|
|
@@ -323,13 +317,11 @@ Napi::Value StartRecording(const Napi::CallbackInfo& info) {
|
|
|
323
317
|
bool includeSystemAudio,
|
|
324
318
|
NSString* audioDeviceId);
|
|
325
319
|
|
|
326
|
-
NSLog(@"๐ง Calling startAVFoundationRecording...");
|
|
327
320
|
bool avResult = startAVFoundationRecording(outputPath, displayID, windowID, captureRect,
|
|
328
321
|
captureCursor, includeMicrophone, includeSystemAudio, audioDeviceId);
|
|
329
|
-
NSLog(@"๐ง AVFoundation result: %s", avResult ? "SUCCESS" : "FAILED");
|
|
330
322
|
|
|
331
323
|
if (avResult) {
|
|
332
|
-
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation
|
|
324
|
+
NSLog(@"๐ฅ RECORDING METHOD: AVFoundation");
|
|
333
325
|
NSLog(@"โ
AVFoundation recording started successfully");
|
|
334
326
|
g_isRecording = true;
|
|
335
327
|
return Napi::Boolean::New(env, true);
|
|
@@ -918,40 +910,93 @@ Napi::Value CheckPermissions(const Napi::CallbackInfo& info) {
|
|
|
918
910
|
Napi::Env env = info.Env();
|
|
919
911
|
|
|
920
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
|
+
|
|
921
945
|
// 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
|
-
);
|
|
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");
|
|
935
952
|
|
|
936
|
-
if (
|
|
937
|
-
|
|
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) {
|
|
938
962
|
hasScreenPermission = true;
|
|
963
|
+
CGImageRelease(testImage);
|
|
964
|
+
NSLog(@"๐ Screen recording permission: GRANTED");
|
|
939
965
|
} else {
|
|
940
966
|
hasScreenPermission = false;
|
|
967
|
+
NSLog(@"โ Screen recording permission: DENIED");
|
|
968
|
+
NSLog(@"๐ Please grant screen recording permission in System Preferences");
|
|
941
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)");
|
|
942
974
|
}
|
|
943
975
|
|
|
944
|
-
// Check audio permission
|
|
976
|
+
// Check audio permission based on framework
|
|
945
977
|
bool hasAudioPermission = true;
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
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");
|
|
950
989
|
}
|
|
951
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
|
+
|
|
952
996
|
return Napi::Boolean::New(env, hasScreenPermission && hasAudioPermission);
|
|
953
997
|
|
|
954
998
|
} @catch (NSException *exception) {
|
|
999
|
+
NSLog(@"โ Exception in permission check: %@", exception.reason);
|
|
955
1000
|
return Napi::Boolean::New(env, false);
|
|
956
1001
|
}
|
|
957
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();
|