Skip to content

Commit

Permalink
fix: precise android replay frame timestamps (#2519)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaind authored Dec 20, 2024
1 parent 9a5040f commit ec83631
Show file tree
Hide file tree
Showing 6 changed files with 30 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,9 @@ class ScreenshotEventProcessor implements EventProcessor {
Future<Uint8List?> createScreenshot() =>
_recorder.capture(_convertImageToUint8List);

Future<Uint8List?> _convertImageToUint8List(Image image) async {
final byteData = await image.toByteData(format: ImageByteFormat.png);
Future<Uint8List?> _convertImageToUint8List(Screenshot screenshot) async {
final byteData =
await screenshot.image.toByteData(format: ImageByteFormat.png);

final bytes = byteData?.buffer.asUint8List();
if (bytes?.isNotEmpty == true) {
Expand Down
3 changes: 2 additions & 1 deletion flutter/lib/src/native/cocoa/sentry_native_cocoa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ class SentryNativeCocoa extends SentryNativeChannel {
});
}

return _replayRecorder?.capture((image) async {
return _replayRecorder?.capture((screenshot) async {
final image = screenshot.image;
final imageData =
await image.toByteData(format: ImageByteFormat.png);
if (imageData != null) {
Expand Down
2 changes: 1 addition & 1 deletion flutter/lib/src/native/java/android_replay_recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AndroidReplayRecorder extends ScheduledScreenshotRecorder {

Future<void> _addReplayScreenshot(
ScreenshotPng screenshot, bool isNewlyCaptured) async {
final timestamp = DateTime.now().millisecondsSinceEpoch;
final timestamp = screenshot.timestamp.millisecondsSinceEpoch;
final filePath = "$_cacheDir/$timestamp.png";

options.logger(
Expand Down
10 changes: 7 additions & 3 deletions flutter/lib/src/replay/scheduled_recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:ui';
import 'package:meta/meta.dart';

import '../../sentry_flutter.dart';
import '../screenshot/recorder.dart';
import 'replay_recorder.dart';
import 'scheduled_recorder_config.dart';
import 'scheduler.dart';
Expand Down Expand Up @@ -95,11 +96,13 @@ class ScheduledScreenshotRecorder extends ReplayScreenshotRecorder {

void _capture(Duration sinceSchedulerEpoch) => capture(_onImageCaptured);

Future<void> _onImageCaptured(Image image) async {
Future<void> _onImageCaptured(Screenshot capturedScreenshot) async {
final image = capturedScreenshot.image;
if (_status == _Status.running) {
var imageData = await image.toByteData(format: ImageByteFormat.png);
if (imageData != null) {
final screenshot = ScreenshotPng(image.width, image.height, imageData);
final screenshot = ScreenshotPng(
image.width, image.height, imageData, capturedScreenshot.timestamp);
await _onScreenshot(screenshot, true);
_idleFrameFiller.actualFrameReceived(screenshot);
} else {
Expand Down Expand Up @@ -133,8 +136,9 @@ class ScreenshotPng {
final int width;
final int height;
final ByteData data;
final DateTime timestamp;

const ScreenshotPng(this.width, this.height, this.data);
const ScreenshotPng(this.width, this.height, this.data, this.timestamp);
}

// Workaround for https://github.com/getsentry/sentry-java/issues/3677
Expand Down
16 changes: 13 additions & 3 deletions flutter/lib/src/screenshot/recorder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ScreenshotRecorder {
/// To prevent accidental addition of await before that happens,
///
/// THIS FUNCTION MUST NOT BE ASYNC.
Future<R> capture<R>(Future<R> Function(Image) callback) {
Future<R> capture<R>(Future<R> Function(Screenshot) callback) {
try {
final flow = Flow.begin();
Timeline.startSync('Sentry::captureScreenshot', flow: flow);
Expand Down Expand Up @@ -182,9 +182,10 @@ class _Capture<R> {
/// See [task] which is what gets completed with the callback result.
void Function() createTask(
Future<Image> futureImage,
Future<R> Function(Image) callback,
Future<R> Function(Screenshot) callback,
List<WidgetFilterItem>? obscureItems,
Flow flow) {
final timestamp = DateTime.now();
return () async {
Timeline.startSync('Sentry::renderScreenshot', flow: flow);
final recorder = PictureRecorder();
Expand All @@ -209,7 +210,8 @@ class _Capture<R> {
Timeline.finishSync(); // Sentry::screenshotToImage
try {
Timeline.startSync('Sentry::screenshotCallback', flow: flow);
_completer.complete(await callback(finalImage));
_completer
.complete(await callback(Screenshot(finalImage, timestamp)));
Timeline.finishSync(); // Sentry::screenshotCallback
} finally {
finalImage.dispose(); // image needs to be disposed-of manually
Expand Down Expand Up @@ -288,3 +290,11 @@ extension on widgets.BuildContext {
return null;
}
}

@internal
class Screenshot {
final Image image;
final DateTime timestamp;

const Screenshot(this.image, this.timestamp);
}
6 changes: 4 additions & 2 deletions flutter/test/screenshot/recorder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ class _Fixture {
return fixture;
}

Future<String?> capture() => sut.capture<String?>(
(Image image) => Future.value("${image.width}x${image.height}"));
Future<String?> capture() => sut.capture<String?>((Screenshot screenshot) {
final image = screenshot.image;
return Future.value("${image.width}x${image.height}");
});
}

0 comments on commit ec83631

Please sign in to comment.