diff --git a/CHANGELOG.md b/CHANGELOG.md index b54b50d..0555dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Next +## 4.9.1 + +- fix: blank screen when viewing session replay recordings ([#139](https://github.com/PostHog/posthog-flutter/pull/139)) + ## 4.9.0 - feat: add getter for current session identifier ([#134](https://github.com/PostHog/posthog-flutter/pull/134)) diff --git a/lib/src/replay/mask/image_mask_painter.dart b/lib/src/replay/mask/image_mask_painter.dart index 237f29c..2ba2da7 100644 --- a/lib/src/replay/mask/image_mask_painter.dart +++ b/lib/src/replay/mask/image_mask_painter.dart @@ -30,7 +30,6 @@ class ImageMaskPainter { (image.width * pixelRatio).round(), (image.height * pixelRatio).round(), ); - image.dispose(); return maskedImage; } } diff --git a/lib/src/replay/screenshot/screenshot_capturer.dart b/lib/src/replay/screenshot/screenshot_capturer.dart index 80fadda..80995af 100644 --- a/lib/src/replay/screenshot/screenshot_capturer.dart +++ b/lib/src/replay/screenshot/screenshot_capturer.dart @@ -9,7 +9,6 @@ import 'package:posthog_flutter/src/replay/vendor/equality.dart'; import 'package:posthog_flutter/src/util/logging.dart'; class ImageInfo { - final ui.Image image; final int id; final int x; final int y; @@ -18,7 +17,7 @@ class ImageInfo { final bool shouldSendMetaEvent; final Uint8List imageBytes; - ImageInfo(this.image, this.id, this.x, this.y, this.width, this.height, + ImageInfo(this.id, this.x, this.y, this.width, this.height, this.shouldSendMetaEvent, this.imageBytes); } @@ -56,6 +55,16 @@ class ScreenshotCapturer { _views[renderObject] = statusView; } + Future _getImageBytes(ui.Image img) async { + final ByteData? byteData = + await img.toByteData(format: ui.ImageByteFormat.png); + if (byteData == null || byteData.lengthInBytes == 0) { + printIfDebug('Error: Failed to convert image to byte data.'); + return null; + } + return byteData.buffer.asUint8List(); + } + Future captureScreenshot() async { final context = PostHogMaskController.instance.containerKey.currentContext; if (context == null) { @@ -90,24 +99,16 @@ class ScreenshotCapturer { // using png because its compressed, the native SDKs will decompress it // and transform to jpeg if needed (soon webp) // https://github.com/brendan-duncan/image does not have webp encoding - final ByteData? byteData = - await image.toByteData(format: ui.ImageByteFormat.png); - if (byteData == null) { - printIfDebug('Error: Failed to convert image to byte data.'); - image.dispose(); - return null; - } - - Uint8List pngBytes = byteData.buffer.asUint8List(); - image.dispose(); - - if (pngBytes.isEmpty) { + Uint8List? pngBytes = await _getImageBytes(image); + if (pngBytes == null || pngBytes.isEmpty) { printIfDebug('Error: Failed to convert image byte data to Uint8List.'); + image.dispose(); return null; } if (const PHListEquality().equals(pngBytes, statusView.imageBytes)) { printIfDebug('Snapshot is the same as the last one.'); + image.dispose(); return null; } @@ -120,29 +121,38 @@ class ScreenshotCapturer { if (screenElementsRects != null) { final ui.Image maskedImage = await _imageMaskPainter.drawMaskedImage( image, screenElementsRects, pixelRatio); + + // Dispose the original image after masking + image.dispose(); + + Uint8List? maskedImagePngBytes = await _getImageBytes(maskedImage); + if (maskedImagePngBytes == null || maskedImagePngBytes.isEmpty) { + maskedImage.dispose(); + return null; + } + final imageInfo = ImageInfo( - maskedImage, viewId, globalPosition.dx.toInt(), globalPosition.dy.toInt(), srcWidth.toInt(), srcHeight.toInt(), shouldSendMetaEvent, - pngBytes); + maskedImagePngBytes); _updateStatusView(shouldSendMetaEvent, renderObject, statusView); return imageInfo; } } final imageInfo = ImageInfo( - image, - viewId, - globalPosition.dx.toInt(), - globalPosition.dy.toInt(), - srcWidth.toInt(), - srcHeight.toInt(), - shouldSendMetaEvent, - pngBytes); + viewId, + globalPosition.dx.toInt(), + globalPosition.dy.toInt(), + srcWidth.toInt(), + srcHeight.toInt(), + shouldSendMetaEvent, + pngBytes, + ); _updateStatusView(shouldSendMetaEvent, renderObject, statusView); return imageInfo; } catch (e) {