@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.
- package/README.md +1 -1
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +20 -18
- package/android/src/main/java/com/rejourney/recording/InteractionRecorder.kt +28 -0
- package/android/src/main/java/com/rejourney/recording/ReplayOrchestrator.kt +42 -33
- package/android/src/main/java/com/rejourney/recording/SegmentDispatcher.kt +242 -34
- package/android/src/main/java/com/rejourney/recording/SpecialCases.kt +572 -0
- package/android/src/main/java/com/rejourney/recording/TelemetryPipeline.kt +6 -4
- package/android/src/main/java/com/rejourney/recording/VisualCapture.kt +156 -64
- package/ios/Engine/RejourneyImpl.swift +3 -18
- package/ios/Recording/InteractionRecorder.swift +28 -0
- package/ios/Recording/ReplayOrchestrator.swift +50 -17
- package/ios/Recording/SegmentDispatcher.swift +147 -13
- package/ios/Recording/SpecialCases.swift +614 -0
- package/ios/Recording/StabilityMonitor.swift +2 -2
- package/ios/Recording/TelemetryPipeline.swift +21 -3
- package/ios/Recording/VisualCapture.swift +50 -20
- package/lib/commonjs/index.js +4 -5
- package/lib/commonjs/sdk/constants.js +2 -2
- package/lib/commonjs/sdk/utils.js +1 -1
- package/lib/module/index.js +4 -5
- package/lib/module/sdk/constants.js +2 -2
- package/lib/module/sdk/utils.js +1 -1
- package/lib/typescript/sdk/constants.d.ts +2 -2
- package/lib/typescript/types/index.d.ts +1 -6
- package/package.json +2 -2
- package/src/index.ts +9 -10
- package/src/sdk/constants.ts +2 -2
- package/src/sdk/utils.ts +1 -1
- package/src/types/index.ts +1 -6
package/README.md
CHANGED
|
@@ -709,26 +709,28 @@ class RejourneyModuleImpl(
|
|
|
709
709
|
fun getSDKMetrics(promise: Promise) {
|
|
710
710
|
val dispatcher = SegmentDispatcher.shared
|
|
711
711
|
val pipeline = TelemetryPipeline.shared
|
|
712
|
+
val telemetry = dispatcher.sdkTelemetrySnapshot(pipeline?.getQueueDepth() ?: 0)
|
|
713
|
+
|
|
714
|
+
fun toIntValue(key: String): Int = (telemetry[key] as? Number)?.toInt() ?: 0
|
|
715
|
+
fun toDoubleValue(key: String, fallback: Double = 0.0): Double = (telemetry[key] as? Number)?.toDouble() ?: fallback
|
|
716
|
+
fun toLongValue(key: String): Long? = (telemetry[key] as? Number)?.toLong()
|
|
712
717
|
|
|
713
718
|
promise.resolve(Arguments.createMap().apply {
|
|
714
|
-
putInt("uploadSuccessCount",
|
|
715
|
-
putInt("uploadFailureCount",
|
|
716
|
-
putInt("retryAttemptCount",
|
|
717
|
-
putInt("circuitBreakerOpenCount",
|
|
718
|
-
putInt("memoryEvictionCount",
|
|
719
|
-
putInt("offlinePersistCount",
|
|
720
|
-
putInt("sessionStartCount",
|
|
721
|
-
putInt("crashCount",
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
putDouble("
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
putNull("lastRetryTime")
|
|
730
|
-
putDouble("totalBytesUploaded", dispatcher.totalBytesUploaded.toDouble())
|
|
731
|
-
putInt("totalBytesEvicted", 0)
|
|
719
|
+
putInt("uploadSuccessCount", toIntValue("uploadSuccessCount"))
|
|
720
|
+
putInt("uploadFailureCount", toIntValue("uploadFailureCount"))
|
|
721
|
+
putInt("retryAttemptCount", toIntValue("retryAttemptCount"))
|
|
722
|
+
putInt("circuitBreakerOpenCount", toIntValue("circuitBreakerOpenCount"))
|
|
723
|
+
putInt("memoryEvictionCount", toIntValue("memoryEvictionCount"))
|
|
724
|
+
putInt("offlinePersistCount", toIntValue("offlinePersistCount"))
|
|
725
|
+
putInt("sessionStartCount", toIntValue("sessionStartCount"))
|
|
726
|
+
putInt("crashCount", toIntValue("crashCount"))
|
|
727
|
+
putDouble("uploadSuccessRate", toDoubleValue("uploadSuccessRate", 1.0))
|
|
728
|
+
putDouble("avgUploadDurationMs", toDoubleValue("avgUploadDurationMs", 0.0))
|
|
729
|
+
putInt("currentQueueDepth", toIntValue("currentQueueDepth"))
|
|
730
|
+
toLongValue("lastUploadTime")?.let { putDouble("lastUploadTime", it.toDouble()) } ?: putNull("lastUploadTime")
|
|
731
|
+
toLongValue("lastRetryTime")?.let { putDouble("lastRetryTime", it.toDouble()) } ?: putNull("lastRetryTime")
|
|
732
|
+
putDouble("totalBytesUploaded", toDoubleValue("totalBytesUploaded", 0.0))
|
|
733
|
+
putDouble("totalBytesEvicted", toDoubleValue("totalBytesEvicted", 0.0))
|
|
732
734
|
})
|
|
733
735
|
}
|
|
734
736
|
|
|
@@ -29,6 +29,7 @@ import android.widget.EditText
|
|
|
29
29
|
import android.widget.ImageButton
|
|
30
30
|
import java.lang.ref.WeakReference
|
|
31
31
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
32
|
+
import java.util.concurrent.atomic.AtomicLong
|
|
32
33
|
import kotlin.math.abs
|
|
33
34
|
import kotlin.math.sqrt
|
|
34
35
|
|
|
@@ -59,6 +60,7 @@ class InteractionRecorder private constructor(private val context: Context) {
|
|
|
59
60
|
private val inputObservers = CopyOnWriteArrayList<WeakReference<EditText>>()
|
|
60
61
|
private val navigationStack = mutableListOf<String>()
|
|
61
62
|
private val coalesceWindow: Long = 300 // ms
|
|
63
|
+
private val lastInteractionTimestampMs = AtomicLong(0L)
|
|
62
64
|
|
|
63
65
|
internal var currentActivity: WeakReference<Activity>? = null
|
|
64
66
|
|
|
@@ -86,8 +88,11 @@ class InteractionRecorder private constructor(private val context: Context) {
|
|
|
86
88
|
gestureAggregator = null
|
|
87
89
|
inputObservers.clear()
|
|
88
90
|
navigationStack.clear()
|
|
91
|
+
lastInteractionTimestampMs.set(0L)
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
fun latestInteractionTimestampMs(): Long = lastInteractionTimestampMs.get()
|
|
95
|
+
|
|
91
96
|
fun observeTextField(field: EditText) {
|
|
92
97
|
if (inputObservers.any { it.get() === field }) return
|
|
93
98
|
inputObservers.add(WeakReference(field))
|
|
@@ -122,13 +127,27 @@ class InteractionRecorder private constructor(private val context: Context) {
|
|
|
122
127
|
window.callback = object : Window.Callback by original {
|
|
123
128
|
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
|
|
124
129
|
if (event != null) {
|
|
130
|
+
markInteractionNow()
|
|
125
131
|
agg.processTouchEvent(event)
|
|
132
|
+
|
|
133
|
+
// Notify SpecialCases about touch phases for touch-based
|
|
134
|
+
// map idle detection (Mapbox v10+ fallback).
|
|
135
|
+
when (event.actionMasked) {
|
|
136
|
+
MotionEvent.ACTION_DOWN ->
|
|
137
|
+
SpecialCases.shared.notifyTouchBegan()
|
|
138
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL ->
|
|
139
|
+
SpecialCases.shared.notifyTouchEnded()
|
|
140
|
+
}
|
|
126
141
|
}
|
|
127
142
|
return original.dispatchTouchEvent(event)
|
|
128
143
|
}
|
|
129
144
|
}
|
|
130
145
|
}
|
|
131
146
|
|
|
147
|
+
private fun markInteractionNow() {
|
|
148
|
+
lastInteractionTimestampMs.set(System.currentTimeMillis())
|
|
149
|
+
}
|
|
150
|
+
|
|
132
151
|
private fun removeGlobalTouchListener() {
|
|
133
152
|
val window = installedWindow?.get()
|
|
134
153
|
if (window != null) {
|
|
@@ -450,6 +469,11 @@ private class GestureAggregator(
|
|
|
450
469
|
}
|
|
451
470
|
|
|
452
471
|
private fun resolveTarget(location: PointF): String {
|
|
472
|
+
// When a map view is visible, skip the expensive hit-test walk
|
|
473
|
+
// through the deep SurfaceView/TextureView hierarchy.
|
|
474
|
+
if (SpecialCases.shared.mapVisible) {
|
|
475
|
+
return "map"
|
|
476
|
+
}
|
|
453
477
|
return "view_${location.x.toInt()}_${location.y.toInt()}"
|
|
454
478
|
}
|
|
455
479
|
|
|
@@ -462,6 +486,10 @@ private class GestureAggregator(
|
|
|
462
486
|
* (e.g. TextView inside a Pressable), not the clickable Pressable itself.
|
|
463
487
|
*/
|
|
464
488
|
private fun isViewInteractive(location: PointF): Boolean {
|
|
489
|
+
// Skip the expensive hierarchy walk when a map is visible to
|
|
490
|
+
// prevent micro-stutter during pan/zoom gestures.
|
|
491
|
+
if (SpecialCases.shared.mapVisible) return false
|
|
492
|
+
|
|
465
493
|
val activity = recorder.currentActivity?.get() ?: return false
|
|
466
494
|
val decorView = activity.window?.decorView ?: return false
|
|
467
495
|
val hit = findViewAt(decorView, location.x.toInt(), location.y.toInt()) ?: return false
|
|
@@ -87,7 +87,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
87
87
|
DeviceRegistrar.shared?.endpoint = value
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
var snapshotInterval: Double = 0
|
|
90
|
+
var snapshotInterval: Double = 1.0
|
|
91
91
|
var compressionLevel: Double = 0.5
|
|
92
92
|
var visualCaptureEnabled: Boolean = true
|
|
93
93
|
var interactionCaptureEnabled: Boolean = true
|
|
@@ -169,20 +169,20 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
fun beginReplay(apiToken: String, serverEndpoint: String, captureSettings: Map<String, Any>? = null) {
|
|
172
|
-
DiagnosticLog.
|
|
172
|
+
DiagnosticLog.trace("[ReplayOrchestrator] beginReplay v2")
|
|
173
173
|
val perf = PerformanceSnapshot.capture()
|
|
174
174
|
DiagnosticLog.debugSessionCreate("ORCHESTRATOR_INIT", "beginReplay", perf)
|
|
175
|
-
DiagnosticLog.
|
|
175
|
+
DiagnosticLog.trace("[ReplayOrchestrator] beginReplay called, endpoint=$serverEndpoint")
|
|
176
176
|
|
|
177
177
|
this.apiToken = apiToken
|
|
178
178
|
this.serverEndpoint = serverEndpoint
|
|
179
179
|
applySettings(captureSettings)
|
|
180
180
|
|
|
181
181
|
DiagnosticLog.debugSessionCreate("CREDENTIAL_START", "Requesting device credential")
|
|
182
|
-
DiagnosticLog.
|
|
182
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Requesting credential from DeviceRegistrar.shared=${DeviceRegistrar.shared != null}")
|
|
183
183
|
|
|
184
184
|
DeviceRegistrar.shared?.obtainCredential(apiToken) { ok, cred ->
|
|
185
|
-
DiagnosticLog.
|
|
185
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Credential callback: ok=$ok, cred=${cred?.take(20) ?: "null"}...")
|
|
186
186
|
if (!ok) {
|
|
187
187
|
DiagnosticLog.debugSessionCreate("CREDENTIAL_FAIL", "Failed")
|
|
188
188
|
DiagnosticLog.caution("[ReplayOrchestrator] Credential fetch FAILED - recording cannot start")
|
|
@@ -194,7 +194,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
194
194
|
SegmentDispatcher.shared.apiToken = apiToken
|
|
195
195
|
SegmentDispatcher.shared.credential = cred
|
|
196
196
|
|
|
197
|
-
DiagnosticLog.
|
|
197
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Credential OK, calling monitorNetwork")
|
|
198
198
|
monitorNetwork(apiToken)
|
|
199
199
|
}
|
|
200
200
|
}
|
|
@@ -217,7 +217,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
217
217
|
initSession()
|
|
218
218
|
TelemetryPipeline.shared?.activateDeferredMode()
|
|
219
219
|
|
|
220
|
-
val renderCfg = computeRender(
|
|
220
|
+
val renderCfg = computeRender(1, "standard")
|
|
221
221
|
|
|
222
222
|
if (visualCaptureEnabled) {
|
|
223
223
|
VisualCapture.shared?.configure(renderCfg.first, renderCfg.second)
|
|
@@ -267,6 +267,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
267
267
|
"screensVisited" to visitedScreens.toList(),
|
|
268
268
|
"screenCount" to visitedScreens.toSet().size
|
|
269
269
|
)
|
|
270
|
+
val queueDepthAtFinalize = TelemetryPipeline.shared?.getQueueDepth() ?: 0
|
|
270
271
|
|
|
271
272
|
SegmentDispatcher.shared.evaluateReplayRetention(sid, metrics) { retain, reason ->
|
|
272
273
|
// UI operations MUST run on main thread
|
|
@@ -287,7 +288,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
287
288
|
}
|
|
288
289
|
finalized = true
|
|
289
290
|
|
|
290
|
-
SegmentDispatcher.shared.concludeReplay(sid, termMs, bgTimeMs, metrics) { ok ->
|
|
291
|
+
SegmentDispatcher.shared.concludeReplay(sid, termMs, bgTimeMs, metrics, queueDepthAtFinalize) { ok ->
|
|
291
292
|
if (ok) clearRecovery()
|
|
292
293
|
completion?.invoke(true, ok)
|
|
293
294
|
}
|
|
@@ -324,7 +325,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
324
325
|
// If recording is disabled, disable visual capture
|
|
325
326
|
if (!recordingEnabled) {
|
|
326
327
|
visualCaptureEnabled = false
|
|
327
|
-
DiagnosticLog.
|
|
328
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Visual capture disabled by remote config (recordingEnabled=false)")
|
|
328
329
|
}
|
|
329
330
|
|
|
330
331
|
// If already recording, restart the duration limit timer with updated config
|
|
@@ -332,7 +333,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
332
333
|
startDurationLimitTimer()
|
|
333
334
|
}
|
|
334
335
|
|
|
335
|
-
DiagnosticLog.
|
|
336
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Remote config applied: rejourneyEnabled=$rejourneyEnabled, recordingEnabled=$recordingEnabled, sampleRate=$sampleRate%, maxRecording=${maxRecordingMinutes}min, isSampledIn=$recordingEnabled")
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
fun unredactView(view: View) {
|
|
@@ -387,8 +388,9 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
387
388
|
"crashCount" to 1,
|
|
388
389
|
"durationSeconds" to ((nowMs - origStart) / 1000).toInt()
|
|
389
390
|
)
|
|
391
|
+
val queueDepthAtFinalize = TelemetryPipeline.shared?.getQueueDepth() ?: 0
|
|
390
392
|
|
|
391
|
-
SegmentDispatcher.shared.concludeReplay(recId, nowMs, 0, crashMetrics) { ok ->
|
|
393
|
+
SegmentDispatcher.shared.concludeReplay(recId, nowMs, 0, crashMetrics, queueDepthAtFinalize) { ok ->
|
|
392
394
|
clearRecovery()
|
|
393
395
|
completion(if (ok) recId else null)
|
|
394
396
|
}
|
|
@@ -467,10 +469,10 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
467
469
|
}
|
|
468
470
|
|
|
469
471
|
private fun monitorNetwork(token: String) {
|
|
470
|
-
DiagnosticLog.
|
|
472
|
+
DiagnosticLog.trace("[ReplayOrchestrator] monitorNetwork called")
|
|
471
473
|
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
472
474
|
if (connectivityManager == null) {
|
|
473
|
-
DiagnosticLog.
|
|
475
|
+
DiagnosticLog.trace("[ReplayOrchestrator] No ConnectivityManager, starting recording directly")
|
|
474
476
|
beginRecording(token)
|
|
475
477
|
return
|
|
476
478
|
}
|
|
@@ -496,12 +498,12 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
496
498
|
// Check current network state immediately (callback only fires on CHANGES)
|
|
497
499
|
val activeNetwork = connectivityManager.activeNetwork
|
|
498
500
|
val capabilities = activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) }
|
|
499
|
-
DiagnosticLog.
|
|
501
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Network check: activeNetwork=${activeNetwork != null}, capabilities=${capabilities != null}")
|
|
500
502
|
if (capabilities != null) {
|
|
501
503
|
handleNetworkChange(capabilities, token)
|
|
502
504
|
} else {
|
|
503
505
|
// No active network - start recording anyway, uploads will retry when network available
|
|
504
|
-
DiagnosticLog.
|
|
506
|
+
DiagnosticLog.trace("[ReplayOrchestrator] No active network, starting recording anyway")
|
|
505
507
|
mainHandler.post { beginRecording(token) }
|
|
506
508
|
}
|
|
507
509
|
} catch (e: Exception) {
|
|
@@ -543,27 +545,27 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
543
545
|
}
|
|
544
546
|
|
|
545
547
|
private fun beginRecording(token: String) {
|
|
546
|
-
DiagnosticLog.
|
|
548
|
+
DiagnosticLog.trace("[ReplayOrchestrator] beginRecording called, live=$live")
|
|
547
549
|
if (live) {
|
|
548
|
-
DiagnosticLog.
|
|
550
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Already live, skipping")
|
|
549
551
|
return
|
|
550
552
|
}
|
|
551
553
|
live = true
|
|
552
554
|
|
|
553
555
|
this.apiToken = token
|
|
554
556
|
initSession()
|
|
555
|
-
DiagnosticLog.
|
|
557
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Session initialized: replayId=$replayId")
|
|
556
558
|
|
|
557
559
|
// Reactivate the dispatcher in case it was halted from a previous session
|
|
558
560
|
SegmentDispatcher.shared.activate()
|
|
559
561
|
TelemetryPipeline.shared?.activate()
|
|
560
562
|
|
|
561
|
-
val renderCfg = computeRender(
|
|
562
|
-
DiagnosticLog.
|
|
563
|
+
val renderCfg = computeRender(1, "standard")
|
|
564
|
+
DiagnosticLog.trace("[ReplayOrchestrator] VisualCapture.shared=${VisualCapture.shared != null}, visualCaptureEnabled=$visualCaptureEnabled")
|
|
563
565
|
VisualCapture.shared?.configure(renderCfg.first, renderCfg.second)
|
|
564
566
|
|
|
565
567
|
if (visualCaptureEnabled) {
|
|
566
|
-
DiagnosticLog.
|
|
568
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Starting VisualCapture")
|
|
567
569
|
VisualCapture.shared?.beginCapture(replayStartMs)
|
|
568
570
|
}
|
|
569
571
|
if (interactionCaptureEnabled) InteractionRecorder.shared?.activate()
|
|
@@ -574,7 +576,7 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
574
576
|
// Start duration limit timer based on remote config
|
|
575
577
|
startDurationLimitTimer()
|
|
576
578
|
|
|
577
|
-
DiagnosticLog.
|
|
579
|
+
DiagnosticLog.trace("[ReplayOrchestrator] beginRecording completed")
|
|
578
580
|
}
|
|
579
581
|
|
|
580
582
|
// MARK: - Duration Limit Timer
|
|
@@ -591,19 +593,19 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
591
593
|
val remaining = if (maxMs > elapsed) maxMs - elapsed else 0L
|
|
592
594
|
|
|
593
595
|
if (remaining <= 0) {
|
|
594
|
-
DiagnosticLog.
|
|
596
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Duration limit already exceeded, stopping session")
|
|
595
597
|
endReplay()
|
|
596
598
|
return
|
|
597
599
|
}
|
|
598
600
|
|
|
599
601
|
durationLimitRunnable = Runnable {
|
|
600
602
|
if (!live) return@Runnable
|
|
601
|
-
DiagnosticLog.
|
|
603
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Recording duration limit reached (${maxMinutes}min), stopping session")
|
|
602
604
|
endReplay()
|
|
603
605
|
}
|
|
604
606
|
mainHandler.postDelayed(durationLimitRunnable!!, remaining)
|
|
605
607
|
|
|
606
|
-
DiagnosticLog.
|
|
608
|
+
DiagnosticLog.trace("[ReplayOrchestrator] Duration limit timer set: ${remaining / 1000}s remaining (max ${maxMinutes}min)")
|
|
607
609
|
}
|
|
608
610
|
|
|
609
611
|
private fun stopDurationLimitTimer() {
|
|
@@ -704,6 +706,13 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
704
706
|
return
|
|
705
707
|
}
|
|
706
708
|
|
|
709
|
+
// Throttle hierarchy capture when map is visible and animating —
|
|
710
|
+
// ViewHierarchyScanner traverses the full view tree including map's
|
|
711
|
+
// deep SurfaceView/TextureView children, adding main-thread pressure.
|
|
712
|
+
if (SpecialCases.shared.mapVisible && !SpecialCases.shared.mapIdle) {
|
|
713
|
+
return
|
|
714
|
+
}
|
|
715
|
+
|
|
707
716
|
val hierarchy = ViewHierarchyScanner.shared?.captureHierarchy() ?: return
|
|
708
717
|
|
|
709
718
|
val hash = hierarchyHash(hierarchy)
|
|
@@ -729,12 +738,12 @@ class ReplayOrchestrator private constructor(private val context: Context) {
|
|
|
729
738
|
}
|
|
730
739
|
|
|
731
740
|
private fun computeRender(fps: Int, tier: String): Pair<Double, Double> {
|
|
732
|
-
val
|
|
733
|
-
|
|
734
|
-
"
|
|
735
|
-
"
|
|
736
|
-
"
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
741
|
+
val tierLower = tier.lowercase()
|
|
742
|
+
return when (tierLower) {
|
|
743
|
+
"minimal" -> Pair(2.0, 0.4) // 0.5 fps for maximum size reduction
|
|
744
|
+
"low" -> Pair(1.0 / fps.coerceIn(1, 99), 0.4)
|
|
745
|
+
"standard" -> Pair(1.0 / fps.coerceIn(1, 99), 0.5)
|
|
746
|
+
"high" -> Pair(1.0 / fps.coerceIn(1, 99), 0.55)
|
|
747
|
+
else -> Pair(1.0 / fps.coerceIn(1, 99), 0.5)
|
|
748
|
+
}
|
|
740
749
|
}
|