Repository navigation
Description
Version
3.61.0 (via @posthog/react-native-plugin 2.5.1 / posthog-react-native 4.66.2)
Steps to Reproduce
- React Native 0.84 app (new architecture, release build) with
enableSessionReplay: true, defaultthrottleDelayMs(1000), on a physical Android 15+ device with an ARR panel (tested: Pixel 10 Pro XL, 120Hz LTPO,Display.hasArrSupport() == true). Emulators cannot reproduce this — the virtual HWC exposes one fixed 60Hz mode, so the frame-rate-override path never engages. - Scroll a content screen continuously (drags + flings) for ~80s. For a deterministic repro we record a real finger session once with
evemu-recordand replay it byte-identically withadb shell uinput(the kernel-path mechanism AOSP CTS uses;adb shell input swipeunder-triggers the ARR heuristics ~10× and won't reproduce). - Capture with Perfetto (
android.surfaceflinger.frametimelinedata source) and count jank types for the app's layer:
select jank_type, count(*) from actual_frame_timeline_slice
where layer_name like '%<your.app>%' group by 1 order by 2 desc;Expected Result
Scrolling stays smooth while replay records. At minimum, replay does not spend capture cost on frames it will discard: during a scroll, maskLegacyScreenshot's redraw guard rejects any capture that overlapped a non-animation redraw (unsafeRedraw), which a scroll redraw always is — so mid-gesture captures are pure cost with zero shipped frames.
Actual Result
Capture is scheduled off onDraw through the Throttler, which re-arms on hasPendingDraw — so during continuous scrolling, captures chain at exactly 1/throttleDelayMs for the entire gesture, by construction. Each capture is a full-window PixelCopy readback plus a ~16MB ARGB Bitmap + WebP encode. The burst stalls the app's frame delivery long enough that ViewRootImpl's intermittent-update detector (Android 15+) drops the frame-rate vote 120→60; when the cadence recovers it flips back. Every flip is a render-rate transition SurfaceFlinger must reschedule around, and the mis-scheduled frame stuffs the buffer queue — 200–950ms of late presentation per episode, felt as intermittent scroll stutter.
Measured (identical replayed input per arm, ~6,200 frames and ~106 render-rate switches per 80s run):
| Arm | Buffer Stuffing frames | SurfaceFlinger Scheduling janks |
|---|---|---|
| Replay recording, defaults | 651 | 33 |
| Same app, replay not recording | 0–45 (3 runs) | 0–2 |
| Replay recording + app-side "pause capture while a pointer is down" (5 runs) | median 75 | median 1 |
The third row is the proposed fix implemented app-side (stopSessionRecording() on pointer-down, startSessionRecording(true) on all-pointers-up + 1s fling debounce): jank returns to the no-replay baseline, and capture-thread activity confirms screenshots still fire between gestures — recordings stay intact because between-gesture frames are the only ones the redraw guard was keeping anyway.
Proposed fix (all hooks exist in PostHogReplayIntegration):
- Defer capture while a pointer is down (+ a short settle window for flings):
onTouchEventListeneralready sees every DOWN/MOVE/UP; gate the throttled snapshot lambda on a volatile pointer-down flag and fire the deferred capture when quiet. Same instinct as 3.17.0's "do not capture screenshot during screen changes", wider. - Micro-fix in the same listener: filter to DOWN/UP before
MotionEvent.obtain+executor.submit— currently every MOVE of a 120Hz scroll is copied and submitted just to be discarded on the executor. - The in-source TODO:
PixelCopy.Request.Builder.ofWindow+Executoron API 34+. - Reuse a persistent
Bitmapacross captures to remove the ~16MB/s allocation churn while scrolling.
Precedent: iOS has screenshotModeBackgroundCapture for exactly this symptom, and the RN docs recommend it verbatim for "janky or drops frames on high-refresh-rate (120Hz ProMotion) iPhones" (PostHog/posthog-js#3552) — Android has no equivalent. Related accepted capture-cost fixes: PostHog/posthog-ios#321, PostHog/posthog-ios#571, and this repo's 3.53.7/3.54.1/3.55.1 changelog entries. PostHog/posthog-js#3796 asks for RN wireframe mode partly for the same perf reason.
Happy to share full Perfetto traces, the replay harness, and our app-side pauser, or to re-run the A/B against a patched build.
Activity
dustinbyrne commentedon Sep 2, 2026
really appreciate the detailed report @LuisOsta! there's a bit to consider here - for example, we don't always discard screenshots when a redraw happens during capture; it depends on the replay configuration and the type and timing of the redraw. continuous animations could be a factor here too.
i don't have a clear answer off the top of my head, but we'll put some thought into this.
4shutosh commentedon Sep 2, 2026
We see repeated OOM crashes across several users, sessions, and devices. They mostly happen on media-heavy chat and preview screens, including devices with a large heap.
We ship Android SDK 3.58.0 and confirmed that the same allocation path remains in 3.61.0: a full-screen bitmap, a large output buffer, a byte-array copy, and Base64 output.
Could bitmap reuse, screenshot downscaling, and lower-memory encoding be included in this fix?
dustinbyrne commentedon Sep 2, 2026
somewhat related, yes. thanks for sharing @4shutosh.
i do think there's some improvements we can look at here, and i'm currently running some benchmarks. namely, using a smaller, single allocated buffer, GPU downscaling, a smaller pixel format for snapshot capture. it's likely this will improve memory performance, and ideally it also lets us hold on to the render thread for less time to avoid refresh rate throttling.
i'll update when i have more info.
LuisOsta commentedon Sep 2, 2026
@dustinbyrne Thank you for the quick response! I'm sure y'all get a lot of low signal issues nowadays so hopefully this wasn't a waste of y'alls time. I was able to mitigate this by essentially pausing Posthog session replay when a finger is on a screen, its subpar probably but for our product its good enough. The general effect of session replay on android on the smoothness of animations/scrolling/etc. was definitely noticeable, though only the scrolling was really aggressively noticeable. on an IPad the issue is noticeable but drammatically less so than on Android (you really need to be finely tuned on what smoothness/animations to expect to really notice)
LuisOsta commentedon Sep 2, 2026
No rush on this from my side as I've gotten a suboptimal but good enough patch that works for us but wanted to mention it here in case this is something that may have deeper effects/problems. Though a clean solution from the upstream is always appreciated
[-]Screenshot capture fires mid-scroll and janks 120Hz ARR panels — capture should defer while a pointer is down (the frames are discarded anyway)[/-][+]Screenshot capture fires mid-scroll and janks 120Hz ARR panels[/+]dustinbyrne commentedon Sep 7, 2026
#756 aims to improve both impact on GPU and memory. if anyone happens to give it a try before it lands, let us know how it goes!
dustinbyrne commentedon Sep 22, 2026
closing the loop on this - as of
posthog-androidv3.63.0+, the following experimental configuration options are available. i recommend trying them out for improved frame times and memory usage:via https://posthog.com/docs/session-replay/installation/android#configure-posthog-with-session-replay
closing this issue for now, but feel free to re-open or open a new issue if issues persist! thanks all! 🙌