@rejourneyco/react-native 1.0.2 → 1.0.4

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 (43) hide show
  1. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +38 -363
  2. package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +11 -113
  3. package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +1 -15
  4. package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +1 -61
  5. package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +3 -1
  6. package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +1 -22
  7. package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +14 -27
  8. package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +0 -2
  9. package/android/src/main/java/com/rejourney/network/UploadManager.kt +7 -93
  10. package/android/src/main/java/com/rejourney/network/UploadWorker.kt +5 -41
  11. package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +2 -58
  12. package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +4 -4
  13. package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +36 -7
  14. package/ios/Capture/RJCaptureEngine.m +9 -61
  15. package/ios/Capture/RJViewHierarchyScanner.m +68 -51
  16. package/ios/Core/RJLifecycleManager.m +0 -14
  17. package/ios/Core/Rejourney.mm +24 -37
  18. package/ios/Network/RJDeviceAuthManager.m +0 -2
  19. package/ios/Network/RJUploadManager.h +8 -0
  20. package/ios/Network/RJUploadManager.m +45 -0
  21. package/ios/Privacy/RJPrivacyMask.m +5 -31
  22. package/ios/Rejourney.h +0 -14
  23. package/ios/Touch/RJTouchInterceptor.m +21 -15
  24. package/ios/Utils/RJEventBuffer.m +57 -69
  25. package/ios/Utils/RJWindowUtils.m +87 -86
  26. package/lib/commonjs/index.js +44 -31
  27. package/lib/commonjs/sdk/autoTracking.js +0 -3
  28. package/lib/commonjs/sdk/constants.js +1 -1
  29. package/lib/commonjs/sdk/networkInterceptor.js +0 -11
  30. package/lib/commonjs/sdk/utils.js +73 -14
  31. package/lib/module/index.js +44 -31
  32. package/lib/module/sdk/autoTracking.js +0 -3
  33. package/lib/module/sdk/constants.js +1 -1
  34. package/lib/module/sdk/networkInterceptor.js +0 -11
  35. package/lib/module/sdk/utils.js +73 -14
  36. package/lib/typescript/sdk/constants.d.ts +1 -1
  37. package/lib/typescript/sdk/utils.d.ts +31 -1
  38. package/package.json +16 -4
  39. package/src/index.ts +42 -20
  40. package/src/sdk/autoTracking.ts +0 -2
  41. package/src/sdk/constants.ts +14 -14
  42. package/src/sdk/networkInterceptor.ts +0 -9
  43. package/src/sdk/utils.ts +76 -14
@@ -85,14 +85,13 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
85
85
  var lastScanTime: Long = 0L
86
86
  )
87
87
 
88
- // Configuration
89
88
  var captureScale: Float = Constants.DEFAULT_CAPTURE_SCALE
90
89
  var minFrameInterval: Double = Constants.DEFAULT_MIN_FRAME_INTERVAL
91
90
  var maxFramesPerMinute: Int = Constants.DEFAULT_MAX_FRAMES_PER_MINUTE
92
91
  var framesPerSegment: Int = Constants.DEFAULT_FRAMES_PER_SEGMENT
93
92
  var targetBitrate: Int = Constants.DEFAULT_VIDEO_BITRATE
94
93
  var targetFps: Int = Constants.DEFAULT_VIDEO_FPS
95
- var hierarchyCaptureInterval: Int = 5 // Capture hierarchy every N frames
94
+ var hierarchyCaptureInterval: Int = 5
96
95
  var adaptiveQualityEnabled: Boolean = true
97
96
  var thermalThrottleEnabled: Boolean = true
98
97
  var batteryAwareEnabled: Boolean = true
@@ -121,60 +120,46 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
121
120
  viewScanner?.config?.detectVideoLayers = value
122
121
  }
123
122
 
124
- // Delegate for segment upload coordination
125
123
  var delegate: CaptureEngineDelegate? = null
126
124
 
127
- // Read-only state
128
125
  var currentPerformanceLevel: PerformanceLevel = PerformanceLevel.NORMAL
129
126
  private set
130
127
  val frameCount: Int
131
128
  get() = videoEncoder?.currentFrameCount ?: 0
132
129
 
133
- // FIX: Change this property getter to use explicit function body
134
130
  val isRecording: Boolean
135
131
  get() {
136
132
  return _isRecording && !isShuttingDown.get()
137
133
  }
138
134
 
139
-
140
- // Hierarchy tracking (matching iOS)
141
135
  private var framesSinceHierarchy: Int = 0
142
136
 
143
- // Hierarchy snapshots accumulator
144
137
  private val hierarchySnapshots = mutableListOf<Map<String, Any?>>()
145
138
 
146
- // Video encoder
147
139
  private var videoEncoder: VideoEncoder? = null
148
140
  private val segmentDir: File by lazy {
149
141
  File(context.filesDir, "rejourney/segments").apply { mkdirs() }
150
142
  }
151
143
 
152
- // Motion tracker
153
144
  private val motionTracker = MotionTracker()
154
145
 
155
146
  private val captureHeuristics = CaptureHeuristics()
156
147
 
157
- // Handler for main thread operations
158
148
  private val mainHandler = Handler(Looper.getMainLooper())
159
149
 
160
- // Executor for background image processing
161
150
  private val processingExecutor = Executors.newSingleThreadExecutor()
162
151
 
163
- // Coroutine scope for async operations
164
152
  private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
165
153
 
166
- // Capture timer (1 FPS = every 1000ms, matches iOS RJCaptureEngine defaults)
167
154
  private var captureRunnable: Runnable? = null
168
155
  private val captureIntervalMs: Long
169
156
  get() = (1000L / targetFps).coerceAtLeast(100)
170
157
 
171
- // Optimized bitmap object pool to reduce GC churn
172
158
  private val bitmapPool = ConcurrentLinkedQueue<Bitmap>()
173
- private val MAX_POOL_SIZE = 8 // Increased pool size for better reuse
159
+ private val MAX_POOL_SIZE = 8
174
160
 
175
161
  var onFrameCaptured: (() -> Unit)? = null
176
162
 
177
- // -- STATE PROPERTIES (Restored) --
178
163
  private val isShuttingDown = AtomicBoolean(false)
179
164
  private var _isRecording: Boolean = false
180
165
  private val captureInProgress = AtomicBoolean(false)
@@ -195,7 +180,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
195
180
  private var lastSafeBitmap: Bitmap? = null
196
181
  private var lastCapturedHadBlockedSurface = false
197
182
 
198
- // Throttling & Adaptive Quality State
199
183
  private var framesSinceSessionStart = 0
200
184
  private var framesThisMinute = 0
201
185
  private var minuteStartTime: Long = 0
@@ -204,24 +188,20 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
204
188
  private var cooldownUntil: Long = 0
205
189
  private var didPrewarmViewCaches = false
206
190
 
207
- // Battery Awareness
208
191
  private var cachedBatteryLevel: Float = 1.0f
209
192
  private var lastBatteryCheckTime: Long = 0L
210
193
 
211
- // Device dimensions
212
194
  private var deviceWidth: Int = 0
213
195
  private var deviceHeight: Int = 0
214
196
  private var lastMapPresenceTimeMs: Long = 0L
215
197
 
216
198
  init {
217
- // Pre-warm H.264 encoder in background
218
199
  try {
219
200
  VideoEncoder.prewarmEncoderAsync()
220
201
  } catch (e: Exception) {
221
202
  Logger.warning("[CaptureEngine] Encoder prewarm failed (non-critical): ${e.message}")
222
203
  }
223
204
 
224
- // Pre-warm render server (Optimization 4)
225
205
  mainHandler.post {
226
206
  prewarmRenderServer()
227
207
  }
@@ -275,15 +255,12 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
275
255
  PrivacyMask.maskWebViews = privacyMaskWebViews
276
256
  PrivacyMask.maskVideoLayers = privacyMaskVideoLayers
277
257
 
278
- // Pre-warm privacy scanner class caches (matching iOS prewarmClassCaches)
279
- // This eliminates ~10-15ms of cold-cache class lookups on first frame
280
258
  if (!didPrewarmViewCaches) {
281
259
  viewScanner?.prewarmClassCaches()
282
260
  PrivacyMask.prewarmClassCaches()
283
261
  didPrewarmViewCaches = true
284
262
  }
285
263
 
286
- // Initialize video encoder
287
264
  videoEncoder = VideoEncoder(segmentDir).apply {
288
265
  this.targetBitrate = this@CaptureEngine.targetBitrate
289
266
  this.framesPerSegment = this@CaptureEngine.framesPerSegment
@@ -294,14 +271,11 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
294
271
  this.keyframeInterval = Constants.DEFAULT_KEYFRAME_INTERVAL
295
272
  this.delegate = this@CaptureEngine
296
273
  setSessionId(sessionId)
297
- prewarm() // Pre-warm encoder to eliminate first-frame spike
274
+ prewarm()
298
275
  }
299
276
 
300
- // Clean up any orphaned segments from previous sessions
301
277
  cleanupOldSegments()
302
278
 
303
- // Start the first video segment
304
- // This is required to set isRecording=true in the encoder so frames can be appended
305
279
  mainHandler.post {
306
280
  val window = getCurrentWindow()
307
281
  if (window != null) {
@@ -310,13 +284,11 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
310
284
  videoEncoder?.startSegment(decorView.width, decorView.height)
311
285
  Logger.debug("[CaptureEngine] Started first segment: ${decorView.width}x${decorView.height}")
312
286
  } else {
313
- // Defer segment start until we have valid dimensions
314
287
  Logger.debug("[CaptureEngine] Deferring segment start - waiting for valid dimensions")
315
288
  }
316
289
  }
317
290
  }
318
291
 
319
- // Start capture timer
320
292
  startCaptureTimer()
321
293
 
322
294
  Logger.debug("[CaptureEngine] Session started: $sessionId")
@@ -333,13 +305,10 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
333
305
  _isRecording = false
334
306
  stopCaptureTimer()
335
307
 
336
- // Finish current segment (will trigger delegate callback)
337
308
  videoEncoder?.finishSegment()
338
309
 
339
- // Upload any remaining hierarchy snapshots
340
310
  uploadCurrentHierarchySnapshots()
341
311
 
342
- // OPTIMIZATION: Clean up resources
343
312
  cleanup()
344
313
 
345
314
  Logger.debug("[CaptureEngine] Session stopped")
@@ -349,14 +318,10 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
349
318
  * OPTIMIZATION: Comprehensive cleanup to prevent memory leaks.
350
319
  */
351
320
  private fun cleanup() {
352
- // Clean up video encoder
353
321
  videoEncoder = null
354
322
 
355
- // Clean up bitmap resources
356
- // Clear caches
357
323
  hierarchySnapshots.clear()
358
324
 
359
- // Reset counters
360
325
  framesSinceSessionStart = 0
361
326
  framesSinceHierarchy = 0
362
327
  sessionId = null
@@ -371,7 +336,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
371
336
  captureHeuristics.reset()
372
337
  resetCachedFrames()
373
338
 
374
- // Cancel any pending operations
375
339
  scope.coroutineContext.cancelChildren()
376
340
  }
377
341
 
@@ -393,10 +357,8 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
393
357
 
394
358
  stopCaptureTimer()
395
359
 
396
- // Finish current segment
397
360
  videoEncoder?.finishSegment()
398
361
 
399
- // Upload hierarchy snapshots
400
362
  uploadCurrentHierarchySnapshots()
401
363
  }
402
364
 
@@ -408,9 +370,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
408
370
 
409
371
  Logger.info("[CaptureEngine] Resuming video capture")
410
372
 
411
- // DEFENSIVE FIX: Warmup period
412
- // When returning from background, the view hierarchy and layout may not be stable immediately.
413
- // This is primarily an iOS issue but we apply it here for consistency and safety.
414
373
  isWarmingUp.set(true)
415
374
  Logger.debug("[CaptureEngine] Warmup started (200ms)")
416
375
 
@@ -419,7 +378,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
419
378
  isWarmingUp.set(false)
420
379
  Logger.debug("[CaptureEngine] Warmup complete")
421
380
 
422
- // Trigger immediate capture check
423
381
  if (_isRecording) {
424
382
  requestCapture(CaptureImportance.MEDIUM, "warmup_complete", forceCapture = false)
425
383
  }
@@ -434,7 +392,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
434
392
  idleCapturePending = false
435
393
  idleCaptureGeneration = 0
436
394
 
437
- // Get window and start new segment
438
395
  mainHandler.post {
439
396
  val window = getCurrentWindow()
440
397
  if (window != null && videoEncoder != null) {
@@ -577,27 +534,12 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
577
534
  return videoEncoder?.emergencyFlushSync() ?: false
578
535
  }
579
536
 
580
- // ==================== VideoEncoderDelegate ====================
581
-
582
537
  override fun onSegmentFinished(segmentFile: File, startTime: Long, endTime: Long, frameCount: Int) {
583
538
  Logger.info("[CaptureEngine] Segment ready: ${segmentFile.name} ($frameCount frames, ${(endTime - startTime) / 1000.0}s)")
584
539
 
585
- // Notify delegate of segment completion
586
540
  delegate?.onSegmentReady(segmentFile, startTime, endTime, frameCount)
587
541
 
588
- // Upload accumulated hierarchy snapshots
589
542
  uploadCurrentHierarchySnapshots()
590
-
591
- // NOTE: Do NOT start a new segment here!
592
- // VideoEncoder.finishSegmentAndContinue() already handles starting the next segment
593
- // during automatic rotation (when segment reaches framesPerSegment limit).
594
- // Starting a segment here causes a race condition where we try to finish an
595
- // encoder that was just created, crashing with IllegalStateException in
596
- // signalEndOfInputStream().
597
- //
598
- // New segments are now started by:
599
- // 1. VideoEncoder.finishSegmentAndContinue() during rotation (internal)
600
- // 2. captureFrameInternal() when recording but encoder not started yet
601
543
  }
602
544
 
603
545
  override fun onEncodingError(error: Exception) {
@@ -605,8 +547,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
605
547
  delegate?.onCaptureError(error)
606
548
  }
607
549
 
608
- // ==================== Private Methods ====================
609
-
610
550
  private fun captureFrame(importance: CaptureImportance, reason: String) {
611
551
  requestCapture(importance, reason, forceCapture = false)
612
552
  }
@@ -865,18 +805,15 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
865
805
  return
866
806
  }
867
807
 
868
- // Check cooldown
869
808
  val nowMs = System.currentTimeMillis()
870
809
  if (nowMs < cooldownUntil) {
871
810
  captureInProgress.set(false)
872
811
  return
873
812
  }
874
813
 
875
- // Store device dimensions for reference
876
814
  deviceWidth = decorView.width
877
815
  deviceHeight = decorView.height
878
816
 
879
- // Calculate scaled dimensions for bitmap
880
817
  var effectiveScale = captureScale
881
818
  if (!effectiveScale.isFinite() || effectiveScale <= 0f) {
882
819
  effectiveScale = Constants.DEFAULT_CAPTURE_SCALE
@@ -930,7 +867,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
930
867
  returnBitmap(bitmap)
931
868
  }
932
869
  } else {
933
- // PixelCopy not available pre-O; skip to avoid incorrect GPU capture
934
870
  captureInProgress.set(false)
935
871
  returnBitmap(bitmap)
936
872
  }
@@ -995,24 +931,18 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
995
931
 
996
932
  try {
997
933
  PerfTiming.time(PerfMetric.FRAME) {
998
- // NUCLEAR FIX: Disable frame deduplication
999
- // Previously we would skip frames with matching hashes, but this caused
1000
- // issues with MapView and other GPU-rendered content where the hash might
1001
- // not reflect visual changes. Always process every frame now.
1002
934
  val now = System.currentTimeMillis()
1003
935
 
1004
- // OPTIMIZATION 2: Lazy privacy masking - only apply if sensitive rects exist
1005
936
  val shouldMask = privacyMaskTextInputs || privacyMaskCameraViews ||
1006
937
  privacyMaskWebViews || privacyMaskVideoLayers
1007
938
  val maskedBitmap = if (sensitiveRects.isNotEmpty() && shouldMask) {
1008
939
  PrivacyMask.applyMasksToBitmap(bitmap, sensitiveRects, rootWidth, rootHeight)
1009
940
  } else {
1010
- bitmap // No masking needed - use original bitmap
941
+ bitmap
1011
942
  }
1012
943
 
1013
944
  val timestamp = System.currentTimeMillis()
1014
945
 
1015
- // Append frame to video encoder
1016
946
  val success = encoder.appendFrame(maskedBitmap, timestamp)
1017
947
 
1018
948
  if (success) {
@@ -1028,7 +958,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1028
958
  try {
1029
959
  onFrameCaptured?.invoke()
1030
960
  } catch (_: Exception) {
1031
- // Best-effort only
1032
961
  }
1033
962
  }
1034
963
 
@@ -1144,9 +1073,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1144
1073
  return rects
1145
1074
  }
1146
1075
 
1147
-
1148
- // Legacy bitmap hash - removed
1149
-
1150
1076
  /**
1151
1077
  * OPTIMIZATION 4: Render Server Pre-warming
1152
1078
  * Performs a dummy render to initialize the graphics subsystem/driver
@@ -1157,8 +1083,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1157
1083
  val window = getCurrentWindow() ?: return
1158
1084
  val bitmap = getBitmap(1, 1)
1159
1085
  val canvas = Canvas(bitmap)
1160
- // Draw a tiny part of the view hierarchy to warm up the rendering pipeline
1161
- // This pays the "first draw" cost (~50-100ms) now instead of during first capture
1162
1086
  window.decorView.draw(canvas)
1163
1087
  returnBitmap(bitmap)
1164
1088
  Logger.debug("[CaptureEngine] Render server pre-warmed")
@@ -1226,7 +1150,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1226
1150
  try {
1227
1151
  Logger.debug("[CaptureEngine] uploadCurrentHierarchySnapshots: Converting ${snapshotsToUpload.size} snapshots to JSON")
1228
1152
 
1229
- // Convert to JSON
1230
1153
  val jsonArray = JSONArray()
1231
1154
  for (snapshot in snapshotsToUpload) {
1232
1155
  try {
@@ -1289,7 +1212,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1289
1212
  }
1290
1213
 
1291
1214
  private fun updatePerformanceLevel() {
1292
- // Check thermal state first (matching iOS RJPerformanceManager behavior)
1293
1215
  if (thermalThrottleEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
1294
1216
  val thermalLevel = getThermalStatus()
1295
1217
  when (thermalLevel) {
@@ -1307,17 +1229,12 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1307
1229
  currentPerformanceLevel = PerformanceLevel.REDUCED
1308
1230
  return
1309
1231
  }
1310
- // THERMAL_STATUS_NONE, THERMAL_STATUS_LIGHT - continue to battery check
1311
1232
  }
1312
1233
  }
1313
1234
 
1314
- // OPTIMIZATION: Battery-aware capture rate adjustment
1315
- // Reduces capture rate when battery is low to extend battery life
1316
1235
  currentPerformanceLevel = when {
1317
1236
  isLowBattery() && batteryAwareEnabled -> {
1318
- // Below 15% battery: reduce to minimal capture (0.25 FPS effective)
1319
1237
  if (cachedBatteryLevel < 0.15f) PerformanceLevel.MINIMAL
1320
- // Below 30% battery: reduce capture rate (0.5 FPS effective)
1321
1238
  else if (cachedBatteryLevel < 0.30f) PerformanceLevel.REDUCED
1322
1239
  else PerformanceLevel.NORMAL
1323
1240
  }
@@ -1343,7 +1260,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1343
1260
  }
1344
1261
 
1345
1262
  private fun isLowBattery(): Boolean {
1346
- // Cache battery level for 15 seconds to avoid frequent calls
1347
1263
  val now = System.currentTimeMillis()
1348
1264
  if (now - lastBatteryCheckTime > 15000) {
1349
1265
  lastBatteryCheckTime = now
@@ -1362,23 +1278,19 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1362
1278
  private fun shouldCapture(importance: CaptureImportance): Boolean {
1363
1279
  val now = System.currentTimeMillis()
1364
1280
 
1365
- // Always allow critical captures
1366
1281
  if (importance == CaptureImportance.CRITICAL) {
1367
1282
  return true
1368
1283
  }
1369
1284
 
1370
- // Check minimum interval
1371
1285
  val elapsed = (now - lastCaptureTime) / 1000.0
1372
1286
  if (elapsed < minFrameInterval) {
1373
1287
  return false
1374
1288
  }
1375
1289
 
1376
- // Check frames per minute limit
1377
1290
  if (framesThisMinute >= maxFramesPerMinute) {
1378
1291
  return importance.value >= CaptureImportance.HIGH.value
1379
1292
  }
1380
1293
 
1381
- // Performance level checks
1382
1294
  return when (currentPerformanceLevel) {
1383
1295
  PerformanceLevel.PAUSED -> importance == CaptureImportance.CRITICAL
1384
1296
  PerformanceLevel.MINIMAL -> importance.value >= CaptureImportance.HIGH.value
@@ -1387,8 +1299,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1387
1299
  }
1388
1300
  }
1389
1301
 
1390
- // Legacy methods removed: checkLayoutSignature, isAnyGestureActiveInView, hasSpecialViewsInView
1391
-
1392
1302
  private fun updateFrameRateTracking() {
1393
1303
  val now = System.currentTimeMillis()
1394
1304
  if (now - minuteStartTime >= 60_000) {
@@ -1435,8 +1345,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1435
1345
  return false
1436
1346
  }
1437
1347
 
1438
- // Legacy Detection Logic (Gesture/Special Views) Removed
1439
-
1440
1348
  private fun startCaptureTimer() {
1441
1349
  stopCaptureTimer()
1442
1350
  captureRunnable = object : Runnable {
@@ -1456,20 +1364,15 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1456
1364
  }
1457
1365
 
1458
1366
  private fun getBitmap(width: Int, height: Int): Bitmap {
1459
- // OPTIMIZATION: Improved bitmap pool with size matching
1460
1367
  val pooled = bitmapPool.poll()
1461
1368
  if (pooled != null && !pooled.isRecycled) {
1462
- // Check if pooled bitmap can be reused (same or larger size)
1463
1369
  if (pooled.width >= width && pooled.height >= height) {
1464
- // Create a subset if the pooled bitmap is larger
1465
1370
  if (pooled.width == width && pooled.height == height) {
1466
1371
  return pooled
1467
1372
  } else {
1468
- // Return oversized bitmap to pool and create exact size
1469
1373
  bitmapPool.offer(pooled)
1470
1374
  }
1471
1375
  } else {
1472
- // Pooled bitmap too small, recycle it
1473
1376
  pooled.recycle()
1474
1377
  }
1475
1378
  }
@@ -1477,10 +1380,9 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1477
1380
  return try {
1478
1381
  Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
1479
1382
  } catch (e: OutOfMemoryError) {
1480
- // Clear pool and try again
1481
1383
  clearBitmapPool()
1482
- System.gc() // Suggest garbage collection
1483
- Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) // Fallback to lower quality
1384
+ System.gc()
1385
+ Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
1484
1386
  }
1485
1387
  }
1486
1388
 
@@ -1491,23 +1393,20 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1491
1393
  private fun returnBitmap(bitmap: Bitmap) {
1492
1394
  if (bitmap.isRecycled) return
1493
1395
 
1494
- // OPTIMIZATION: Stricter pool size limits based on memory pressure
1495
1396
  val maxPoolSize = when (currentPerformanceLevel) {
1496
- PerformanceLevel.MINIMAL, PerformanceLevel.PAUSED -> 2 // Minimal pool under pressure
1497
- PerformanceLevel.REDUCED -> 4 // Reduced pool
1498
- PerformanceLevel.NORMAL -> MAX_POOL_SIZE // Full pool
1397
+ PerformanceLevel.MINIMAL, PerformanceLevel.PAUSED -> 2
1398
+ PerformanceLevel.REDUCED -> 4
1399
+ PerformanceLevel.NORMAL -> MAX_POOL_SIZE
1499
1400
  }
1500
1401
 
1501
1402
  if (bitmapPool.size < maxPoolSize) {
1502
- // Only pool bitmaps of reasonable size to avoid memory bloat
1503
1403
  val bitmapBytes = bitmap.byteCount
1504
- if (bitmapBytes < 2 * 1024 * 1024) { // Max 2MB per bitmap in pool
1404
+ if (bitmapBytes < 2 * 1024 * 1024) {
1505
1405
  bitmapPool.offer(bitmap)
1506
1406
  return
1507
1407
  }
1508
1408
  }
1509
1409
 
1510
- // Pool full or bitmap too large - recycle immediately
1511
1410
  bitmap.recycle()
1512
1411
  }
1513
1412
 
@@ -1523,7 +1422,6 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1523
1422
  }
1524
1423
  if (recycledCount > 0) {
1525
1424
  Logger.debug("[CaptureEngine] Recycled $recycledCount pooled bitmaps")
1526
- // Suggest GC after clearing pool to reclaim memory quickly
1527
1425
  System.gc()
1528
1426
  }
1529
1427
  }
@@ -1547,7 +1445,7 @@ class CaptureEngine(private val context: Context) : VideoEncoderDelegate {
1547
1445
  private fun cleanupOldSegments() {
1548
1446
  scope.launch {
1549
1447
  try {
1550
- val cutoffTime = System.currentTimeMillis() - (24 * 60 * 60 * 1000) // 24 hours ago
1448
+ val cutoffTime = System.currentTimeMillis() - (24 * 60 * 60 * 1000)
1551
1449
  segmentDir.listFiles()?.forEach { file ->
1552
1450
  if (file.isFile && file.name.endsWith(".mp4") && file.lastModified() < cutoffTime) {
1553
1451
  file.delete()
@@ -71,7 +71,6 @@ class SegmentUploader(
71
71
  private val pendingUploadCount = AtomicInteger(0)
72
72
  private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
73
73
 
74
- // Use shared client with longer timeouts for video uploads (SSL pinning removed)
75
74
  private val client = HttpClientProvider.shared.newBuilder()
76
75
  .connectTimeout(60, TimeUnit.SECONDS)
77
76
  .readTimeout(120, TimeUnit.SECONDS)
@@ -114,7 +113,6 @@ class SegmentUploader(
114
113
  pendingUploadCount.incrementAndGet()
115
114
 
116
115
  try {
117
- // Step 1: Request presigned URL from backend
118
116
  Logger.debug("[SegmentUploader] Requesting presigned URL for segment")
119
117
  val presignResult = requestPresignedURL(
120
118
  sessionId = sessionId,
@@ -136,7 +134,6 @@ class SegmentUploader(
136
134
 
137
135
  Logger.debug("[SegmentUploader] Got presigned URL, segmentId=$segmentId")
138
136
 
139
- // Step 2: Upload directly to S3
140
137
  Logger.debug("[SegmentUploader] Uploading to S3...")
141
138
  val uploadResult = uploadFileToS3(segmentFile, presignedUrl, "video/mp4")
142
139
 
@@ -147,7 +144,6 @@ class SegmentUploader(
147
144
 
148
145
  Logger.debug("[SegmentUploader] S3 upload SUCCESS, calling segment/complete")
149
146
 
150
- // Step 3: Notify backend of completion
151
147
  val completeResult = notifySegmentComplete(
152
148
  segmentId = segmentId!!,
153
149
  sessionId = sessionId,
@@ -198,10 +194,8 @@ class SegmentUploader(
198
194
  pendingUploadCount.incrementAndGet()
199
195
 
200
196
  try {
201
- // Compress data
202
197
  val compressedData = gzipData(hierarchyData)
203
198
 
204
- // Request presigned URL for hierarchy upload with gzip compression
205
199
  val presignResult = requestPresignedURL(
206
200
  sessionId = sessionId,
207
201
  kind = "hierarchy",
@@ -220,7 +214,6 @@ class SegmentUploader(
220
214
  val presignedUrl = presignResult.presignedUrl
221
215
  val segmentId = presignResult.segmentId
222
216
 
223
- // Upload compressed data to S3
224
217
  val uploadResult = uploadDataToS3(compressedData, presignedUrl, "application/gzip")
225
218
 
226
219
  if (!uploadResult.success) {
@@ -228,7 +221,6 @@ class SegmentUploader(
228
221
  return@withContext SegmentUploadResult(false, uploadResult.error)
229
222
  }
230
223
 
231
- // Notify backend of completion
232
224
  val completeResult = notifySegmentComplete(
233
225
  segmentId = segmentId!!,
234
226
  sessionId = sessionId,
@@ -264,7 +256,7 @@ class SegmentUploader(
264
256
  */
265
257
  fun cleanupOrphanedSegments(segmentDir: File) {
266
258
  try {
267
- val cutoffTime = System.currentTimeMillis() - (24 * 60 * 60 * 1000) // 24 hours ago
259
+ val cutoffTime = System.currentTimeMillis() - (24 * 60 * 60 * 1000)
268
260
  segmentDir.listFiles()?.forEach { file ->
269
261
  if (file.isFile && file.lastModified() < cutoffTime) {
270
262
  file.delete()
@@ -276,7 +268,6 @@ class SegmentUploader(
276
268
  }
277
269
  }
278
270
 
279
- // ==================== Private Methods ====================
280
271
 
281
272
  private data class PresignResult(
282
273
  val success: Boolean,
@@ -358,7 +349,6 @@ class SegmentUploader(
358
349
  attempt: Int = 1
359
350
  ): SegmentUploadResult = withContext(Dispatchers.IO) {
360
351
  try {
361
- // Read file content directly (no gzip)
362
352
  val fileData = file.readBytes()
363
353
 
364
354
  val mediaType = contentType.toMediaType()
@@ -368,7 +358,6 @@ class SegmentUploader(
368
358
  .url(presignedUrl)
369
359
  .put(body)
370
360
  .header("Content-Type", contentType)
371
- // Removed Content-Encoding: gzip
372
361
  .build()
373
362
 
374
363
  val response = client.newCall(request).execute()
@@ -401,7 +390,6 @@ class SegmentUploader(
401
390
  attempt: Int = 1
402
391
  ): SegmentUploadResult = withContext(Dispatchers.IO) {
403
392
  try {
404
- // Upload data directly (no gzip)
405
393
  val mediaType = contentType.toMediaType()
406
394
  val body = data.toRequestBody(mediaType)
407
395
 
@@ -409,7 +397,6 @@ class SegmentUploader(
409
397
  .url(presignedUrl)
410
398
  .put(body)
411
399
  .header("Content-Type", contentType)
412
- // Removed Content-Encoding: gzip
413
400
  .build()
414
401
 
415
402
  val response = client.newCall(request).execute()
@@ -506,7 +493,6 @@ class SegmentUploader(
506
493
  }
507
494
 
508
495
  private fun calculateBackoff(attempt: Int): Long {
509
- // Exponential backoff: 1s, 2s, 4s, etc.
510
496
  return (1000L * (1 shl (attempt - 1))).coerceAtMost(30000L)
511
497
  }
512
498
  }