Repository navigation
fix(replay): Keep flushed video and segment id when stop follows flush - #6340
Merged
Merged
Conversation
📲 Install BuildsAndroid
|
createVideoOf deleted the existing segment video before checking for captured frames, so an immediate stop() after flush() in buffer mode removed the flushed video before the transport read it, and segment 0 was sent without video. The segment-sent callback that advances the converted session strategy hopped to the main thread and was dropped when stop() ran first, so the session reused the flushed segment id for its stop segment and overwrote the flushed video. Advance it on the replay executor the strategies share instead, which orders it before any later segment of the session. Fixes #6247 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
romtsn
force-pushed
the
fix/replay-keep-video-on-empty-frames
branch
from
October 21, 2026 10:14
faa7970 to
fb21910
Compare
The regression test for #6247 pushed ReplayCacheTest over the LargeClass threshold, like ReplayIntegrationTest already is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
romtsn
marked this pull request as ready for review
October 21, 2026 10:57
romtsn
requested review from
0xadam-brown,
adinauer,
markushi and
runningcode
as code owners
October 21, 2026 10:57
This was referenced Oct 21, 2026
markushi
approved these changes
Oct 21, 2026
4 of 9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
Calling
stop()right afterflush()in buffer mode no longer loses the flushed replay video, and no longer sends two segments with the samesegment_id.The flush encodes segment 0 on the replay executor, then the buffer strategy is converted to a session strategy that starts at segment 0. When the flushed segment is done, a callback advances the session strategy to segment 1. Since #5965 that callback hopped to the main thread and was dropped if the replay was no longer recording. A
stop()queued right afterflush()always runs first, and its stop segment is already queued on the executor behind the flush. So the session created its stop segment as segment 0 again, at the same0.mp4path:ReplayCache.createVideoOfdeleted the existing0.mp4before it checked for frames, so segment 0 was sent with no video.onScreenshotRecorded(File, …)): the stop segment re-encoded0.mp4with the session frames, so segment 0 carried the wrong video and a secondsegment_id=0envelope followed with no video.Two changes:
ReplayIntegration.captureCurrentReplayconverts the strategy before submitting the capture, and the callback advancescurrentSegment,segmentTimestampandisFlusheddirectly on the replay executor, which both strategies share. Every othercurrentSegmentwrite already runs there, and the session reads it when its segment task runs, so the update always lands before the next session segment. The callback updates only the converted strategy object it captured, so a stopped or restarted replay is not affected (stale capture callback cannot mutate restarted replaystill passes).ReplayCache.createVideoOfdeletes an existing video only after it knows it has frames to encode, so a call with no frames never removes a video that is still waiting for the transport.Review focus:
convert()now runs before the buffer capture is submitted instead of after. Both are synchronous on the main thread, and the synchronous part ofBufferCaptureStrategy.captureReplayreads only buffer-owned state, so the order of executor work does not change.💡 Motivation and Context
💚 How did you test it?
Unit tests in
ReplayIntegrationTestandReplayCacheTest; the integration test fails without the fix.On an API 34 emulator with the sample app (session sample rate 0, PixelCopy recorder) and a local mock ingest server that reads each replay envelope:
mainstartBuffering(), 5 s, thenflush()+stop()segment_id=0, 0 video bytessegment_id=0, 12,244 bytesonScreenshotRecorded(File, …)beforestop()segment_id=0envelopes: 4,423 bytes (session frames), then 0 bytessegment_id=014,350 bytes,segment_id=14,423 bytesThe
ReplayCachechange alone fixes the first row but not the second.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
Related issues found while verifying, to be tracked separately:
finalizePreviousReplay→cleanupReplays) can delete the folder of a replay that started and stopped before the task ran, including videos still waiting for the transport.CanvasStrategy.close()can recycle the screenshot bitmap while aPixelCopyrequest still writes into it, which aborts the RenderThread.🤖 Generated with Claude Code