Skip to content

feat(session-replay): add preferred frame rate to display link wrapper #5258

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

Closed
wants to merge 3 commits into from
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
38 changes: 37 additions & 1 deletion SentryTestUtils/TestDisplayLinkWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,32 @@ public enum FrameRate: UInt64 {
}
}

public struct FrameRateRange {
public let minimum: Float
public let maximum: Float
public let preferred: Float?

public init(minimum: Float, maximum: Float, preferred: Float?) {
self.minimum = minimum
self.maximum = maximum
self.preferred = preferred
}

@available(iOS 15.0, tvOS 15.0, *)
fileprivate static func from(_ range: CAFrameRateRange) -> FrameRateRange {
return FrameRateRange(
minimum: range.minimum,
maximum: range.maximum,
preferred: range.preferred
)
}
}

public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper {
public var target: AnyObject!
public var selector: Selector!
public var preferredFrameRateRange: FrameRateRange?
public var preferredFramesPerSecond: Int?
public var currentFrameRate: FrameRate = .low

private let frozenFrameThreshold = 0.7
Expand All @@ -43,15 +66,28 @@ public class TestDisplayLinkWrapper: SentryDisplayLinkWrapper {

public var ignoreLinkInvocations = false
public var linkInvocations = Invocations<Void>()
public override func link(withTarget target: Any, selector sel: Selector) {

@available(iOS 15.0, tvOS 15.0, *)
public override func link( withTarget target: Any, selector sel: Selector, preferredFrameRateRange: CAFrameRateRange) {
if ignoreLinkInvocations == false {
linkInvocations.record(Void())
self.target = target as AnyObject
self.selector = sel
self.preferredFrameRateRange = FrameRateRange.from(preferredFrameRateRange)
_isRunning = true
}
}

public override func link(withTarget target: Any, selector sel: Selector, preferredFramesPerSecond fps: Int) {
if ignoreLinkInvocations == false {
linkInvocations.record(Void())
self.target = target as AnyObject
self.selector = sel
self.preferredFramesPerSecond = fps
_isRunning = true
}
}

public override var timestamp: CFTimeInterval {
return dateProvider.systemTime().toTimeInterval()
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/Sentry/SentryFramesTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@

_isRunning = YES;

[_displayLinkWrapper linkWithTarget:self selector:@selector(displayLinkCallback)];
// Set the display link to use the default frame rate of 60 fps.
[_displayLinkWrapper linkWithTarget:self
selector:@selector(displayLinkCallback)
preferredFramesPerSecond:-1];

Check warning on line 174 in Sources/Sentry/SentryFramesTracker.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/SentryFramesTracker.m#L172-L174

Added lines #L172 - L174 were not covered by tests
}

- (void)displayLinkCallback
Expand Down
22 changes: 21 additions & 1 deletion Sources/Sentry/include/SentryDisplayLinkWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#if SENTRY_HAS_UIKIT

# import <QuartzCore/QuartzCore.h>

NS_ASSUME_NONNULL_BEGIN

/**
Expand All @@ -13,7 +15,25 @@ NS_ASSUME_NONNULL_BEGIN

@property (readonly, nonatomic) CFTimeInterval targetTimestamp API_AVAILABLE(ios(10.0), tvos(10.0));

- (void)linkWithTarget:(id)target selector:(SEL)sel;
/**
* Link the display link to the target and selector with the preferred frames per second.
* @param target The target of the selector.
* @param sel The selector to call on the target.
* @param fps The preferred frames per second. Setting to `-1` will use the default frames per
* second.
*/
- (void)linkWithTarget:(id)target selector:(SEL)sel preferredFramesPerSecond:(NSInteger)fps;

/**
* Link the display link to the target and selector with the preferred frame rate range.
* @param target The target of the selector.
* @param sel The selector to call on the target.
* @param preferredFrameRateRange The preferred frame rate range. Use `CAFrameRateRangeDefault` to
* use the default frame rate range.
*/
- (void)linkWithTarget:(id)target
selector:(SEL)sel
preferredFrameRateRange:(CAFrameRateRange)preferredFrameRateRange API_AVAILABLE(ios(15.0));

- (void)invalidate;

Expand Down
16 changes: 15 additions & 1 deletion Sources/Sentry/include/SentryDisplayLinkWrapper.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,23 @@
return displayLink.targetTimestamp;
}

- (void)linkWithTarget:(id)target selector:(SEL)sel
- (void)linkWithTarget:(id)target
selector:(SEL)sel
preferredFramesPerSecond:(NSInteger)preferredFramesPerSecond
{
displayLink = [CADisplayLink displayLinkWithTarget:target selector:sel];
if (preferredFramesPerSecond >= 0) {
displayLink.preferredFramesPerSecond = preferredFramesPerSecond;

Check warning on line 27 in Sources/Sentry/include/SentryDisplayLinkWrapper.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/include/SentryDisplayLinkWrapper.m#L26-L27

Added lines #L26 - L27 were not covered by tests
}
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

Check warning on line 29 in Sources/Sentry/include/SentryDisplayLinkWrapper.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/include/SentryDisplayLinkWrapper.m#L29

Added line #L29 was not covered by tests
}

- (void)linkWithTarget:(id)target
selector:(SEL)sel
preferredFrameRateRange:(CAFrameRateRange)preferredFrameRateRange API_AVAILABLE(ios(15.0))
{
displayLink = [CADisplayLink displayLinkWithTarget:target selector:sel];
displayLink.preferredFrameRateRange = preferredFrameRateRange;

Check warning on line 37 in Sources/Sentry/include/SentryDisplayLinkWrapper.m

View check run for this annotation

Codecov / codecov/patch

Sources/Sentry/include/SentryDisplayLinkWrapper.m#L35-L37

Added lines #L35 - L37 were not covered by tests
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,20 @@
self.replayMaker = replayMaker
self.breadcrumbConverter = breadcrumbConverter
self.touchTracker = touchTracker
super.init()

Check warning on line 59 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L59

Added line #L59 was not covered by tests
}

deinit { displayLink.invalidate() }
deinit {
disconnectDisplayLink()

Check warning on line 63 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L62-L63

Added lines #L62 - L63 were not covered by tests
}

func start(rootView: UIView, fullSession: Bool) {
SentryLog.debug("[Session Replay] Starting session replay with full session: \(fullSession)")
guard !isRunning else {
SentryLog.debug("[Session Replay] Session replay is already running, not starting again")
return
}
displayLink.link(withTarget: self, selector: #selector(newFrame(_:)))
connectDisplayLink()

Check warning on line 72 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L72

Added line #L72 was not covered by tests
self.rootView = rootView
lastScreenShot = dateProvider.date()
videoSegmentStart = nil
Expand Down Expand Up @@ -101,7 +104,7 @@
lock.lock()
defer { lock.unlock() }

displayLink.invalidate()
disconnectDisplayLink()

Check warning on line 107 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L107

Added line #L107 was not covered by tests
if isFullSession {
prepareSegmentUntil(date: dateProvider.date())
}
Expand All @@ -128,7 +131,7 @@
}

videoSegmentStart = nil
displayLink.link(withTarget: self, selector: #selector(newFrame(_:)))
connectDisplayLink()

Check warning on line 134 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L134

Added line #L134 was not covered by tests
}

func captureReplayFor(event: Event) {
Expand Down Expand Up @@ -194,7 +197,7 @@
}

@objc
private func newFrame(_ sender: CADisplayLink) {
private func displayLinkDidUpdateAction(_ sender: CADisplayLink) {

Check warning on line 200 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L200

Added line #L200 was not covered by tests
guard let lastScreenShot = lastScreenShot, isRunning &&
!(isFullSession && isSessionPaused) //If replay is in session mode but it is paused we dont take screenshots
else { return }
Expand Down Expand Up @@ -362,7 +365,22 @@
replayMaker.addFrameAsync(image: image, forScreen: screen)
}
}

// - MARK: - Display Link

func connectDisplayLink() {
if #available(iOS 15.0, tvOS 15.0, *) {
let preferredFrameRateRange = CAFrameRateRange(minimum: Float(replayOptions.frameRate), maximum: Float(replayOptions.frameRate))
displayLink.link(withTarget: self, selector: #selector(displayLinkDidUpdateAction(_:)), preferredFrameRateRange: preferredFrameRateRange)
} else {
let preferredFramesPerSecond = Int(replayOptions.frameRate)
displayLink.link(withTarget: self, selector: #selector(displayLinkDidUpdateAction(_:)), preferredFramesPerSecond: preferredFramesPerSecond)

Check warning on line 377 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L371-L377

Added lines #L371 - L377 were not covered by tests
}
}

func disconnectDisplayLink() {
displayLink.invalidate()

Check warning on line 382 in Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift

View check run for this annotation

Codecov / codecov/patch

Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift#L381-L382

Added lines #L381 - L382 were not covered by tests
}
}
// swiftlint:enable type_body_length

#endif
Loading