Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import io.sentry.android.core.SentryAndroidOptions
import io.sentry.android.core.performance.AppStartMetrics
import io.sentry.android.core.performance.TimeSpan
import io.sentry.android.replay.ReplayIntegration
import io.sentry.android.replay.ScreenshotRecorderConfig
import io.sentry.protocol.DebugImage
import io.sentry.protocol.User
import io.sentry.transport.CurrentDateProvider
Expand Down Expand Up @@ -61,8 +60,6 @@ class SentryFlutterPlugin :
when (call.method) {
"initNativeSdk" -> initNativeSdk(call, result)
"closeNativeSdk" -> closeNativeSdk(result)
"setReplayConfig" -> setReplayConfig(call, result)
"captureReplay" -> captureReplay(result)
else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -344,83 +341,5 @@ class SentryFlutterPlugin :
"code_id" to codeId,
"debug_file" to debugFile,
)

private fun Double.adjustReplaySizeToBlockSize(): Double {
val remainder = this % VIDEO_BLOCK_SIZE
return if (remainder <= VIDEO_BLOCK_SIZE / 2) {
this - remainder
} else {
this + (VIDEO_BLOCK_SIZE - remainder)
}
}
}

private fun setReplayConfig(
call: MethodCall,
result: Result,
) {
// Since codec block size is 16, so we have to adjust the width and height to it,
// otherwise the codec might fail to configure on some devices, see
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/MediaCodecInfo.java;l=1999-2001
val windowWidth = call.argument("windowWidth") as? Double ?: 0.0
val windowHeight = call.argument("windowHeight") as? Double ?: 0.0

var width = call.argument("width") as? Double ?: 0.0
var height = call.argument("height") as? Double ?: 0.0

val invalidConfig =
width == 0.0 ||
height == 0.0 ||
windowWidth == 0.0 ||
windowHeight == 0.0

if (invalidConfig) {
result.error(
"5",
"Replay config is not valid: width: $width, height: $height, " +
"windowWidth: $windowWidth, windowHeight: $windowHeight",
null,
)
return
}

// First update the smaller dimension, as changing that will affect the screen ratio more.
if (width < height) {
val newWidth = width.adjustReplaySizeToBlockSize()
height = (height * (newWidth / width)).adjustReplaySizeToBlockSize()
width = newWidth
} else {
val newHeight = height.adjustReplaySizeToBlockSize()
width = (width * (newHeight / height)).adjustReplaySizeToBlockSize()
height = newHeight
}

val replayConfig =
ScreenshotRecorderConfig(
recordingWidth = width.roundToInt(),
recordingHeight = height.roundToInt(),
scaleFactorX = width.toFloat() / windowWidth.toFloat(),
scaleFactorY = height.toFloat() / windowHeight.toFloat(),
frameRate = call.argument("frameRate") as? Int ?: 0,
bitRate = call.argument("bitRate") as? Int ?: 0,
)
Log.i(
"Sentry",
"Configuring replay: %dx%d at %d FPS, %d BPS".format(
replayConfig.recordingWidth,
replayConfig.recordingHeight,
replayConfig.frameRate,
replayConfig.bitRate,
),
)
replay?.onConfigurationChanged(replayConfig)
result.success("")
}

private fun captureReplay(
result: Result,
) {
replay!!.captureReplay(isTerminating = false)
result.success(replay!!.getReplayId().toString())
}
}
2 changes: 2 additions & 0 deletions packages/flutter/ffi-jni.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ log_level: all
classes:
- io.sentry.android.core.InternalSentrySdk
- io.sentry.android.replay.ReplayIntegration
- io.sentry.android.replay.ScreenshotRecorderConfig
- io.sentry.flutter.SentryFlutterPlugin
- io.sentry.Sentry
- io.sentry.Breadcrumb
- io.sentry.ScopesAdapter
- io.sentry.Scope
- io.sentry.ScopeCallback
- io.sentry.protocol.User
- io.sentry.protocol.SentryId
- android.graphics.Bitmap
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,6 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
collectProfile(call, result)
#endif

case "captureReplay":
#if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS))
PrivateSentrySDKOnly.captureReplay()
result(PrivateSentrySDKOnly.getReplayId())
#else
result(nil)
#endif

default:
result(FlutterMethodNotImplemented)
}
Expand Down Expand Up @@ -274,6 +266,15 @@ public class SentryFlutterPlugin: NSObject, FlutterPlugin {
//
// Purpose: Called from the Flutter plugin's native bridge (FFI) - bindings are created from SentryFlutterPlugin.h

@objc public class func captureReplay() -> String? {
#if canImport(UIKit) && !SENTRY_NO_UIKIT && (os(iOS) || os(tvOS))
PrivateSentrySDKOnly.captureReplay()
return PrivateSentrySDKOnly.getReplayId()
#else
return nil
#endif
}

#if os(iOS)
// Taken from the Flutter engine:
// https://github.com/flutter/engine/blob/main/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm#L150
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
+ (nullable NSData *)fetchNativeAppStartAsBytes;
+ (nullable NSData *)loadContextsAsBytes;
+ (nullable NSData *)loadDebugImagesAsBytes:(NSSet<NSString *> *)instructionAddresses;
+ (nullable NSString *)captureReplay;
@end
#endif
10 changes: 10 additions & 0 deletions packages/flutter/lib/src/native/cocoa/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,7 @@ late final _sel_fetchNativeAppStartAsBytes =
late final _sel_loadContextsAsBytes = objc.registerName("loadContextsAsBytes");
late final _sel_loadDebugImagesAsBytes_ =
objc.registerName("loadDebugImagesAsBytes:");
late final _sel_captureReplay = objc.registerName("captureReplay");

/// SentryFlutterPlugin
class SentryFlutterPlugin extends objc.NSObject {
Expand Down Expand Up @@ -1568,6 +1569,15 @@ class SentryFlutterPlugin extends objc.NSObject {
: objc.NSData.castFromPointer(_ret, retain: true, release: true);
}

/// captureReplay
static objc.NSString? captureReplay() {
final _ret =
_objc_msgSend_151sglz(_class_SentryFlutterPlugin, _sel_captureReplay);
return _ret.address == 0
? null
: objc.NSString.castFromPointer(_ret, retain: true, release: true);
}

/// init
SentryFlutterPlugin init() {
objc.checkOsVersionInternal('SentryFlutterPlugin.init',
Expand Down
11 changes: 11 additions & 0 deletions packages/flutter/lib/src/native/cocoa/sentry_native_cocoa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,17 @@ class SentryNativeCocoa extends SentryNativeChannel {
scope.removeTagForKey(key.toNSString());
}));
});

@override
SentryId captureReplay() =>
tryCatchSync('captureReplay', () {
final value = cocoa.SentryFlutterPlugin.captureReplay()?.toDartString();
if (value == null) {
return SentryId.empty();
}
return SentryId.fromId(value);
}) ??
SentryId.empty();
}

/// This map conversion is needed so we can use the toNSDictionary extension function
Expand Down
Loading
Loading