Skip to content

Commit d8ffa0a

Browse files
fix: UIDevice.current.orientation.isLandscape not main thread access (#102)
Move access to UIDevice.current.orientation.isLandscape onto main thread access <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Capture orientation on the main thread and propagate it through the image/export pipeline to viewport events, removing direct UIDevice orientation access. > > - **Session Replay Pipeline**: > - Capture device orientation on the main thread in `ScreenCaptureService.captureUIImage` and include it in `CapturedImage`. > - Propagate orientation through `UIImage.exportImage(...)` and `ExportImage` (new `orientation` field/initializer parameter). > - `SessionReplayEventGenerator.viewPortEvent` now uses `exportImage.orientation` instead of querying `UIDevice`. > - `SnapshotTaker` passes `CapturedImage.orientation` when creating `ExportImage`. > - **Misc**: > - Record `timestamp` once in `ScreenCaptureService` and reuse for the captured frame. > - Test app config: minor cleanup in `Observability` options. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 0728cca. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 0a531f5 commit d8ffa0a

File tree

4 files changed

+20
-13
lines changed

4 files changed

+20
-13
lines changed

Sources/LaunchDarklySessionReplay/Exporter/SessionReplayEventGenerator.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -220,18 +220,13 @@ actor SessionReplayEventGenerator {
220220
}
221221

222222
func viewPortEvent(exportImage: ExportImage, timestamp: TimeInterval) -> Event {
223-
#if os(iOS)
224-
let currentOrientation = UIDevice.current.orientation.isLandscape ? 1 : 0
225-
#else
226-
let currentOrientation = 0
227-
#endif
228223
let payload = ViewportPayload(width: exportImage.originalWidth,
229224
height: exportImage.originalHeight,
230225
availWidth: exportImage.originalWidth,
231226
availHeight: exportImage.originalHeight,
232227
colorDepth: 30,
233228
pixelDepth: 30,
234-
orientation: currentOrientation)
229+
orientation: exportImage.orientation)
235230
let eventData = CustomEventData(tag: .viewport, payload: payload)
236231
let event = Event(type: .Custom,
237232
data: AnyEventData(eventData),

Sources/LaunchDarklySessionReplay/ScreenCapture/ExportImage.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ struct ExportImage: Equatable {
99
let scale: CGFloat
1010
let format: ExportFormat
1111
let timestamp: TimeInterval
12-
13-
init(data: Data, originalWidth: Int, originalHeight: Int, scale: CGFloat, format: ExportFormat, timestamp: TimeInterval) {
12+
let orientation: Int
13+
14+
init(data: Data, originalWidth: Int, originalHeight: Int, scale: CGFloat, format: ExportFormat, timestamp: TimeInterval, orientation: Int) {
1415
self.data = data
1516
self.dataHashValue = data.hashValue
1617
self.originalWidth = originalWidth
1718
self.originalHeight = originalHeight
1819
self.scale = scale
1920
self.format = format
2021
self.timestamp = timestamp
22+
self.orientation = orientation
2123
}
2224

2325
func eventNode(id: Int, rr_dataURL: String) -> EventNode {
@@ -51,14 +53,15 @@ struct ExportImage: Equatable {
5153
}
5254

5355
extension UIImage {
54-
func exportImage(format: ExportFormat, originalSize: CGSize, scale: CGFloat, timestamp: TimeInterval) -> ExportImage? {
56+
func exportImage(format: ExportFormat, originalSize: CGSize, scale: CGFloat, timestamp: TimeInterval, orientation: Int) -> ExportImage? {
5557
guard let data = asData(format: format) else { return nil }
5658
return ExportImage(data: data,
5759
originalWidth: Int(originalSize.width),
5860
originalHeight: Int(originalSize.height),
5961
scale: scale,
6062
format: format,
61-
timestamp: timestamp)
63+
timestamp: timestamp,
64+
orientation: orientation)
6265
}
6366
}
6467

Sources/LaunchDarklySessionReplay/ScreenCapture/ScreenCaptureService.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ public struct CapturedImage {
88
public let scale: CGFloat
99
public let renderSize: CGSize
1010
public let timestamp: TimeInterval
11+
public let orientation: Int
1112
}
1213

1314
public final class ScreenCaptureService {
@@ -22,6 +23,12 @@ public final class ScreenCaptureService {
2223

2324
/// Capture as UIImage (must be on main thread).
2425
public func captureUIImage(yield: @escaping (CapturedImage?) async -> Void) {
26+
#if os(iOS)
27+
let orientation = UIDevice.current.orientation.isLandscape ? 1 : 0
28+
#else
29+
let orientation = 0
30+
#endif
31+
let timestamp = Date().timeIntervalSince1970
2532
let scale = 1.0
2633
let format = UIGraphicsImageRendererFormat()
2734
format.scale = scale
@@ -69,7 +76,8 @@ public final class ScreenCaptureService {
6976
let capturedImage = CapturedImage(image: image,
7077
scale: scale,
7178
renderSize: enclosingBounds.size,
72-
timestamp: Date().timeIntervalSince1970)
79+
timestamp: timestamp,
80+
orientation: orientation)
7381

7482
await yield(capturedImage)
7583
}

Sources/LaunchDarklySessionReplay/ScreenCapture/SnapshotTaker.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ final class SnapshotTaker: EventSource {
8888

8989
let lastFrameDispatchTime = DispatchTime.now()
9090
self.lastFrameDispatchTime = lastFrameDispatchTime
91-
91+
9292
captureService.captureUIImage { capturedImage in
9393
guard let capturedImage else {
9494
// dropped frame
@@ -98,7 +98,8 @@ final class SnapshotTaker: EventSource {
9898
guard let exportImage = capturedImage.image.exportImage(format: .jpeg(quality: 0.3),
9999
originalSize: capturedImage.renderSize,
100100
scale: capturedImage.scale,
101-
timestamp: capturedImage.timestamp) else {
101+
timestamp: capturedImage.timestamp,
102+
orientation: capturedImage.orientation) else {
102103
return
103104
}
104105

0 commit comments

Comments
 (0)