node-mac-recorder 2.16.12 โ†’ 2.16.13

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing fixed AVFoundation...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.startRecording(''/tmp/electron-test2.mov'')\n .then(success => {\n console.log(''Start:'', success ? ''โœ… SUCCESS'' : ''โŒ FAILED'');\n if (success) {\n setTimeout(() => {\n console.log(''โน๏ธ Stopping...'');\n recorder.stopRecording().then(() => {\n console.log(''โœ… Stop completed'');\n const fs = require(''fs'');\n if (fs.existsSync(''/tmp/electron-test2.mov'')) {\n console.log(''๐Ÿ“น File:'', Math.round(fs.statSync(''/tmp/electron-test2.mov'').size/1024) + ''KB'');\n console.log(''๐ŸŽ‰ Fix successful!'');\n }\n });\n }, 2000);\n }\n })\n .catch(console.error);\n\")"
4
+ "Bash(FORCE_AVFOUNDATION=1 node -e \"\nconsole.log(''๐Ÿงช Testing Electron crash fix (macOS 14 simulation)...'');\nconst MacRecorder = require(''./index.js'');\nconst recorder = new MacRecorder();\n\nrecorder.on(''recordingStarted'', (details) => {\n console.log(''โœ… Recording started safely'');\n});\n\nrecorder.on(''stopped'', () => {\n console.log(''โœ… Recording stopped without crash'');\n});\n\nlet recordingStarted = false;\nrecorder.startRecording(''/tmp/crash-fix-test.mov'')\n .then(success => {\n console.log(''Start result:'', success ? ''โœ… SUCCESS'' : ''โŒ FAILED'');\n if (success) {\n recordingStarted = true;\n // Test quick start/stop cycles to stress test memory handling\n setTimeout(() => {\n console.log(''โน๏ธ Quick stop test...'');\n recorder.stopRecording().then(() => {\n console.log(''โœ… Quick stop completed'');\n \n // Test immediate restart\n setTimeout(() => {\n console.log(''๐Ÿ”„ Testing immediate restart...'');\n recorder.startRecording(''/tmp/crash-fix-test2.mov'').then(() => {\n setTimeout(() => {\n recorder.stopRecording().then(() => {\n console.log(''๐ŸŽ‰ Crash fix test completed successfully!'');\n const fs = require(''fs'');\n const files = [''/tmp/crash-fix-test.mov'', ''/tmp/crash-fix-test2.mov''];\n files.forEach(file => {\n if (fs.existsSync(file)) {\n const size = Math.round(fs.statSync(file).size/1024);\n console.log(''๐Ÿ“น '' + file + '':'', size + ''KB'');\n }\n });\n });\n }, 1000);\n });\n }, 500);\n });\n }, 2000);\n }\n })\n .catch(err => {\n console.error(''โŒ Error:'', err);\n if (recordingStarted) {\n recorder.stopRecording();\n }\n });\n\")"
5
5
  ],
6
6
  "deny": [],
7
7
  "ask": []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.16.12",
3
+ "version": "2.16.13",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -135,9 +135,20 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
135
135
  uint64_t interval = NSEC_PER_SEC / 10; // 10 FPS for Electron stability
136
136
  dispatch_source_set_timer(g_avTimer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, interval / 10);
137
137
 
138
+ // Retain objects before passing to block to prevent deallocation
139
+ AVAssetWriterInput *localVideoInput = g_avVideoInput;
140
+ AVAssetWriterInputPixelBufferAdaptor *localPixelBufferAdaptor = g_avPixelBufferAdaptor;
141
+
138
142
  dispatch_source_set_event_handler(g_avTimer, ^{
139
143
  if (!g_avIsRecording) return;
140
144
 
145
+ // Additional null checks for Electron safety
146
+ if (!localVideoInput || !localPixelBufferAdaptor) {
147
+ NSLog(@"โš ๏ธ Video input or pixel buffer adaptor is nil, stopping recording");
148
+ g_avIsRecording = false;
149
+ return;
150
+ }
151
+
141
152
  @autoreleasepool {
142
153
  @try {
143
154
  // Capture screen with Electron-safe error handling
@@ -159,7 +170,7 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
159
170
 
160
171
  // Convert to pixel buffer with Electron-safe error handling
161
172
  CVPixelBufferRef pixelBuffer = nil;
162
- CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, g_avPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
173
+ CVReturn cvRet = CVPixelBufferPoolCreatePixelBuffer(NULL, localPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
163
174
 
164
175
  if (cvRet == kCVReturnSuccess && pixelBuffer) {
165
176
  CVPixelBufferLockBaseAddress(pixelBuffer, 0);
@@ -201,9 +212,9 @@ extern "C" bool startAVFoundationRecording(const std::string& outputPath,
201
212
  CGContextRelease(context);
202
213
 
203
214
  // Write frame only if input is ready
204
- if (g_avVideoInput && g_avVideoInput.readyForMoreMediaData) {
215
+ if (localVideoInput && localVideoInput.readyForMoreMediaData) {
205
216
  CMTime frameTime = CMTimeAdd(g_avStartTime, CMTimeMakeWithSeconds(g_avFrameNumber / 10.0, 600));
206
- BOOL appendSuccess = [g_avPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
217
+ BOOL appendSuccess = [localPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:frameTime];
207
218
  if (appendSuccess) {
208
219
  g_avFrameNumber++;
209
220
  } else {
@@ -253,22 +264,36 @@ extern "C" bool stopAVFoundationRecording() {
253
264
  @try {
254
265
  // Stop timer with Electron-safe cleanup
255
266
  if (g_avTimer) {
267
+ // Mark as not recording FIRST to stop timer callbacks
268
+ g_avIsRecording = false;
269
+
270
+ // Cancel timer and wait a brief moment for completion
256
271
  dispatch_source_cancel(g_avTimer);
272
+
273
+ // Use async to avoid deadlock in Electron
274
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 100 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
275
+ // Timer should be fully cancelled by now
276
+ });
277
+
257
278
  g_avTimer = nil;
258
279
  NSLog(@"โœ… AVFoundation timer stopped safely");
259
280
  }
260
281
 
261
- // Finish writing
262
- if (g_avVideoInput) {
263
- [g_avVideoInput markAsFinished];
282
+ // Finish writing with null checks
283
+ AVAssetWriterInput *writerInput = g_avVideoInput;
284
+ if (writerInput) {
285
+ [writerInput markAsFinished];
264
286
  }
265
287
 
266
- if (g_avWriter && g_avWriter.status == AVAssetWriterStatusWriting) {
288
+ AVAssetWriter *writer = g_avWriter;
289
+ if (writer && writer.status == AVAssetWriterStatusWriting) {
267
290
  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
268
- [g_avWriter finishWritingWithCompletionHandler:^{
291
+ [writer finishWritingWithCompletionHandler:^{
269
292
  dispatch_semaphore_signal(semaphore);
270
293
  }];
271
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
294
+ // Add timeout to prevent infinite wait in Electron
295
+ dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
296
+ dispatch_semaphore_wait(semaphore, timeout);
272
297
  }
273
298
 
274
299
  // Cleanup