node-mac-recorder 2.4.10 → 2.4.12

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/binding.gyp CHANGED
@@ -20,32 +20,25 @@
20
20
  "xcode_settings": {
21
21
  "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
22
22
  "CLANG_CXX_LIBRARY": "libc++",
23
- "MACOSX_DEPLOYMENT_TARGET": "12.3",
24
- "ARCHS": ["arm64"],
25
- "VALID_ARCHS": ["arm64"],
23
+ "MACOSX_DEPLOYMENT_TARGET": "10.15",
26
24
  "OTHER_CFLAGS": [
27
- "-ObjC++",
28
- "-fmodules"
29
- ],
30
- "CLANG_ENABLE_OBJC_ARC": "YES"
25
+ "-ObjC++"
26
+ ]
31
27
  },
32
28
  "link_settings": {
33
29
  "libraries": [
34
- "-framework ScreenCaptureKit",
35
30
  "-framework AVFoundation",
36
31
  "-framework CoreMedia",
37
32
  "-framework CoreVideo",
38
33
  "-framework Foundation",
39
34
  "-framework AppKit",
35
+ "-framework ScreenCaptureKit",
40
36
  "-framework ApplicationServices",
41
37
  "-framework Carbon",
42
38
  "-framework Accessibility"
43
39
  ]
44
40
  },
45
- "defines": [
46
- "NAPI_DISABLE_CPP_EXCEPTIONS",
47
- "USE_SCREENCAPTUREKIT=1"
48
- ]
41
+ "defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ]
49
42
  }
50
43
  ]
51
44
  }
package/index.js CHANGED
@@ -5,25 +5,16 @@ const fs = require("fs");
5
5
  // Native modülü yükle
6
6
  let nativeBinding;
7
7
  try {
8
- // Prefer prebuild on arm64
9
- if (process.platform === "darwin" && process.arch === "arm64") {
10
- nativeBinding = require("./prebuilds/darwin-arm64/node.napi.node");
11
- } else {
12
- nativeBinding = require("./build/Release/mac_recorder.node");
13
- }
8
+ nativeBinding = require("./build/Release/mac_recorder.node");
14
9
  } catch (error) {
15
10
  try {
16
- nativeBinding = require("./build/Release/mac_recorder.node");
17
- } catch (_) {
18
- try {
19
- nativeBinding = require("./build/Debug/mac_recorder.node");
20
- } catch (debugError) {
21
- throw new Error(
22
- 'Native module not found. Please run "npm run build" to compile the native module.\n' +
23
- "Original error: " +
24
- error.message
25
- );
26
- }
11
+ nativeBinding = require("./build/Debug/mac_recorder.node");
12
+ } catch (debugError) {
13
+ throw new Error(
14
+ 'Native module not found. Please run "npm run build" to compile the native module.\n' +
15
+ "Original error: " +
16
+ error.message
17
+ );
27
18
  }
28
19
  }
29
20
 
@@ -127,11 +118,6 @@ class MacRecorder extends EventEmitter {
127
118
  audioDeviceId: options.audioDeviceId || null, // null = default device
128
119
  systemAudioDeviceId: options.systemAudioDeviceId || null, // null = auto-detect system audio device
129
120
  captureArea: options.captureArea || null,
130
- // Exclusion options
131
- excludeCurrentApp: options.excludeCurrentApp || false,
132
- excludeWindowIds: Array.isArray(options.excludeWindowIds)
133
- ? options.excludeWindowIds
134
- : [],
135
121
  };
136
122
  }
137
123
 
@@ -181,12 +167,11 @@ class MacRecorder extends EventEmitter {
181
167
  targetDisplayId = display.id; // Use actual display ID, not array index
182
168
  // Koordinatları display'e göre normalize et
183
169
  adjustedX = targetWindow.x - display.x;
184
-
170
+
185
171
  // Y coordinate conversion: CGWindow (top-left) to AVFoundation (bottom-left)
186
172
  // Overlay'deki dönüşümle aynı mantık: screenHeight - windowY - windowHeight
187
173
  const displayHeight = parseInt(display.resolution.split("x")[1]);
188
- const convertedY =
189
- displayHeight - targetWindow.y - targetWindow.height;
174
+ const convertedY = displayHeight - targetWindow.y - targetWindow.height;
190
175
  adjustedY = Math.max(0, convertedY - display.y);
191
176
  break;
192
177
  }
@@ -221,9 +206,7 @@ class MacRecorder extends EventEmitter {
221
206
  this.options.displayId = targetDisplayId;
222
207
 
223
208
  // Recording için display bilgisini sakla (cursor capture için)
224
- const targetDisplay = displays.find(
225
- (d) => d.id === targetDisplayId
226
- );
209
+ const targetDisplay = displays.find(d => d.id === targetDisplayId);
227
210
  this.recordingDisplayInfo = {
228
211
  displayId: targetDisplayId,
229
212
  x: targetDisplay.x,
@@ -256,9 +239,7 @@ class MacRecorder extends EventEmitter {
256
239
  if (this.options.displayId !== null && !this.recordingDisplayInfo) {
257
240
  try {
258
241
  const displays = await this.getDisplays();
259
- const targetDisplay = displays.find(
260
- (d) => d.id === this.options.displayId
261
- );
242
+ const targetDisplay = displays.find(d => d.id === this.options.displayId);
262
243
  if (targetDisplay) {
263
244
  this.recordingDisplayInfo = {
264
245
  displayId: this.options.displayId,
@@ -292,9 +273,6 @@ class MacRecorder extends EventEmitter {
292
273
  windowId: this.options.windowId || null, // null = tam ekran
293
274
  audioDeviceId: this.options.audioDeviceId || null, // null = default device
294
275
  systemAudioDeviceId: this.options.systemAudioDeviceId || null, // null = auto-detect system audio device
295
- // Exclusion options passthrough
296
- excludeCurrentApp: this.options.excludeCurrentApp || false,
297
- excludeWindowIds: this.options.excludeWindowIds || [],
298
276
  };
299
277
 
300
278
  // Manuel captureArea varsa onu kullan
@@ -332,13 +310,13 @@ class MacRecorder extends EventEmitter {
332
310
  if (nativeStatus && !recordingStartedEmitted) {
333
311
  recordingStartedEmitted = true;
334
312
  clearInterval(checkRecordingStatus);
335
-
313
+
336
314
  // Kayıt gerçekten başladığı anda event emit et
337
315
  this.emit("recordingStarted", {
338
316
  outputPath: this.outputPath,
339
317
  timestamp: Date.now(), // Gerçek başlangıç zamanı
340
318
  options: this.options,
341
- nativeConfirmed: true,
319
+ nativeConfirmed: true
342
320
  });
343
321
  }
344
322
  } catch (error) {
@@ -350,12 +328,12 @@ class MacRecorder extends EventEmitter {
350
328
  outputPath: this.outputPath,
351
329
  timestamp: this.recordingStartTime,
352
330
  options: this.options,
353
- nativeConfirmed: false,
331
+ nativeConfirmed: false
354
332
  });
355
333
  }
356
334
  }
357
335
  }, 50); // Her 50ms kontrol et
358
-
336
+
359
337
  // Timeout fallback - 5 saniye sonra hala başlamamışsa emit et
360
338
  setTimeout(() => {
361
339
  if (!recordingStartedEmitted) {
@@ -365,11 +343,11 @@ class MacRecorder extends EventEmitter {
365
343
  outputPath: this.outputPath,
366
344
  timestamp: this.recordingStartTime,
367
345
  options: this.options,
368
- nativeConfirmed: false,
346
+ nativeConfirmed: false
369
347
  });
370
348
  }
371
349
  }, 5000);
372
-
350
+
373
351
  this.emit("started", this.outputPath);
374
352
  resolve(this.outputPath);
375
353
  } else {
@@ -610,7 +588,7 @@ class MacRecorder extends EventEmitter {
610
588
  width: options.windowInfo.width,
611
589
  height: options.windowInfo.height,
612
590
  windowRelative: true,
613
- windowInfo: options.windowInfo,
591
+ windowInfo: options.windowInfo
614
592
  };
615
593
  } else if (this.recordingDisplayInfo) {
616
594
  // Recording başlatılmışsa o display'i kullan
@@ -666,7 +644,7 @@ class MacRecorder extends EventEmitter {
666
644
  if (this.cursorDisplayInfo.windowRelative) {
667
645
  // Window-relative koordinatlar
668
646
  coordinateSystem = "window-relative";
669
-
647
+
670
648
  // Window bounds kontrolü - cursor window dışındaysa kaydetme
671
649
  if (
672
650
  x < 0 ||
@@ -679,7 +657,7 @@ class MacRecorder extends EventEmitter {
679
657
  } else {
680
658
  // Display-relative koordinatlar
681
659
  coordinateSystem = "display-relative";
682
-
660
+
683
661
  // Display bounds kontrolü
684
662
  if (
685
663
  x < 0 ||
@@ -704,9 +682,9 @@ class MacRecorder extends EventEmitter {
704
682
  windowInfo: {
705
683
  width: this.cursorDisplayInfo.width,
706
684
  height: this.cursorDisplayInfo.height,
707
- originalWindow: this.cursorDisplayInfo.windowInfo,
708
- },
709
- }),
685
+ originalWindow: this.cursorDisplayInfo.windowInfo
686
+ }
687
+ })
710
688
  };
711
689
 
712
690
  // Sadece eventType değiştiğinde veya pozisyon değiştiğinde kaydet
@@ -950,66 +928,9 @@ class MacRecorder extends EventEmitter {
950
928
 
951
929
  return Promise.all(windowPromises);
952
930
  }
953
-
954
- // Window Selection Methods (ScreenCaptureKit compatible)
955
- startWindowSelection() {
956
- if (!nativeBinding.startWindowSelection) {
957
- throw new Error('Window selection is not available in this build');
958
- }
959
- return nativeBinding.startWindowSelection();
960
- }
961
-
962
- stopWindowSelection() {
963
- if (!nativeBinding.stopWindowSelection) {
964
- return false;
965
- }
966
- return nativeBinding.stopWindowSelection();
967
- }
968
-
969
- getSelectedWindowInfo() {
970
- if (!nativeBinding.getSelectedWindowInfo) {
971
- return null;
972
- }
973
- return nativeBinding.getSelectedWindowInfo();
974
- }
975
-
976
- getWindowSelectionStatus() {
977
- if (!nativeBinding.getWindowSelectionStatus) {
978
- return { isSelecting: false, hasSelectedWindow: false };
979
- }
980
- return nativeBinding.getWindowSelectionStatus();
981
- }
982
-
983
- bringWindowToFront(windowId) {
984
- if (!nativeBinding.bringWindowToFront) {
985
- return false;
986
- }
987
- return nativeBinding.bringWindowToFront(windowId);
988
- }
989
-
990
- setBringToFrontEnabled(enabled) {
991
- if (!nativeBinding.setBringToFrontEnabled) {
992
- return false;
993
- }
994
- return nativeBinding.setBringToFrontEnabled(enabled);
995
- }
996
-
997
- showRecordingPreview(windowInfo) {
998
- if (!nativeBinding.showRecordingPreview) {
999
- return false;
1000
- }
1001
- return nativeBinding.showRecordingPreview(windowInfo);
1002
- }
1003
-
1004
- hideRecordingPreview() {
1005
- if (!nativeBinding.hideRecordingPreview) {
1006
- return false;
1007
- }
1008
- return nativeBinding.hideRecordingPreview();
1009
- }
1010
931
  }
1011
932
 
1012
933
  // WindowSelector modülünü de export edelim
1013
- MacRecorder.WindowSelector = require("./window-selector");
934
+ MacRecorder.WindowSelector = require('./window-selector');
1014
935
 
1015
936
  module.exports = MacRecorder;
package/install.js CHANGED
@@ -2,7 +2,7 @@ const { spawn } = require("child_process");
2
2
  const fs = require("fs");
3
3
  const path = require("path");
4
4
 
5
- console.log("🔨 Installing node-mac-recorder...\n");
5
+ console.log("🔨 Building native macOS recorder module...\n");
6
6
 
7
7
  // Check if we're on macOS
8
8
  if (process.platform !== "darwin") {
@@ -10,24 +10,7 @@ if (process.platform !== "darwin") {
10
10
  process.exit(1);
11
11
  }
12
12
 
13
- // Prefer prebuilds on supported platforms
14
- const prebuildPath = path.join(
15
- __dirname,
16
- "prebuilds",
17
- `darwin-${process.arch}`,
18
- "node.napi.node"
19
- );
20
- if (
21
- process.platform === "darwin" &&
22
- process.arch === "arm64" &&
23
- fs.existsSync(prebuildPath)
24
- ) {
25
- console.log("✅ Using prebuilt binary:", prebuildPath);
26
- console.log("🎉 node-mac-recorder is ready to use (no compilation needed)");
27
- process.exit(0);
28
- }
29
-
30
- // Fallback to building from source
13
+ // Check if Xcode Command Line Tools are installed
31
14
  console.log("🔍 Checking Xcode Command Line Tools...");
32
15
  const xcodebuild = spawn("xcode-select", ["--print-path"], { stdio: "pipe" });
33
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.4.10",
3
+ "version": "2.4.12",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -37,9 +37,6 @@
37
37
  "install": "node install.js",
38
38
  "build": "node-gyp build",
39
39
  "rebuild": "node-gyp rebuild",
40
- "prebuild:node-arm64": "prebuildify --platform darwin --arch arm64 --napi --strip",
41
- "prebuild:electron-arm64": "prebuildify --platform darwin --arch arm64 --napi --strip --targets electron@27.0.0",
42
- "prebuild:all-arm64": "npm run prebuild:node-arm64 && npm run prebuild:electron-arm64",
43
40
  "clean": "node-gyp clean",
44
41
  "test:window-selector": "node window-selector-test.js",
45
42
  "example:window-selector": "node examples/window-selector-example.js"
@@ -48,8 +45,7 @@
48
45
  "node-addon-api": "^7.0.0"
49
46
  },
50
47
  "devDependencies": {
51
- "node-gyp": "^10.0.0",
52
- "prebuildify": "^6.0.1"
48
+ "node-gyp": "^10.0.0"
53
49
  },
54
50
  "gypfile": true
55
51
  }
@@ -1,11 +1,9 @@
1
- #import <ScreenCaptureKit/ScreenCaptureKit.h>
2
1
  #import <AVFoundation/AVFoundation.h>
3
2
  #import <CoreAudio/CoreAudio.h>
4
3
 
5
4
  @interface AudioCapture : NSObject
6
5
 
7
6
  + (NSArray *)getAudioDevices;
8
- + (NSArray *)getSystemAudioDevices;
9
7
  + (BOOL)hasAudioPermission;
10
8
  + (void)requestAudioPermission:(void(^)(BOOL granted))completion;
11
9
 
@@ -16,39 +14,25 @@
16
14
  + (NSArray *)getAudioDevices {
17
15
  NSMutableArray *devices = [NSMutableArray array];
18
16
 
19
- // Get microphone devices using AVFoundation
20
- AVCaptureDeviceDiscoverySession *discoverySession = [AVCaptureDeviceDiscoverySession
21
- discoverySessionWithDeviceTypes:@[AVCaptureDeviceTypeBuiltInMicrophone, AVCaptureDeviceTypeExternalUnknown]
22
- mediaType:AVMediaTypeAudio
23
- position:AVCaptureDevicePositionUnspecified];
24
- NSArray *audioDevices = discoverySession.devices;
17
+ // Get all audio devices
18
+ NSArray *audioDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio];
25
19
 
26
20
  for (AVCaptureDevice *device in audioDevices) {
27
21
  NSDictionary *deviceInfo = @{
28
22
  @"id": device.uniqueID,
29
23
  @"name": device.localizedName,
30
24
  @"manufacturer": device.manufacturer ?: @"Unknown",
31
- @"type": @"microphone",
32
25
  @"isDefault": @([device isEqual:[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]])
33
26
  };
34
27
 
35
28
  [devices addObject:deviceInfo];
36
29
  }
37
30
 
38
- // Add system audio devices using Core Audio API
39
- NSArray *systemDevices = [self getSystemAudioDevices];
40
- [devices addObjectsFromArray:systemDevices];
41
-
42
- return [devices copy];
43
- }
44
-
45
- + (NSArray *)getSystemAudioDevices {
46
- NSMutableArray *devices = [NSMutableArray array];
47
-
31
+ // Also get system audio devices using Core Audio
48
32
  AudioObjectPropertyAddress propertyAddress = {
49
33
  kAudioHardwarePropertyDevices,
50
34
  kAudioObjectPropertyScopeGlobal,
51
- kAudioObjectPropertyElementMain // Changed from kAudioObjectPropertyElementMaster (deprecated)
35
+ kAudioObjectPropertyElementMaster
52
36
  };
53
37
 
54
38
  UInt32 dataSize = 0;
@@ -65,41 +49,37 @@
65
49
  AudioDeviceID deviceID = audioDeviceIDs[i];
66
50
 
67
51
  // Get device name
52
+ propertyAddress.mSelector = kAudioDevicePropertyDeviceNameCFString;
53
+ propertyAddress.mScope = kAudioObjectPropertyScopeGlobal;
54
+
68
55
  CFStringRef deviceName = NULL;
69
- UInt32 size = sizeof(deviceName);
70
- AudioObjectPropertyAddress nameAddress = {
71
- kAudioDevicePropertyDeviceNameCFString,
72
- kAudioDevicePropertyScopeOutput, // Focus on output devices for system audio
73
- kAudioObjectPropertyElementMain
74
- };
56
+ dataSize = sizeof(deviceName);
75
57
 
76
- status = AudioObjectGetPropertyData(deviceID, &nameAddress, 0, NULL, &size, &deviceName);
58
+ status = AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, &deviceName);
77
59
 
78
60
  if (status == kAudioHardwareNoError && deviceName) {
79
- // Check if this is an output device
80
- AudioObjectPropertyAddress streamAddress = {
81
- kAudioDevicePropertyStreams,
82
- kAudioDevicePropertyScopeOutput,
83
- kAudioObjectPropertyElementMain
84
- };
61
+ // Check if it's an input device
62
+ propertyAddress.mSelector = kAudioDevicePropertyStreamConfiguration;
63
+ propertyAddress.mScope = kAudioDevicePropertyScopeInput;
85
64
 
86
- UInt32 streamSize = 0;
87
- status = AudioObjectGetPropertyDataSize(deviceID, &streamAddress, 0, NULL, &streamSize);
65
+ AudioObjectGetPropertyDataSize(deviceID, &propertyAddress, 0, NULL, &dataSize);
88
66
 
89
- if (status == kAudioHardwareNoError && streamSize > 0) {
90
- // This is an output device - can be used for system audio capture
91
- const char *name = CFStringGetCStringPtr(deviceName, kCFStringEncodingUTF8);
92
- NSString *deviceNameStr = name ? [NSString stringWithUTF8String:name] : @"Unknown Device";
67
+ if (dataSize > 0) {
68
+ AudioBufferList *bufferList = (AudioBufferList *)malloc(dataSize);
69
+ AudioObjectGetPropertyData(deviceID, &propertyAddress, 0, NULL, &dataSize, bufferList);
93
70
 
94
- NSDictionary *deviceInfo = @{
95
- @"id": [NSString stringWithFormat:@"%u", deviceID],
96
- @"name": deviceNameStr,
97
- @"manufacturer": @"System",
98
- @"type": @"system_audio",
99
- @"isDefault": @(NO) // We'll determine default separately if needed
100
- };
71
+ if (bufferList->mNumberBuffers > 0) {
72
+ NSDictionary *deviceInfo = @{
73
+ @"id": @(deviceID),
74
+ @"name": (__bridge NSString *)deviceName,
75
+ @"type": @"System Audio Input",
76
+ @"isSystemDevice": @YES
77
+ };
78
+
79
+ [devices addObject:deviceInfo];
80
+ }
101
81
 
102
- [devices addObject:deviceInfo];
82
+ free(bufferList);
103
83
  }
104
84
 
105
85
  CFRelease(deviceName);
@@ -114,59 +94,23 @@
114
94
  }
115
95
 
116
96
  + (BOOL)hasAudioPermission {
117
- // Check microphone permission using AVFoundation
118
- AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
119
- return authStatus == AVAuthorizationStatusAuthorized;
97
+ if (@available(macOS 10.14, *)) {
98
+ AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
99
+ return status == AVAuthorizationStatusAuthorized;
100
+ }
101
+ return YES; // Older versions don't require explicit permission
120
102
  }
121
103
 
122
104
  + (void)requestAudioPermission:(void(^)(BOOL granted))completion {
123
- [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
124
- dispatch_async(dispatch_get_main_queue(), ^{
125
- if (completion) {
105
+ if (@available(macOS 10.14, *)) {
106
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted) {
107
+ dispatch_async(dispatch_get_main_queue(), ^{
126
108
  completion(granted);
127
- }
128
- });
129
- }];
130
- }
131
-
132
- @end
133
-
134
- // ScreenCaptureKit Audio Configuration Helper
135
- API_AVAILABLE(macos(12.3))
136
- @interface SCKAudioConfiguration : NSObject
137
-
138
- + (BOOL)configureAudioForStream:(SCStreamConfiguration *)config
139
- includeMicrophone:(BOOL)includeMicrophone
140
- includeSystemAudio:(BOOL)includeSystemAudio
141
- microphoneDevice:(NSString *)micDeviceID
142
- systemAudioDevice:(NSString *)sysDeviceID;
143
-
144
- @end
145
-
146
- @implementation SCKAudioConfiguration
147
-
148
- + (BOOL)configureAudioForStream:(SCStreamConfiguration *)config
149
- includeMicrophone:(BOOL)includeMicrophone
150
- includeSystemAudio:(BOOL)includeSystemAudio
151
- microphoneDevice:(NSString *)micDeviceID
152
- systemAudioDevice:(NSString *)sysDeviceID {
153
-
154
- // Configure system audio capture (requires macOS 13.0+)
155
- if (@available(macOS 13.0, *)) {
156
- config.capturesAudio = includeSystemAudio;
157
- config.excludesCurrentProcessAudio = YES;
158
-
159
- if (includeSystemAudio) {
160
- // ScreenCaptureKit will capture system audio from the selected content
161
- // Quality settings
162
- config.channelCount = 2; // Stereo
163
- config.sampleRate = 48000; // 48kHz
164
- } else {
165
- config.capturesAudio = NO;
166
- }
109
+ });
110
+ }];
111
+ } else {
112
+ completion(YES);
167
113
  }
168
-
169
- return YES;
170
114
  }
171
115
 
172
- @end
116
+ @end
@@ -168,7 +168,7 @@ void writeToFile(NSDictionary *cursorData) {
168
168
  options:0
169
169
  error:&error];
170
170
  if (jsonData && !error) {
171
- NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
171
+ NSString *jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] autorelease];
172
172
 
173
173
  if (g_isFirstWrite) {
174
174
  // İlk yazma - array başlat
@@ -292,6 +292,7 @@ void cleanupCursorTracking() {
292
292
  }
293
293
 
294
294
  if (g_timerTarget) {
295
+ [g_timerTarget autorelease];
295
296
  g_timerTarget = nil;
296
297
  }
297
298
 
@@ -351,12 +352,12 @@ Napi::Value StartCursorTracking(const Napi::CallbackInfo& info) {
351
352
  @try {
352
353
  // Dosyayı oluştur ve aç
353
354
  g_outputPath = [NSString stringWithUTF8String:outputPath.c_str()];
354
- g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
355
+ g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
355
356
 
356
357
  if (!g_fileHandle) {
357
358
  // Dosya yoksa oluştur
358
359
  [[NSFileManager defaultManager] createFileAtPath:g_outputPath contents:nil attributes:nil];
359
- g_fileHandle = [NSFileHandle fileHandleForWritingAtPath:g_outputPath];
360
+ g_fileHandle = [[NSFileHandle fileHandleForWritingAtPath:g_outputPath] retain];
360
361
  }
361
362
 
362
363
  if (!g_fileHandle) {