Skip to content

Commit ea6d86d

Browse files
authored
feat: replay quality setting (#2582)
* replay quality config * configure quality on ios * chore * ktlint * chore: format * swiftlint * fix kotlin tests
1 parent 1eca72b commit ea6d86d

16 files changed

+98
-34
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Features
6+
7+
- Add SentryReplayQuality setting (`options.experimental.replay.quality`) ([#2582](https://github.com/getsentry/sentry-dart/pull/2582))
8+
59
### Dependencies
610

711
- Bump Native SDK from v0.7.17 to v0.7.18 ([#2578](https://github.com/getsentry/sentry-dart/pull/2578))

flutter/android/src/main/kotlin/io/sentry/flutter/SentryFlutter.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,14 @@ class SentryFlutter(
163163
options: SentryReplayOptions,
164164
data: Map<String, Any>,
165165
) {
166+
options.quality =
167+
when (data["quality"] as? String) {
168+
"low" -> SentryReplayOptions.SentryReplayQuality.LOW
169+
"high" -> SentryReplayOptions.SentryReplayQuality.HIGH
170+
else -> {
171+
SentryReplayOptions.SentryReplayQuality.MEDIUM
172+
}
173+
}
166174
options.sessionSampleRate = data["sessionSampleRate"] as? Double
167175
options.onErrorSampleRate = data["onErrorSampleRate"] as? Double
168176

flutter/ios/Classes/SentryFlutter.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ public final class SentryFlutter {
113113
}
114114
#if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS))
115115
if let replayOptions = data["replay"] as? [String: Any] {
116+
switch data["quality"] as? String {
117+
case "low":
118+
options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.low
119+
case "high":
120+
options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.high
121+
default:
122+
options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality.medium
123+
}
116124
options.sessionReplay.sessionSampleRate =
117125
(replayOptions["sessionSampleRate"] as? NSNumber)?.floatValue ?? 0
118126
options.sessionReplay.onErrorSampleRate =

flutter/lib/sentry_flutter.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export 'src/navigation/sentry_navigator_observer.dart';
99
export 'src/sentry_flutter.dart';
1010
export 'src/sentry_flutter_options.dart';
1111
export 'src/sentry_replay_options.dart';
12+
export 'src/replay/replay_quality.dart';
1213
export 'src/sentry_privacy_options.dart';
1314
export 'src/flutter_sentry_attachment.dart';
1415
export 'src/sentry_asset_bundle.dart' show SentryAssetBundle;

flutter/lib/src/native/cocoa/cocoa_replay_recorder.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ class CocoaReplayRecorder {
1717
var _completer = Completer<Map<String, int>?>();
1818

1919
CocoaReplayRecorder(this._options)
20-
: _recorder =
21-
ReplayScreenshotRecorder(ScreenshotRecorderConfig(), _options) {
20+
: _recorder = ReplayScreenshotRecorder(
21+
ScreenshotRecorderConfig(
22+
pixelRatio:
23+
_options.experimental.replay.quality.resolutionScalingFactor,
24+
),
25+
_options) {
2226
_stabilizer = ScreenshotStabilizer(_recorder, _options, (screenshot) async {
2327
final data = await screenshot.rawRgbaData;
2428
_options.logger(

flutter/lib/src/native/sentry_native_channel.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class SentryNativeChannel
6969
options.appHangTimeoutInterval.inMilliseconds,
7070
if (options.proxy != null) 'proxy': options.proxy?.toJson(),
7171
'replay': <String, dynamic>{
72+
'quality': options.experimental.replay.quality.name,
7273
'sessionSampleRate': options.experimental.replay.sessionSampleRate,
7374
'onErrorSampleRate': options.experimental.replay.onErrorSampleRate,
7475
// TMP: this doesn't actually mask, just ensures we show the correct
@@ -235,7 +236,6 @@ class SentryNativeChannel
235236
'width': config.width,
236237
'height': config.height,
237238
'frameRate': config.frameRate,
238-
'bitRate': config.bitRate,
239239
});
240240

241241
@override

flutter/lib/src/replay/integration.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,22 @@ class ReplayIntegration extends Integration<SentryFlutterOptions> {
1818

1919
@override
2020
FutureOr<void> call(Hub hub, SentryFlutterOptions options) {
21-
if (_native.supportsReplay && options.experimental.replay.isEnabled) {
21+
final replayOptions = options.experimental.replay;
22+
if (_native.supportsReplay && replayOptions.isEnabled) {
2223
options.sdk.addIntegration(replayIntegrationName);
2324

2425
// We only need the integration when error-replay capture is enabled.
25-
if ((options.experimental.replay.onErrorSampleRate ?? 0) > 0) {
26+
if ((replayOptions.onErrorSampleRate ?? 0) > 0) {
2627
options.addEventProcessor(ReplayEventProcessor(hub, _native));
2728
}
2829

2930
SentryScreenshotWidget.onBuild((status, prevStatus) {
3031
if (status != prevStatus) {
3132
_native.setReplayConfig(ReplayConfig(
32-
width: status.size?.width ?? 0.0,
33-
height: status.size?.height ?? 0.0,
34-
frameRate: 1,
35-
bitRate: 75000, // TODO replay quality config
36-
));
33+
width: replayOptions.quality.resolutionScalingFactor *
34+
(status.size?.width ?? 0.0),
35+
height: replayOptions.quality.resolutionScalingFactor *
36+
(status.size?.height ?? 0.0)));
3737
}
3838
return true;
3939
});

flutter/lib/src/replay/replay_config.dart

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,9 @@ class ReplayConfig extends ScheduledScreenshotRecorderConfig {
1111
@override
1212
double get height => super.height!;
1313

14-
final int bitRate;
15-
1614
const ReplayConfig({
1715
required double super.width,
1816
required double super.height,
19-
required super.frameRate,
20-
required this.bitRate,
17+
super.frameRate = 1,
2118
});
2219
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'package:meta/meta.dart';
2+
3+
/// The quality of the captured replay.
4+
enum SentryReplayQuality {
5+
high(resolutionScalingFactor: 1.0),
6+
medium(resolutionScalingFactor: 1.0),
7+
low(resolutionScalingFactor: 0.8);
8+
9+
@internal
10+
final double resolutionScalingFactor;
11+
12+
const SentryReplayQuality({required this.resolutionScalingFactor});
13+
}

flutter/lib/src/screenshot/recorder_config.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,24 @@ import 'package:meta/meta.dart';
66
class ScreenshotRecorderConfig {
77
final double? width;
88
final double? height;
9+
final double? pixelRatio;
910

10-
const ScreenshotRecorderConfig({
11-
this.width,
12-
this.height,
13-
});
11+
const ScreenshotRecorderConfig({this.width, this.height, this.pixelRatio});
1412

1513
double? getPixelRatio(double srcWidth, double srcHeight) {
16-
assert((width == null) == (height == null));
17-
if (width == null || height == null) {
14+
assert((width == null) == (height == null),
15+
"Screenshot width and height must be both set or both null (automatic).");
16+
assert(pixelRatio == null || (pixelRatio! > 0 && pixelRatio! <= 1.0),
17+
'Screenshot pixelRatio must be between 0 and 1.');
18+
assert(!(width != null && pixelRatio != null),
19+
'Screenshot config may only define the size (width & height) or the pixelRatio, not both.');
20+
21+
if (pixelRatio != null) {
22+
return pixelRatio;
23+
} else if (width != null && height != null) {
24+
return min(width! / srcWidth, height! / srcHeight);
25+
} else {
1826
return null;
1927
}
20-
return min(width! / srcWidth, height! / srcHeight);
2128
}
2229
}

0 commit comments

Comments
 (0)