node-mac-recorder 2.21.26 → 2.21.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-mac-recorder",
3
- "version": "2.21.26",
3
+ "version": "2.21.27",
4
4
  "description": "Native macOS screen recording package for Node.js applications",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -314,7 +314,7 @@ static dispatch_queue_t g_audioCaptureQueue = nil;
314
314
  NSLog(@"❌ Audio writer failed to start: %@", self.writer.error);
315
315
  return;
316
316
  }
317
- [self.writer startSessionAtSourceTime:timestamp];
317
+ [self.writer startSessionAtSourceTime:kCMTimeZero];
318
318
  self.writerStarted = YES;
319
319
  self.startTime = timestamp;
320
320
  }
@@ -323,9 +323,65 @@ static dispatch_queue_t g_audioCaptureQueue = nil;
323
323
  return;
324
324
  }
325
325
 
326
- if (![self.writerInput appendSampleBuffer:sampleBuffer]) {
326
+ if (CMTIME_IS_INVALID(self.startTime)) {
327
+ self.startTime = timestamp;
328
+ }
329
+
330
+ CMSampleBufferRef bufferToAppend = sampleBuffer;
331
+ CMItemCount timingEntryCount = 0;
332
+ OSStatus timingStatus = CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, 0, NULL, &timingEntryCount);
333
+ CMSampleTimingInfo *timingInfo = NULL;
334
+
335
+ if (timingStatus == noErr && timingEntryCount > 0) {
336
+ timingInfo = (CMSampleTimingInfo *)malloc(sizeof(CMSampleTimingInfo) * timingEntryCount);
337
+ if (timingInfo) {
338
+ timingStatus = CMSampleBufferGetSampleTimingInfoArray(sampleBuffer, timingEntryCount, timingInfo, &timingEntryCount);
339
+
340
+ if (timingStatus == noErr) {
341
+ for (CMItemCount i = 0; i < timingEntryCount; ++i) {
342
+ // Shift audio timestamps to begin at t=0 so they align with camera capture
343
+ if (CMTIME_IS_VALID(timingInfo[i].presentationTimeStamp)) {
344
+ CMTime adjustedPTS = CMTimeSubtract(timingInfo[i].presentationTimeStamp, self.startTime);
345
+ if (CMTIME_COMPARE_INLINE(adjustedPTS, <, kCMTimeZero)) {
346
+ adjustedPTS = kCMTimeZero;
347
+ }
348
+ timingInfo[i].presentationTimeStamp = adjustedPTS;
349
+ } else {
350
+ timingInfo[i].presentationTimeStamp = kCMTimeZero;
351
+ }
352
+
353
+ if (CMTIME_IS_VALID(timingInfo[i].decodeTimeStamp)) {
354
+ CMTime adjustedDTS = CMTimeSubtract(timingInfo[i].decodeTimeStamp, self.startTime);
355
+ if (CMTIME_COMPARE_INLINE(adjustedDTS, <, kCMTimeZero)) {
356
+ adjustedDTS = kCMTimeZero;
357
+ }
358
+ timingInfo[i].decodeTimeStamp = adjustedDTS;
359
+ }
360
+ }
361
+
362
+ CMSampleBufferRef adjustedBuffer = NULL;
363
+ timingStatus = CMSampleBufferCreateCopyWithNewTiming(kCFAllocatorDefault,
364
+ sampleBuffer,
365
+ timingEntryCount,
366
+ timingInfo,
367
+ &adjustedBuffer);
368
+ if (timingStatus == noErr && adjustedBuffer) {
369
+ bufferToAppend = adjustedBuffer;
370
+ }
371
+ }
372
+
373
+ free(timingInfo);
374
+ timingInfo = NULL;
375
+ }
376
+ }
377
+
378
+ if (![self.writerInput appendSampleBuffer:bufferToAppend]) {
327
379
  NSLog(@"⚠️ Failed appending audio buffer: %@", self.writer.error);
328
380
  }
381
+
382
+ if (bufferToAppend != sampleBuffer) {
383
+ CFRelease(bufferToAppend);
384
+ }
329
385
  }
330
386
 
331
387
  @end
@@ -837,10 +837,10 @@ static BOOL MRIsContinuityCamera(AVCaptureDevice *device) {
837
837
  if (!self.writerStarted) {
838
838
  if (self.assetWriter.status == AVAssetWriterStatusUnknown) {
839
839
  if ([self.assetWriter startWriting]) {
840
- [self.assetWriter startSessionAtSourceTime:timestamp];
840
+ [self.assetWriter startSessionAtSourceTime:kCMTimeZero];
841
841
  self.writerStarted = YES;
842
842
  self.firstSampleTime = timestamp;
843
- MRLog(@"✅ Camera writer started");
843
+ MRLog(@"✅ Camera writer started (zero-based timeline)");
844
844
  } else {
845
845
  MRLog(@"❌ CameraRecorder: Failed to start asset writer: %@", self.assetWriter.error);
846
846
  self.isRecording = NO;
@@ -862,13 +862,26 @@ static BOOL MRIsContinuityCamera(AVCaptureDevice *device) {
862
862
  return;
863
863
  }
864
864
 
865
+ if (CMTIME_IS_INVALID(self.firstSampleTime)) {
866
+ self.firstSampleTime = timestamp;
867
+ }
868
+
869
+ CMTime relativeTimestamp = timestamp;
870
+ if (CMTIME_IS_VALID(self.firstSampleTime)) {
871
+ // Align camera frames to a zero-based timeline so multi-track compositions stay in sync
872
+ relativeTimestamp = CMTimeSubtract(timestamp, self.firstSampleTime);
873
+ if (CMTIME_COMPARE_INLINE(relativeTimestamp, <, kCMTimeZero)) {
874
+ relativeTimestamp = kCMTimeZero;
875
+ }
876
+ }
877
+
865
878
  CVPixelBufferRetain(pixelBuffer);
866
- BOOL appended = [self.pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:timestamp];
879
+ BOOL appended = [self.pixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:relativeTimestamp];
867
880
  CVPixelBufferRelease(pixelBuffer);
868
881
 
869
882
  if (!appended) {
870
883
  MRLog(@"⚠️ CameraRecorder: Failed to append camera frame at time %.2f (status %ld)",
871
- CMTimeGetSeconds(timestamp), (long)self.assetWriter.status);
884
+ CMTimeGetSeconds(relativeTimestamp), (long)self.assetWriter.status);
872
885
  if (self.assetWriter.status == AVAssetWriterStatusFailed) {
873
886
  MRLog(@"❌ CameraRecorder writer failure: %@", self.assetWriter.error);
874
887
  self.isRecording = NO;