@rejourneyco/react-native 1.0.0 → 1.0.2

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.
Files changed (49) hide show
  1. package/README.md +29 -0
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +47 -30
  3. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +25 -1
  4. package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +70 -32
  5. package/android/src/main/java/com/rejourney/core/Constants.kt +4 -4
  6. package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +14 -0
  7. package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +9 -0
  8. package/ios/Capture/RJCaptureEngine.m +72 -34
  9. package/ios/Capture/RJCaptureHeuristics.h +7 -5
  10. package/ios/Capture/RJCaptureHeuristics.m +138 -112
  11. package/ios/Capture/RJVideoEncoder.m +0 -26
  12. package/ios/Core/Rejourney.mm +64 -102
  13. package/ios/Utils/RJPerfTiming.m +0 -5
  14. package/ios/Utils/RJWindowUtils.m +0 -1
  15. package/lib/commonjs/components/Mask.js +1 -6
  16. package/lib/commonjs/index.js +12 -101
  17. package/lib/commonjs/sdk/autoTracking.js +55 -353
  18. package/lib/commonjs/sdk/constants.js +2 -13
  19. package/lib/commonjs/sdk/errorTracking.js +1 -29
  20. package/lib/commonjs/sdk/metricsTracking.js +3 -24
  21. package/lib/commonjs/sdk/navigation.js +3 -42
  22. package/lib/commonjs/sdk/networkInterceptor.js +7 -49
  23. package/lib/commonjs/sdk/utils.js +0 -5
  24. package/lib/module/components/Mask.js +1 -6
  25. package/lib/module/index.js +11 -105
  26. package/lib/module/sdk/autoTracking.js +55 -354
  27. package/lib/module/sdk/constants.js +2 -13
  28. package/lib/module/sdk/errorTracking.js +1 -29
  29. package/lib/module/sdk/index.js +0 -2
  30. package/lib/module/sdk/metricsTracking.js +3 -24
  31. package/lib/module/sdk/navigation.js +3 -42
  32. package/lib/module/sdk/networkInterceptor.js +7 -49
  33. package/lib/module/sdk/utils.js +0 -5
  34. package/lib/typescript/NativeRejourney.d.ts +2 -0
  35. package/lib/typescript/sdk/autoTracking.d.ts +5 -6
  36. package/lib/typescript/types/index.d.ts +0 -1
  37. package/package.json +11 -3
  38. package/src/NativeRejourney.ts +4 -0
  39. package/src/components/Mask.tsx +0 -3
  40. package/src/index.ts +11 -88
  41. package/src/sdk/autoTracking.ts +72 -331
  42. package/src/sdk/constants.ts +13 -13
  43. package/src/sdk/errorTracking.ts +1 -17
  44. package/src/sdk/index.ts +0 -2
  45. package/src/sdk/metricsTracking.ts +5 -33
  46. package/src/sdk/navigation.ts +8 -29
  47. package/src/sdk/networkInterceptor.ts +9 -33
  48. package/src/sdk/utils.ts +0 -5
  49. package/src/types/index.ts +0 -29
@@ -20,7 +20,7 @@ static const NSTimeInterval kRJQuietScrollSeconds = 0.2;
20
20
  static const NSTimeInterval kRJQuietBounceSeconds = 0.2;
21
21
  static const NSTimeInterval kRJQuietRefreshSeconds = 0.22;
22
22
  static const NSTimeInterval kRJQuietMapSeconds = 0.55;
23
- static const NSTimeInterval kRJQuietTransitionSeconds = 0.2;
23
+ static const NSTimeInterval kRJQuietTransitionSeconds = 0.1;
24
24
  static const NSTimeInterval kRJQuietKeyboardSeconds = 0.25;
25
25
  static const NSTimeInterval kRJQuietAnimationSeconds = 0.25;
26
26
 
@@ -204,7 +204,8 @@ typedef struct {
204
204
 
205
205
  - (void)recordTouchEventAtTime:(NSTimeInterval)time {
206
206
  self.lastTouchTime = time;
207
- [self scheduleBonusCaptureAfterDelay:kRJBonusInteractionDelaySeconds now:time];
207
+ [self scheduleBonusCaptureAfterDelay:kRJBonusInteractionDelaySeconds
208
+ now:time];
208
209
  }
209
210
 
210
211
  - (void)recordInteractionEventAtTime:(NSTimeInterval)time {
@@ -221,12 +222,11 @@ typedef struct {
221
222
 
222
223
  - (void)recordNavigationEventAtTime:(NSTimeInterval)time {
223
224
  self.lastTransitionTime = time;
224
- [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds
225
- now:time];
225
+ [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds now:time];
226
226
  }
227
227
 
228
228
  - (void)recordRenderedSignature:(nullable NSString *)signature
229
- atTime:(NSTimeInterval)time {
229
+ atTime:(NSTimeInterval)time {
230
230
  self.lastRenderedSignature = signature.length > 0 ? signature : nil;
231
231
  self.lastRenderedTime = time;
232
232
  if (self.pendingKeyframes > 0) {
@@ -262,8 +262,8 @@ typedef struct {
262
262
  }
263
263
  self.lastObservedSignatureTime = now;
264
264
  self.lastSignatureChurnTime = now;
265
- self.lastObservedSignature = currentSignature.length > 0 ? currentSignature
266
- : nil;
265
+ self.lastObservedSignature =
266
+ currentSignature.length > 0 ? currentSignature : nil;
267
267
  } else if (self.lastSignatureChurnTime > 0 &&
268
268
  (now - self.lastSignatureChurnTime) >
269
269
  kRJSignatureChurnWindowSeconds) {
@@ -296,15 +296,15 @@ typedef struct {
296
296
  }
297
297
 
298
298
  [self updateScrollActiveState:scanResult.scrollActive
299
- refreshActive:scanResult.refreshActive
300
- mapActive:scanResult.mapActive
301
- now:now];
299
+ refreshActive:scanResult.refreshActive
300
+ mapActive:scanResult.mapActive
301
+ now:now];
302
302
 
303
303
  BOOL blockingAnimation = NO;
304
304
  if (scanResult.hasAnyAnimations) {
305
305
  self.lastAnimationAreaRatio = scanResult.animationAreaRatio;
306
- blockingAnimation = (scanResult.animationAreaRatio >=
307
- kRJAnimationSmallAreaAllowed);
306
+ blockingAnimation =
307
+ (scanResult.animationAreaRatio >= kRJAnimationSmallAreaAllowed);
308
308
  } else {
309
309
  self.lastAnimationAreaRatio = 0.0;
310
310
  }
@@ -335,88 +335,113 @@ typedef struct {
335
335
  [self probeAnimatedViewsAtTime:now];
336
336
  }
337
337
 
338
- - (RJCaptureHeuristicsDecision *)decisionForSignature:
339
- (nullable NSString *)signature
340
- now:(NSTimeInterval)now
341
- hasLastFrame:(BOOL)hasLastFrame {
338
+ - (RJCaptureHeuristicsDecision *)
339
+ decisionForSignature:(nullable NSString *)signature
340
+ now:(NSTimeInterval)now
341
+ hasLastFrame:(BOOL)hasLastFrame
342
+ importance:(RJCaptureImportance)importance {
342
343
  RJCaptureHeuristicsDecision *decision =
343
344
  [[RJCaptureHeuristicsDecision alloc] init];
344
345
 
345
346
  NSTimeInterval earliestSafeTime = now;
346
347
  RJCaptureHeuristicsReason blockerReason = RJCaptureHeuristicsReasonRenderNow;
347
348
 
349
+ // Check importance to potentially bypass heuristics
350
+ BOOL isUrgent = (importance == RJCaptureImportanceHigh ||
351
+ importance == RJCaptureImportanceCritical);
352
+
348
353
  // Active touch/gesture input should be avoided to keep input latency smooth.
349
- [self considerBlockerSince:self.lastTouchTime
350
- quietInterval:kRJQuietTouchSeconds
351
- now:now
352
- reason:RJCaptureHeuristicsReasonDeferTouch
353
- earliestTime:&earliestSafeTime
354
- chosenReason:&blockerReason];
354
+ // CRITICAL updates bypass this.
355
+ if (!isUrgent) {
356
+ [self considerBlockerSince:self.lastTouchTime
357
+ quietInterval:kRJQuietTouchSeconds
358
+ now:now
359
+ reason:RJCaptureHeuristicsReasonDeferTouch
360
+ earliestTime:&earliestSafeTime
361
+ chosenReason:&blockerReason];
362
+ }
355
363
 
356
364
  // Scroll motion (dragging or deceleration) is a high-jank period.
357
- [self considerBlockerSince:self.lastScrollTime
358
- quietInterval:kRJQuietScrollSeconds
359
- now:now
360
- reason:RJCaptureHeuristicsReasonDeferScroll
361
- earliestTime:&earliestSafeTime
362
- chosenReason:&blockerReason];
365
+ // Even urgent captures should respect scroll to avoid visible hitching,
366
+ // unless CRITICAL
367
+ if (importance != RJCaptureImportanceCritical) {
368
+ [self considerBlockerSince:self.lastScrollTime
369
+ quietInterval:kRJQuietScrollSeconds
370
+ now:now
371
+ reason:RJCaptureHeuristicsReasonDeferScroll
372
+ earliestTime:&earliestSafeTime
373
+ chosenReason:&blockerReason];
374
+ }
363
375
 
364
376
  // Rubber-band bounce and inset animations are visually sensitive.
365
- [self considerBlockerSince:self.lastBounceTime
366
- quietInterval:kRJQuietBounceSeconds
367
- now:now
368
- reason:RJCaptureHeuristicsReasonDeferBounce
369
- earliestTime:&earliestSafeTime
370
- chosenReason:&blockerReason];
377
+ if (!isUrgent) {
378
+ [self considerBlockerSince:self.lastBounceTime
379
+ quietInterval:kRJQuietBounceSeconds
380
+ now:now
381
+ reason:RJCaptureHeuristicsReasonDeferBounce
382
+ earliestTime:&earliestSafeTime
383
+ chosenReason:&blockerReason];
384
+ }
371
385
 
372
386
  // Pull-to-refresh animations should finish before rendering.
373
- [self considerBlockerSince:self.lastRefreshTime
374
- quietInterval:kRJQuietRefreshSeconds
375
- now:now
376
- reason:RJCaptureHeuristicsReasonDeferRefresh
377
- earliestTime:&earliestSafeTime
378
- chosenReason:&blockerReason];
387
+ if (!isUrgent) {
388
+ [self considerBlockerSince:self.lastRefreshTime
389
+ quietInterval:kRJQuietRefreshSeconds
390
+ now:now
391
+ reason:RJCaptureHeuristicsReasonDeferRefresh
392
+ earliestTime:&earliestSafeTime
393
+ chosenReason:&blockerReason];
394
+ }
379
395
 
380
396
  // Interactive transitions (swipe back, drag-to-dismiss) are hitch-sensitive.
381
- [self considerBlockerSince:self.lastTransitionTime
382
- quietInterval:kRJQuietTransitionSeconds
383
- now:now
384
- reason:RJCaptureHeuristicsReasonDeferTransition
385
- earliestTime:&earliestSafeTime
386
- chosenReason:&blockerReason];
397
+ // KEY FIX: Urgent captures (NAVIGATION) must bypass this!
398
+ if (!isUrgent) {
399
+ [self considerBlockerSince:self.lastTransitionTime
400
+ quietInterval:kRJQuietTransitionSeconds
401
+ now:now
402
+ reason:RJCaptureHeuristicsReasonDeferTransition
403
+ earliestTime:&earliestSafeTime
404
+ chosenReason:&blockerReason];
405
+ }
387
406
 
388
407
  // Keyboard frame animations can stutter; wait for settle.
389
408
  if (self.keyboardAnimating) {
390
409
  self.lastKeyboardTime = now;
391
410
  }
392
- [self considerBlockerSince:self.lastKeyboardTime
393
- quietInterval:kRJQuietKeyboardSeconds
394
- now:now
395
- reason:RJCaptureHeuristicsReasonDeferKeyboard
396
- earliestTime:&earliestSafeTime
397
- chosenReason:&blockerReason];
411
+ if (!isUrgent) {
412
+ [self considerBlockerSince:self.lastKeyboardTime
413
+ quietInterval:kRJQuietKeyboardSeconds
414
+ now:now
415
+ reason:RJCaptureHeuristicsReasonDeferKeyboard
416
+ earliestTime:&earliestSafeTime
417
+ chosenReason:&blockerReason];
418
+ }
398
419
 
399
420
  // Map camera or tile motion is visually obvious; avoid rendering mid-flight.
400
- [self considerBlockerSince:self.lastMapTime
401
- quietInterval:kRJQuietMapSeconds
402
- now:now
403
- reason:RJCaptureHeuristicsReasonDeferMap
404
- earliestTime:&earliestSafeTime
405
- chosenReason:&blockerReason];
421
+ // Maps are special; even CRITICAL captures might want to wait for map settle
422
+ // if possible.
423
+ if (importance != RJCaptureImportanceCritical) {
424
+ [self considerBlockerSince:self.lastMapTime
425
+ quietInterval:kRJQuietMapSeconds
426
+ now:now
427
+ reason:RJCaptureHeuristicsReasonDeferMap
428
+ earliestTime:&earliestSafeTime
429
+ chosenReason:&blockerReason];
406
430
 
407
- if (self.mapSettleUntil > now && self.mapSettleUntil > earliestSafeTime) {
408
- earliestSafeTime = self.mapSettleUntil;
409
- blockerReason = RJCaptureHeuristicsReasonDeferMap;
431
+ if (self.mapSettleUntil > now && self.mapSettleUntil > earliestSafeTime) {
432
+ earliestSafeTime = self.mapSettleUntil;
433
+ blockerReason = RJCaptureHeuristicsReasonDeferMap;
434
+ }
410
435
  }
411
436
 
412
437
  // Large-area animations (Lottie, shimmer, etc.) are very noticeable.
413
- if (self.animationBlocking) {
438
+ if (self.animationBlocking && !isUrgent) {
414
439
  [self considerBlockerSince:self.lastAnimationTime
415
440
  quietInterval:kRJQuietAnimationSeconds
416
441
  now:now
417
- reason:RJCaptureHeuristicsReasonDeferBigAnimation
418
- earliestTime:&earliestSafeTime
419
- chosenReason:&blockerReason];
442
+ reason:RJCaptureHeuristicsReasonDeferBigAnimation
443
+ earliestTime:&earliestSafeTime
444
+ chosenReason:&blockerReason];
420
445
  }
421
446
 
422
447
  if (earliestSafeTime > now) {
@@ -426,26 +451,27 @@ typedef struct {
426
451
  return decision;
427
452
  }
428
453
 
429
- BOOL signatureChanged = (signature.length == 0 ||
430
- ![signature isEqualToString:self.lastRenderedSignature]);
454
+ BOOL signatureChanged =
455
+ (signature.length == 0 ||
456
+ ![signature isEqualToString:self.lastRenderedSignature]);
431
457
  BOOL stale = (self.lastRenderedTime <= 0 ||
432
458
  (now - self.lastRenderedTime) > self.maxStaleSeconds);
433
459
  BOOL bonusDue = (self.bonusCaptureTime > 0 && now >= self.bonusCaptureTime);
434
- BOOL keyframeDue = bonusDue && self.pendingKeyframes > 0 &&
435
- (now - self.lastKeyframeRenderTime) >=
436
- kRJKeyframeSpacingSeconds;
460
+ BOOL keyframeDue =
461
+ bonusDue && self.pendingKeyframes > 0 &&
462
+ (now - self.lastKeyframeRenderTime) >= kRJKeyframeSpacingSeconds;
437
463
  BOOL staleOnly = stale && hasLastFrame && !signatureChanged && !keyframeDue;
438
464
  BOOL suppressStaleRender =
439
465
  staleOnly &&
440
466
  (self.hasVideoSurface || self.hasWebSurface || self.hasCameraSurface);
441
467
 
442
- if (suppressStaleRender) {
468
+ if (suppressStaleRender && !isUrgent) {
443
469
  decision.action = RJCaptureHeuristicsActionReuseLast;
444
470
  decision.reason = RJCaptureHeuristicsReasonReuseSignatureUnchanged;
445
471
  return decision;
446
472
  }
447
473
 
448
- if (!hasLastFrame || signatureChanged || stale || keyframeDue) {
474
+ if (!hasLastFrame || signatureChanged || stale || keyframeDue || isUrgent) {
449
475
  decision.action = RJCaptureHeuristicsActionRenderNow;
450
476
  decision.reason = RJCaptureHeuristicsReasonRenderNow;
451
477
  return decision;
@@ -516,7 +542,8 @@ typedef struct {
516
542
  UIViewController *topVC = [self topViewControllerForWindow:window];
517
543
  if (topVC != self.lastTopVC) {
518
544
  self.lastTransitionTime = now;
519
- [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds now:now];
545
+ [self scheduleBonusCaptureAfterDelay:kRJBonusTransitionDelaySeconds
546
+ now:now];
520
547
  self.lastTopVC = topVC;
521
548
  }
522
549
  id<UIViewControllerTransitionCoordinator> coordinator =
@@ -552,7 +579,7 @@ typedef struct {
552
579
  #pragma mark - Scroll Tracking
553
580
 
554
581
  - (void)updateTrackedScrollViews:(NSArray<NSValue *> *)scrollViewPointers
555
- now:(NSTimeInterval)now {
582
+ now:(NSTimeInterval)now {
556
583
  if (!scrollViewPointers) {
557
584
  return;
558
585
  }
@@ -563,18 +590,18 @@ typedef struct {
563
590
  continue;
564
591
  }
565
592
  [self evaluateScrollView:scrollView
566
- now:now
567
- scrollActive:NULL
568
- refreshActive:NULL];
593
+ now:now
594
+ scrollActive:NULL
595
+ refreshActive:NULL];
569
596
  }
570
-
571
597
  }
572
598
 
573
599
  - (void)probeScrollViewsAtTime:(NSTimeInterval)now {
574
600
  BOOL anyScrollActive = NO;
575
601
  BOOL anyRefreshActive = NO;
576
602
 
577
- NSArray<UIScrollView *> *scrollViews = [[self.scrollSamples keyEnumerator] allObjects];
603
+ NSArray<UIScrollView *> *scrollViews =
604
+ [[self.scrollSamples keyEnumerator] allObjects];
578
605
  if (!scrollViews) {
579
606
  scrollViews = @[];
580
607
  }
@@ -583,21 +610,21 @@ typedef struct {
583
610
  continue;
584
611
  }
585
612
  [self evaluateScrollView:scrollView
586
- now:now
587
- scrollActive:&anyScrollActive
588
- refreshActive:&anyRefreshActive];
613
+ now:now
614
+ scrollActive:&anyScrollActive
615
+ refreshActive:&anyRefreshActive];
589
616
  }
590
617
 
591
618
  [self updateScrollActiveState:anyScrollActive
592
- refreshActive:anyRefreshActive
593
- mapActive:self.mapActive
594
- now:now];
619
+ refreshActive:anyRefreshActive
620
+ mapActive:self.mapActive
621
+ now:now];
595
622
  }
596
623
 
597
624
  - (void)evaluateScrollView:(UIScrollView *)scrollView
598
625
  now:(NSTimeInterval)now
599
- scrollActive:(BOOL *)scrollActive
600
- refreshActive:(BOOL *)refreshActive {
626
+ scrollActive:(BOOL *)scrollActive
627
+ refreshActive:(BOOL *)refreshActive {
601
628
  RJScrollViewSample *sample = [self.scrollSamples objectForKey:scrollView];
602
629
  if (!sample) {
603
630
  sample = [[RJScrollViewSample alloc] init];
@@ -609,8 +636,9 @@ typedef struct {
609
636
 
610
637
  BOOL tracking = scrollView.isTracking || scrollView.isDragging ||
611
638
  scrollView.isDecelerating;
612
- BOOL offsetMoved = (fabs(offset.x - sample.contentOffset.x) > kRJScrollEpsilon ||
613
- fabs(offset.y - sample.contentOffset.y) > kRJScrollEpsilon);
639
+ BOOL offsetMoved =
640
+ (fabs(offset.x - sample.contentOffset.x) > kRJScrollEpsilon ||
641
+ fabs(offset.y - sample.contentOffset.y) > kRJScrollEpsilon);
614
642
  BOOL zoomMoved = fabs(zoomScale - sample.zoomScale) > kRJZoomEpsilon;
615
643
  BOOL isScrolling = tracking || offsetMoved || zoomMoved;
616
644
 
@@ -635,8 +663,8 @@ typedef struct {
635
663
  }
636
664
 
637
665
  BOOL refreshVisible = [self isRefreshActiveForScrollView:scrollView
638
- offset:offset
639
- inset:inset];
666
+ offset:offset
667
+ inset:inset];
640
668
  if (refreshVisible) {
641
669
  // Pull-to-refresh is active or settling.
642
670
  self.lastRefreshTime = now;
@@ -660,8 +688,8 @@ typedef struct {
660
688
  }
661
689
  CGFloat topLimit = -inset.top - kRJScrollEpsilon;
662
690
  CGFloat bottomLimit = scrollView.contentSize.height -
663
- scrollView.bounds.size.height +
664
- inset.bottom + kRJScrollEpsilon;
691
+ scrollView.bounds.size.height + inset.bottom +
692
+ kRJScrollEpsilon;
665
693
 
666
694
  if (offset.y < topLimit || offset.y > bottomLimit) {
667
695
  return YES;
@@ -669,8 +697,8 @@ typedef struct {
669
697
 
670
698
  CGFloat leftLimit = -inset.left - kRJScrollEpsilon;
671
699
  CGFloat rightLimit = scrollView.contentSize.width -
672
- scrollView.bounds.size.width +
673
- inset.right + kRJScrollEpsilon;
700
+ scrollView.bounds.size.width + inset.right +
701
+ kRJScrollEpsilon;
674
702
  if (offset.x < leftLimit || offset.x > rightLimit) {
675
703
  return YES;
676
704
  }
@@ -690,8 +718,8 @@ typedef struct {
690
718
  return YES;
691
719
  }
692
720
 
693
- CGFloat triggerOffset = -scrollView.adjustedContentInset.top -
694
- kRJScrollEpsilon;
721
+ CGFloat triggerOffset =
722
+ -scrollView.adjustedContentInset.top - kRJScrollEpsilon;
695
723
  if (offset.y < triggerOffset) {
696
724
  return YES;
697
725
  }
@@ -709,9 +737,9 @@ typedef struct {
709
737
  }
710
738
 
711
739
  - (void)updateScrollActiveState:(BOOL)scrollActive
712
- refreshActive:(BOOL)refreshActive
713
- mapActive:(BOOL)mapActive
714
- now:(NSTimeInterval)now {
740
+ refreshActive:(BOOL)refreshActive
741
+ mapActive:(BOOL)mapActive
742
+ now:(NSTimeInterval)now {
715
743
  if (self.scrollActive && !scrollActive) {
716
744
  [self scheduleBonusCaptureAfterDelay:kRJBonusScrollDelaySeconds now:now];
717
745
  }
@@ -734,7 +762,7 @@ typedef struct {
734
762
  #pragma mark - Map Tracking
735
763
 
736
764
  - (void)updateTrackedMapViews:(NSArray<NSValue *> *)mapViewPointers
737
- now:(NSTimeInterval)now {
765
+ now:(NSTimeInterval)now {
738
766
  if (!mapViewPointers) {
739
767
  return;
740
768
  }
@@ -763,9 +791,9 @@ typedef struct {
763
791
  self.lastMapTime = now;
764
792
  }
765
793
  [self updateScrollActiveState:self.scrollActive
766
- refreshActive:self.refreshActive
767
- mapActive:anyMapActive || self.mapActive
768
- now:now];
794
+ refreshActive:self.refreshActive
795
+ mapActive:anyMapActive || self.mapActive
796
+ now:now];
769
797
  }
770
798
 
771
799
  - (BOOL)updateMapStateForView:(UIView *)view atTime:(NSTimeInterval)now {
@@ -836,8 +864,7 @@ typedef struct {
836
864
 
837
865
  - (void)probeAnimatedViewsAtTime:(NSTimeInterval)now {
838
866
  if (self.churnBlocking) {
839
- if ((now - self.lastSignatureChurnTime) <
840
- kRJSignatureChurnWindowSeconds) {
867
+ if ((now - self.lastSignatureChurnTime) < kRJSignatureChurnWindowSeconds) {
841
868
  self.lastAnimationTime = now;
842
869
  return;
843
870
  }
@@ -863,15 +890,14 @@ typedef struct {
863
890
  self.lastAnimationTime = now;
864
891
  } else {
865
892
  self.animationBlocking = NO;
866
- [self scheduleBonusCaptureAfterDelay:kRJBonusAnimationDelaySeconds
867
- now:now];
893
+ [self scheduleBonusCaptureAfterDelay:kRJBonusAnimationDelaySeconds now:now];
868
894
  }
869
895
  }
870
896
 
871
897
  #pragma mark - Bonus Capture
872
898
 
873
899
  - (void)scheduleBonusCaptureAfterDelay:(NSTimeInterval)delay
874
- now:(NSTimeInterval)now {
900
+ now:(NSTimeInterval)now {
875
901
  if (self.pendingKeyframes < kRJMaxPendingKeyframes) {
876
902
  self.pendingKeyframes += 1;
877
903
  }
@@ -886,9 +912,9 @@ typedef struct {
886
912
  - (void)considerBlockerSince:(NSTimeInterval)timestamp
887
913
  quietInterval:(NSTimeInterval)quietInterval
888
914
  now:(NSTimeInterval)now
889
- reason:(RJCaptureHeuristicsReason)reason
890
- earliestTime:(NSTimeInterval *)earliestTime
891
- chosenReason:(RJCaptureHeuristicsReason *)chosenReason {
915
+ reason:(RJCaptureHeuristicsReason)reason
916
+ earliestTime:(NSTimeInterval *)earliestTime
917
+ chosenReason:(RJCaptureHeuristicsReason *)chosenReason {
892
918
  if (timestamp <= 0) {
893
919
  return;
894
920
  }
@@ -520,16 +520,7 @@
520
520
  }
521
521
 
522
522
  - (void)cleanup {
523
- // Only cancel the current in-progress segment, don't delete the entire temp
524
- // directory. Completed segments may still be uploading and must not be
525
- // deleted here. The RJSegmentUploader handles file cleanup after successful
526
- // upload.
527
523
  [self cancelSegment];
528
-
529
- // NOTE: Do NOT delete rj_segments directory here!
530
- // Other segments may be in the middle of uploading.
531
- // Old orphaned segments are cleaned up by
532
- // RJSegmentUploader.cleanupOrphanedSegments()
533
524
  }
534
525
 
535
526
  #pragma mark - Private Methods
@@ -561,14 +552,9 @@
561
552
  size_t width = (size_t)self.currentFrameSize.width;
562
553
  size_t height = (size_t)self.currentFrameSize.height;
563
554
 
564
- // CRITICAL FIX: Validate incoming image dimensions match expected size
565
- // During keyboard/rotation transitions, image size may temporarily differ
566
- // from currentFrameSize, causing CGBitmapContextCreate bytesPerRow mismatch
567
555
  size_t imageWidth = CGImageGetWidth(cgImage);
568
556
  size_t imageHeight = CGImageGetHeight(cgImage);
569
557
 
570
- // Allow small variance (1-2 pixels) due to rounding, but reject major
571
- // mismatches
572
558
  if (labs((long)imageWidth - (long)width) > 2 ||
573
559
  labs((long)imageHeight - (long)height) > 2) {
574
560
  RJLogDebug(@"Video encoder: Skipping frame - size mismatch (got %zux%zu, "
@@ -639,9 +625,6 @@
639
625
  colorSpace = CGColorSpaceCreateDeviceRGB();
640
626
  }
641
627
 
642
- // CRITICAL: Validate bytesPerRow is sufficient for the target width
643
- // Error "CGBitmapContextCreate: invalid data bytes/row" occurs when
644
- // bytesPerRow < width * 4 (4 bytes per pixel for BGRA)
645
628
  size_t requiredBytesPerRow = width * 4;
646
629
  if (bytesPerRow < requiredBytesPerRow) {
647
630
  CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
@@ -663,7 +646,6 @@
663
646
  return NULL;
664
647
  }
665
648
 
666
- // Use fastest interpolation for pixel buffer drawing
667
649
  CGContextSetInterpolationQuality(context, kCGInterpolationNone);
668
650
  CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
669
651
  CGContextRelease(context);
@@ -681,14 +663,10 @@
681
663
  }
682
664
 
683
665
  - (void)prewarmPixelBufferPool {
684
- // Pre-warm the VideoToolbox H.264 encoder by creating a minimal AVAssetWriter
685
- // and encoding a single dummy frame. This eliminates the ~1.5s spike on first
686
- // real frame encode by front-loading the hardware encoder initialization.
687
666
  dispatch_async(self.encodingQueue, ^{
688
667
  @autoreleasepool {
689
668
  NSTimeInterval startTime = CACurrentMediaTime();
690
669
 
691
- // Use a small size for fast prewarm (H.264 requires even dimensions)
692
670
  CGSize warmupSize = CGSizeMake(100, 100);
693
671
 
694
672
  // Create temp file for dummy segment
@@ -808,15 +786,12 @@ static dispatch_once_t sPrewarmOnceToken;
808
786
  return;
809
787
  sEncoderPrewarmed = YES;
810
788
 
811
- // Run prewarm on a low-priority background queue
812
789
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
813
790
  @autoreleasepool {
814
791
  NSTimeInterval startTime = CACurrentMediaTime();
815
792
 
816
- // Use a small size for fast prewarm (H.264 requires even dimensions)
817
793
  CGSize warmupSize = CGSizeMake(100, 100);
818
794
 
819
- // Create temp file for dummy segment
820
795
  NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory()];
821
796
  NSURL *warmupURL =
822
797
  [tempDir URLByAppendingPathComponent:@"rj_encoder_prewarm.mp4"];
@@ -873,7 +848,6 @@ static dispatch_once_t sPrewarmOnceToken;
873
848
 
874
849
  [warmupWriter startSessionAtSourceTime:kCMTimeZero];
875
850
 
876
- // Create and encode a single dummy frame to trigger H.264 encoder init
877
851
  CVPixelBufferRef dummyBuffer = NULL;
878
852
  NSDictionary *pixelBufferOpts = @{
879
853
  (id)kCVPixelBufferCGImageCompatibilityKey : @YES,