Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture pixel screenshots implementation on iOS #99

Merged
merged 9 commits into from
Nov 6, 2024
Merged
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
5 changes: 3 additions & 2 deletions platform/swift/source/CoreLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ extension CoreLogger: CoreLogging {
)
}

func logSessionReplayScreenshot(screen: SessionReplayCapture, duration: TimeInterval) {
func logSessionReplayScreenshot(screen: SessionReplayCapture?, duration: TimeInterval) {
let fields = screen.flatMap { screen in self.convertFields(fields: ["screen_px": screen]) } ?? []
self.underlyingLogger.logSessionReplayScreenshot(
fields: self.convertFields(fields: ["screen": screen]),
fields: fields,
duration: duration
)
}
Expand Down
4 changes: 2 additions & 2 deletions platform/swift/source/CoreLogging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ protocol CoreLogging: AnyObject {

/// Writes a session replay screen log.
///
/// - parameter screen: The captured screenshot.
/// - parameter screen: The captured screenshot. `nil` if screenshot couldn't be taken.
/// - parameter duration: The duration of time the preparation of the log took.
func logSessionReplayScreenshot(screen: SessionReplayCapture, duration: TimeInterval)
func logSessionReplayScreenshot(screen: SessionReplayCapture?, duration: TimeInterval)

/// Writes a resource utilization log.
///
Expand Down
37 changes: 28 additions & 9 deletions platform/swift/source/replay/SessionReplayTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@_implementationOnly import CapturePassable
import Foundation
import UIKit

final class SessionReplayTarget {
private let queue = DispatchQueue.serial(withLabelSuffix: "ReplayController", target: .default)
Expand Down Expand Up @@ -41,14 +42,32 @@ extension SessionReplayTarget: CapturePassable.SessionReplayTarget {
}

func captureScreenshot() {
// TODO: Implement
// DispatchQueue.main.async { [weak self] in
// self?.queue.async {
// self?.logger?.logSessionReplayScreenshot(
// screen: SessionReplayCapture(data: Data()),
// duration: 0
// )
// }
// }
DispatchQueue.main.async {
guard let window = UIApplication.shared.sessionReplayWindows().first else {
self.logger?.logSessionReplayScreenshot(
screen: nil,
duration: 0
)
return
}

let layer = window.layer
let bounds = UIScreen.main.bounds.size

self.queue.async { [weak self] in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Reflejo for my android-side understanding: would these two operations happen on separate threads? are any of these new worker threads or do we re-use existing ones?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly, those these threads get spawned even when the runtime flag session_replay.screenshots.enabled is off?

Copy link
Contributor Author

@Reflejo Reflejo Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Queues do not map 1:1 to threads, we actually use a default queue for the target here meaning this won't create an additional thread.

The lines that are under the DispatchQueue.main.async but above self.queue.async will be dispatched to the main thread (basically just to access the layer object) then the self.queue.async closure runs that in our serial queue mentioned before

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm more confused than before? then what's the diff between queue and thread? what does default mean?

let start = Uptime()
let format = UIGraphicsImageRendererFormat()
format.scale = 1.0

let renderer = UIGraphicsImageRenderer(size: bounds, format: format)
let jpeg = renderer.jpegData(withCompressionQuality: 0.1) { context in
layer.render(in: context.cgContext)
}
self?.logger?.logSessionReplayScreenshot(
screen: SessionReplayCapture(data: jpeg),
duration: Uptime().timeIntervalSince(start)
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class SessionReplayTargetTests: XCTestCase {
func testEmitsSessionReplayScreenLog() {
let expectation = self.expectation(description: "screen log is emitted")

self.logger.logSessionReplayScreen = expectation
self.logger.logSessionReplayScreenExpectation = expectation
self.target.captureScreen()

XCTAssertEqual(.completed, XCTWaiter().wait(for: [expectation], timeout: 0.5))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public final class MockCoreLogging {
public var logResourceUtilizationExpectation: XCTestExpectation?

public private(set) var sessionReplayScreenLogs = [SessionReplayScreenLog]()
public var logSessionReplayScreen: XCTestExpectation?
public var logSessionReplayScreenExpectation: XCTestExpectation?

public var shouldLogAppUpdateEvent = false

Expand Down Expand Up @@ -115,10 +115,10 @@ extension MockCoreLogging: CoreLogging {
self.sessionReplayScreenLogs.append(SessionReplayScreenLog(
screen: screen, duration: duration)
)
self.logSessionReplayScreen?.fulfill()
self.logSessionReplayScreenExpectation?.fulfill()
}

public func logSessionReplayScreenshot(screen _: SessionReplayCapture, duration _: TimeInterval) {}
public func logSessionReplayScreenshot(screen _: SessionReplayCapture?, duration _: TimeInterval) {}

public func logResourceUtilization(fields: Fields, duration: TimeInterval) {
self.resourceUtilizationLogs.append(ResourceUtilizationLog(fields: fields, duration: duration))
Expand Down
Loading