@rejourneyco/react-native 1.0.7 → 1.0.8

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 (29) hide show
  1. package/README.md +1 -1
  2. package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
  3. package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
  4. package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
  5. package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
  6. package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
  7. package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
  8. package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
  9. package/ios/Engine/RejourneyImpl.swift +3 -18
  10. package/ios/Recording/InteractionRecorder.swift +28 -0
  11. package/ios/Recording/ReplayOrchestrator.swift +50 -17
  12. package/ios/Recording/SegmentDispatcher.swift +147 -13
  13. package/ios/Recording/SpecialCases.swift +614 -0
  14. package/ios/Recording/StabilityMonitor.swift +2 -2
  15. package/ios/Recording/TelemetryPipeline.swift +21 -3
  16. package/ios/Recording/VisualCapture.swift +50 -20
  17. package/lib/commonjs/index.js +4 -5
  18. package/lib/commonjs/sdk/constants.js +2 -2
  19. package/lib/commonjs/sdk/utils.js +1 -1
  20. package/lib/module/index.js +4 -5
  21. package/lib/module/sdk/constants.js +2 -2
  22. package/lib/module/sdk/utils.js +1 -1
  23. package/lib/typescript/sdk/constants.d.ts +2 -2
  24. package/lib/typescript/types/index.d.ts +1 -6
  25. package/package.json +2 -2
  26. package/src/index.ts +9 -10
  27. package/src/sdk/constants.ts +2 -2
  28. package/src/sdk/utils.ts +1 -1
  29. package/src/types/index.ts +1 -6
@@ -25,8 +25,10 @@ public final class VisualCapture: NSObject {
25
25
 
26
26
  @objc public static let shared = VisualCapture()
27
27
 
28
- @objc public var snapshotInterval: Double = 0.5
28
+ @objc public var snapshotInterval: Double = 1.0
29
29
  @objc public var quality: CGFloat = 0.5
30
+ /// Capture scale (e.g. 1.25 = capture at 80% linear size). Matches Android for parity; reduces JPEG size.
31
+ @objc public var captureScale: CGFloat = 1.25
30
32
 
31
33
  @objc public var isCapturing: Bool {
32
34
  _stateMachine.currentState == .capturing
@@ -58,6 +60,7 @@ public final class VisualCapture: NSObject {
58
60
 
59
61
  // Industry standard batch size (20 frames per batch, not 5)
60
62
  private let _batchSize = 20
63
+
61
64
 
62
65
  private override init() {
63
66
  _redactionMask = RedactionMask()
@@ -135,6 +138,13 @@ public final class VisualCapture: NSObject {
135
138
  _flushBufferToDisk()
136
139
  }
137
140
 
141
+ /// Submit any buffered frames to the upload pipeline immediately
142
+ /// (regardless of batch size threshold). Packages synchronously to
143
+ /// avoid race conditions during backgrounding.
144
+ @objc public func flushBufferToNetwork() {
145
+ _flushBuffer()
146
+ }
147
+
138
148
  @objc public func activateDeferredMode() {
139
149
  _deferredUntilCommit = true
140
150
  }
@@ -152,9 +162,10 @@ public final class VisualCapture: NSObject {
152
162
  _redactionMask.remove(view)
153
163
  }
154
164
 
155
- @objc public func configure(snapshotInterval: Double, jpegQuality: Double) {
165
+ @objc public func configure(snapshotInterval: Double, jpegQuality: Double, captureScale: CGFloat = 1.25) {
156
166
  self.snapshotInterval = snapshotInterval
157
167
  self.quality = CGFloat(jpegQuality)
168
+ self.captureScale = max(1.0, captureScale)
158
169
  if _stateMachine.currentState == .capturing {
159
170
  _stopCaptureTimer()
160
171
  _startCaptureTimer()
@@ -163,15 +174,12 @@ public final class VisualCapture: NSObject {
163
174
 
164
175
  @objc public func snapshotNow() {
165
176
  DispatchQueue.main.async { [weak self] in
166
- self?._captureFrame()
177
+ self?._captureFrame(forced: true)
167
178
  }
168
179
  }
169
180
 
170
181
  private func _startCaptureTimer() {
171
182
  _stopCaptureTimer()
172
- // Industry standard: Use default run loop mode (NOT .common)
173
- // This lets the timer pause during scrolling/tracking which prevents stutter
174
- // The capture will resume when scrolling stops
175
183
  _captureTimer = Timer.scheduledTimer(withTimeInterval: snapshotInterval, repeats: true) { [weak self] _ in
176
184
  self?._captureFrame()
177
185
  }
@@ -182,7 +190,7 @@ public final class VisualCapture: NSObject {
182
190
  _captureTimer = nil
183
191
  }
184
192
 
185
- private func _captureFrame() {
193
+ private func _captureFrame(forced: Bool = false) {
186
194
  guard _stateMachine.currentState == .capturing else { return }
187
195
 
188
196
  // Skip capture if app is not in foreground (prevents "not in visible window" warnings)
@@ -190,10 +198,31 @@ public final class VisualCapture: NSObject {
190
198
 
191
199
  let frameStart = CFAbsoluteTimeGetCurrent()
192
200
 
201
+ // Refresh map detection state (very cheap shallow walk)
202
+ SpecialCases.shared.refreshMapState()
203
+
204
+ // Debug-only: confirm capture is running and map state
205
+ if _frameCounter < 5 || _frameCounter % 30 == 0 {
206
+ DiagnosticLog.trace("[VisualCapture] frame#\(_frameCounter) mapVisible=\(SpecialCases.shared.mapVisible) mapIdle=\(SpecialCases.shared.mapIdle) forced=\(forced)")
207
+ }
208
+
209
+ // Map stutter prevention: when a map view is visible and its camera
210
+ // is still moving (user gesture or animation), skip drawHierarchy
211
+ // entirely — this is the call that causes GPU readback stutter on
212
+ // Metal/OpenGL-backed map tiles. We resume capture at 1 FPS once
213
+ // the map SDK reports idle.
214
+ if !forced && SpecialCases.shared.mapVisible && !SpecialCases.shared.mapIdle {
215
+ DiagnosticLog.trace("[VisualCapture] SKIPPING frame (map moving)")
216
+ return
217
+ }
218
+
193
219
  // Capture the pixel buffer on the main thread (required by UIKit),
194
220
  // then move JPEG compression to the encode queue to reduce main-thread blocking.
195
221
  autoreleasepool {
196
- guard let window = UIApplication.shared.windows.first(where: \.isKeyWindow) else { return }
222
+ guard let window = UIApplication.shared.connectedScenes
223
+ .compactMap({ $0 as? UIWindowScene })
224
+ .flatMap({ $0.windows })
225
+ .first(where: { $0.isKeyWindow }) else { return }
197
226
  let bounds = window.bounds
198
227
  // Guard against NaN and invalid bounds that cause CoreGraphics errors
199
228
  guard bounds.width > 0, bounds.height > 0 else { return }
@@ -201,15 +230,18 @@ public final class VisualCapture: NSObject {
201
230
  guard bounds.width.isFinite && bounds.height.isFinite else { return }
202
231
 
203
232
  let redactRects = _redactionMask.computeRects()
233
+ let scale = max(1.0, captureScale)
234
+ let scaledSize = CGSize(width: bounds.width / scale, height: bounds.height / scale)
235
+ guard scaledSize.width >= 1, scaledSize.height >= 1 else {
236
+ return
237
+ }
204
238
 
205
- // Use UIGraphicsBeginImageContextWithOptions for lower overhead (industry pattern)
206
- let screenScale: CGFloat = 1.25 // Lower scale reduces encoding time significantly
207
- UIGraphicsBeginImageContextWithOptions(bounds.size, false, screenScale)
239
+ UIGraphicsBeginImageContextWithOptions(scaledSize, false, 1.0)
208
240
  guard let context = UIGraphicsGetCurrentContext() else {
209
241
  UIGraphicsEndImageContext()
210
242
  return
211
243
  }
212
-
244
+ context.scaleBy(x: 1.0 / scale, y: 1.0 / scale)
213
245
  window.drawHierarchy(in: bounds, afterScreenUpdates: false)
214
246
 
215
247
  // Apply redactions inline while context is open
@@ -263,6 +295,8 @@ public final class VisualCapture: NSObject {
263
295
  }
264
296
  }
265
297
 
298
+
299
+
266
300
  /// Enforce memory caps to prevent unbounded growth (industry standard backpressure)
267
301
  private func _enforceScreenshotCaps() {
268
302
  // Called with lock held
@@ -594,14 +628,10 @@ private final class RedactionMask {
594
628
  }
595
629
 
596
630
  private func _keyWindow() -> UIWindow? {
597
- if #available(iOS 15.0, *) {
598
- return UIApplication.shared.connectedScenes
599
- .compactMap { $0 as? UIWindowScene }
600
- .flatMap { $0.windows }
601
- .first { $0.isKeyWindow }
602
- } else {
603
- return UIApplication.shared.windows.first { $0.isKeyWindow }
604
- }
631
+ return UIApplication.shared.connectedScenes
632
+ .compactMap { $0 as? UIWindowScene }
633
+ .flatMap { $0.windows }
634
+ .first { $0.isKeyWindow }
605
635
  }
606
636
 
607
637
  private func _scanForSensitiveViews(in view: UIView, rects: inout [CGRect], depth: Int = 0) {
@@ -322,7 +322,7 @@ let _storedConfig = null;
322
322
  // Abort recording (fail-closed)
323
323
 
324
324
  let _remoteConfig = null;
325
- let _sessionSampledOut = false; // True = telemetry only, no replay video
325
+ let _sessionSampledOut = false; // True = telemetry only, no visual replay capture
326
326
 
327
327
  /**
328
328
  * Fetch project configuration from backend
@@ -618,7 +618,7 @@ const Rejourney = {
618
618
  // =========================================================
619
619
  _sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
620
620
  if (_sessionSampledOut) {
621
- getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay video`);
621
+ getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
622
622
  }
623
623
 
624
624
  // =========================================================
@@ -865,7 +865,6 @@ const Rejourney = {
865
865
  pixelRatio: 1
866
866
  },
867
867
  eventCount: 0,
868
- videoSegmentCount: 0,
869
868
  storageSize: 0,
870
869
  sdkVersion: _constants.SDK_VERSION,
871
870
  isComplete: false
@@ -941,10 +940,10 @@ const Rejourney = {
941
940
  }, false);
942
941
  },
943
942
  /**
944
- * Report a scroll event for video capture timing
943
+ * Report a scroll event for visual replay timing
945
944
  *
946
945
  * Call this from your ScrollView's onScroll handler to improve scroll capture.
947
- * The SDK captures video at 2 FPS continuously, but this helps log scroll events
946
+ * The SDK captures visual replay frames continuously, and this helps log scroll events
948
947
  * for timeline correlation during replay.
949
948
  *
950
949
  * @param scrollOffset - Current scroll offset (vertical or horizontal)
@@ -37,7 +37,7 @@ var _version = require("./version");
37
37
  /** Default configuration values */
38
38
  const DEFAULT_CONFIG = exports.DEFAULT_CONFIG = {
39
39
  enabled: true,
40
- captureFPS: 0.5,
40
+ captureFPS: 1.0,
41
41
  captureOnEvents: true,
42
42
  maxSessionDuration: 10 * 60 * 1000,
43
43
  maxStorageSize: 50 * 1024 * 1024,
@@ -90,7 +90,7 @@ const PLAYBACK_SPEEDS = exports.PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
90
90
 
91
91
  /** Capture settings */
92
92
  const CAPTURE_SETTINGS = exports.CAPTURE_SETTINGS = {
93
- DEFAULT_FPS: 0.5,
93
+ DEFAULT_FPS: 1.0,
94
94
  MIN_FPS: 0.1,
95
95
  MAX_FPS: 2,
96
96
  CAPTURE_SCALE: 0.25,
@@ -322,7 +322,7 @@ class Logger {
322
322
  }
323
323
  }
324
324
  notice(...args) {
325
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
325
+ if (this.minimumLogLevel <= LogLevel.INFO) {
326
326
  console.info(this.prefix, ...args);
327
327
  }
328
328
  }
@@ -214,7 +214,7 @@ let _storedConfig = null;
214
214
  // Abort recording (fail-closed)
215
215
 
216
216
  let _remoteConfig = null;
217
- let _sessionSampledOut = false; // True = telemetry only, no replay video
217
+ let _sessionSampledOut = false; // True = telemetry only, no visual replay capture
218
218
 
219
219
  /**
220
220
  * Fetch project configuration from backend
@@ -510,7 +510,7 @@ const Rejourney = {
510
510
  // =========================================================
511
511
  _sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
512
512
  if (_sessionSampledOut) {
513
- getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay video`);
513
+ getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
514
514
  }
515
515
 
516
516
  // =========================================================
@@ -757,7 +757,6 @@ const Rejourney = {
757
757
  pixelRatio: 1
758
758
  },
759
759
  eventCount: 0,
760
- videoSegmentCount: 0,
761
760
  storageSize: 0,
762
761
  sdkVersion: SDK_VERSION,
763
762
  isComplete: false
@@ -833,10 +832,10 @@ const Rejourney = {
833
832
  }, false);
834
833
  },
835
834
  /**
836
- * Report a scroll event for video capture timing
835
+ * Report a scroll event for visual replay timing
837
836
  *
838
837
  * Call this from your ScrollView's onScroll handler to improve scroll capture.
839
- * The SDK captures video at 2 FPS continuously, but this helps log scroll events
838
+ * The SDK captures visual replay frames continuously, and this helps log scroll events
840
839
  * for timeline correlation during replay.
841
840
  *
842
841
  * @param scrollOffset - Current scroll offset (vertical or horizontal)
@@ -25,7 +25,7 @@ export { SDK_VERSION };
25
25
  /** Default configuration values */
26
26
  export const DEFAULT_CONFIG = {
27
27
  enabled: true,
28
- captureFPS: 0.5,
28
+ captureFPS: 1.0,
29
29
  captureOnEvents: true,
30
30
  maxSessionDuration: 10 * 60 * 1000,
31
31
  maxStorageSize: 50 * 1024 * 1024,
@@ -78,7 +78,7 @@ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4];
78
78
 
79
79
  /** Capture settings */
80
80
  export const CAPTURE_SETTINGS = {
81
- DEFAULT_FPS: 0.5,
81
+ DEFAULT_FPS: 1.0,
82
82
  MIN_FPS: 0.1,
83
83
  MAX_FPS: 2,
84
84
  CAPTURE_SCALE: 0.25,
@@ -301,7 +301,7 @@ class Logger {
301
301
  }
302
302
  }
303
303
  notice(...args) {
304
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
304
+ if (this.minimumLogLevel <= LogLevel.INFO) {
305
305
  console.info(this.prefix, ...args);
306
306
  }
307
307
  }
@@ -21,7 +21,7 @@ export { SDK_VERSION };
21
21
  /** Default configuration values */
22
22
  export declare const DEFAULT_CONFIG: {
23
23
  readonly enabled: true;
24
- readonly captureFPS: 0.5;
24
+ readonly captureFPS: 1;
25
25
  readonly captureOnEvents: true;
26
26
  readonly maxSessionDuration: number;
27
27
  readonly maxStorageSize: number;
@@ -70,7 +70,7 @@ export declare const GESTURE_TYPES: {
70
70
  export declare const PLAYBACK_SPEEDS: readonly [0.5, 1, 2, 4];
71
71
  /** Capture settings */
72
72
  export declare const CAPTURE_SETTINGS: {
73
- readonly DEFAULT_FPS: 0.5;
73
+ readonly DEFAULT_FPS: 1;
74
74
  readonly MIN_FPS: 0.1;
75
75
  readonly MAX_FPS: 2;
76
76
  readonly CAPTURE_SCALE: 0.25;
@@ -17,7 +17,7 @@ export interface RejourneyConfig {
17
17
  packageName?: string;
18
18
  /** Enable or disable recording (default: true) */
19
19
  enabled?: boolean;
20
- /** Video capture FPS (default: 2 = capture every 500ms) */
20
+ /** Visual capture FPS (default: 1 = capture every 1000ms) */
21
21
  captureFPS?: number;
22
22
  /** Maximum session duration in milliseconds (default: 30 minutes) */
23
23
  maxSessionDuration?: number;
@@ -325,8 +325,6 @@ export interface SessionMetadata {
325
325
  geoLocation?: GeoLocation;
326
326
  /** Number of events in session */
327
327
  eventCount: number;
328
- /** Number of video segments */
329
- videoSegmentCount?: number;
330
328
  /** Total storage size in bytes */
331
329
  storageSize: number;
332
330
  /** Session tags */
@@ -349,7 +347,6 @@ export interface SessionSummary {
349
347
  endTime?: number;
350
348
  duration?: number;
351
349
  eventCount: number;
352
- videoSegmentCount?: number;
353
350
  storageSize: number;
354
351
  isComplete: boolean;
355
352
  filePath: string;
@@ -363,8 +360,6 @@ export interface ReplayState {
363
360
  speed: 0.5 | 1 | 2 | 4;
364
361
  /** Session duration */
365
362
  duration: number;
366
- /** Current video segment time position */
367
- currentVideoTime?: number;
368
363
  /** Events at or near current time */
369
364
  activeEvents: SessionEvent[];
370
365
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rejourneyco/react-native",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Rejourney Session Recording SDK for React Native",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -84,7 +84,7 @@
84
84
  "react-native": "*",
85
85
  "react-native-builder-bob": "^0.23.0",
86
86
  "typescript": "^5.0.0",
87
- "vitest": "^2.1.0"
87
+ "vitest": "^3.2.4"
88
88
  },
89
89
  "peerDependencies": {
90
90
  "react": "*",
package/src/index.ts CHANGED
@@ -247,13 +247,13 @@ interface RemoteConfig {
247
247
  }
248
248
 
249
249
  // Result type for fetchRemoteConfig - distinguishes network errors from access denial
250
- type ConfigFetchResult =
250
+ type ConfigFetchResult =
251
251
  | { status: 'success'; config: RemoteConfig }
252
252
  | { status: 'network_error' } // Proceed with defaults (fail-open)
253
253
  | { status: 'access_denied'; httpStatus: number }; // Abort recording (fail-closed)
254
254
 
255
255
  let _remoteConfig: RemoteConfig | null = null;
256
- let _sessionSampledOut: boolean = false; // True = telemetry only, no replay video
256
+ let _sessionSampledOut: boolean = false; // True = telemetry only, no visual replay capture
257
257
 
258
258
  /**
259
259
  * Fetch project configuration from backend
@@ -323,7 +323,7 @@ function shouldRecordSession(sampleRate: number): boolean {
323
323
  // sampleRate is 0-100 (percentage)
324
324
  if (sampleRate >= 100) return true;
325
325
  if (sampleRate <= 0) return false;
326
-
326
+
327
327
  const randomValue = Math.random() * 100;
328
328
  return randomValue < sampleRate;
329
329
  }
@@ -522,7 +522,7 @@ const Rejourney: RejourneyAPI = {
522
522
  // This determines if recording is enabled and at what rate
523
523
  // =========================================================
524
524
  const configResult = await fetchRemoteConfig(apiUrl, publicKey);
525
-
525
+
526
526
  // =========================================================
527
527
  // CASE 0: Access denied (401/403) - abort immediately
528
528
  // This means project disabled, invalid key, etc - HARD STOP
@@ -531,10 +531,10 @@ const Rejourney: RejourneyAPI = {
531
531
  getLogger().info(`Recording disabled - access denied (${configResult.httpStatus})`);
532
532
  return false;
533
533
  }
534
-
534
+
535
535
  // For success, extract the config; for network_error, proceed with null
536
536
  _remoteConfig = configResult.status === 'success' ? configResult.config : null;
537
-
537
+
538
538
  if (_remoteConfig) {
539
539
  // =========================================================
540
540
  // CASE 1: Rejourney completely disabled - abort early, nothing captured
@@ -560,7 +560,7 @@ const Rejourney: RejourneyAPI = {
560
560
  // =========================================================
561
561
  _sessionSampledOut = !shouldRecordSession(_remoteConfig.sampleRate ?? 100);
562
562
  if (_sessionSampledOut) {
563
- getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no replay video`);
563
+ getLogger().info(`Session sampled out (rate: ${_remoteConfig.sampleRate}%) - telemetry only, no visual replay capture`);
564
564
  }
565
565
 
566
566
  // =========================================================
@@ -868,7 +868,6 @@ const Rejourney: RejourneyAPI = {
868
868
  duration: 0,
869
869
  deviceInfo: { model: '', os: 'ios', osVersion: '', screenWidth: 0, screenHeight: 0, pixelRatio: 1 },
870
870
  eventCount: 0,
871
- videoSegmentCount: 0,
872
871
  storageSize: 0,
873
872
  sdkVersion: SDK_VERSION,
874
873
  isComplete: false,
@@ -957,10 +956,10 @@ const Rejourney: RejourneyAPI = {
957
956
  },
958
957
 
959
958
  /**
960
- * Report a scroll event for video capture timing
959
+ * Report a scroll event for visual replay timing
961
960
  *
962
961
  * Call this from your ScrollView's onScroll handler to improve scroll capture.
963
- * The SDK captures video at 2 FPS continuously, but this helps log scroll events
962
+ * The SDK captures visual replay frames continuously, and this helps log scroll events
964
963
  * for timeline correlation during replay.
965
964
  *
966
965
  * @param scrollOffset - Current scroll offset (vertical or horizontal)
@@ -26,7 +26,7 @@ export { SDK_VERSION };
26
26
  /** Default configuration values */
27
27
  export const DEFAULT_CONFIG = {
28
28
  enabled: true,
29
- captureFPS: 0.5,
29
+ captureFPS: 1.0,
30
30
  captureOnEvents: true,
31
31
  maxSessionDuration: 10 * 60 * 1000,
32
32
  maxStorageSize: 50 * 1024 * 1024,
@@ -79,7 +79,7 @@ export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4] as const;
79
79
 
80
80
  /** Capture settings */
81
81
  export const CAPTURE_SETTINGS = {
82
- DEFAULT_FPS: 0.5,
82
+ DEFAULT_FPS: 1.0,
83
83
  MIN_FPS: 0.1,
84
84
  MAX_FPS: 2,
85
85
  CAPTURE_SCALE: 0.25,
package/src/sdk/utils.ts CHANGED
@@ -324,7 +324,7 @@ class Logger {
324
324
  }
325
325
 
326
326
  notice(...args: any[]): void {
327
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
327
+ if (this.minimumLogLevel <= LogLevel.INFO) {
328
328
  console.info(this.prefix, ...args);
329
329
  }
330
330
  }
@@ -23,7 +23,7 @@ export interface RejourneyConfig {
23
23
 
24
24
  /** Enable or disable recording (default: true) */
25
25
  enabled?: boolean;
26
- /** Video capture FPS (default: 2 = capture every 500ms) */
26
+ /** Visual capture FPS (default: 1 = capture every 1000ms) */
27
27
  captureFPS?: number;
28
28
  /** Maximum session duration in milliseconds (default: 30 minutes) */
29
29
  maxSessionDuration?: number;
@@ -410,8 +410,6 @@ export interface SessionMetadata {
410
410
  geoLocation?: GeoLocation;
411
411
  /** Number of events in session */
412
412
  eventCount: number;
413
- /** Number of video segments */
414
- videoSegmentCount?: number;
415
413
  /** Total storage size in bytes */
416
414
  storageSize: number;
417
415
  /** Session tags */
@@ -436,7 +434,6 @@ export interface SessionSummary {
436
434
  endTime?: number;
437
435
  duration?: number;
438
436
  eventCount: number;
439
- videoSegmentCount?: number;
440
437
  storageSize: number;
441
438
  isComplete: boolean;
442
439
  filePath: string;
@@ -451,8 +448,6 @@ export interface ReplayState {
451
448
  speed: 0.5 | 1 | 2 | 4;
452
449
  /** Session duration */
453
450
  duration: number;
454
- /** Current video segment time position */
455
- currentVideoTime?: number;
456
451
  /** Events at or near current time */
457
452
  activeEvents: SessionEvent[];
458
453
  }