Skip to content

fix(macos): drop video frames whose timestamp does not advance - #658

Merged
EtienneLescot merged 2 commits into
mainfrom
fix/macos-video-timestamp-gate
Sep 14, 2026
Merged

fix(macos): drop video frames whose timestamp does not advance#658
EtienneLescot merged 2 commits into
mainfrom
fix/macos-video-timestamp-gate

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

On macOS, pausing and resuming could end the recording. The helper then reported video append: … AVFoundationErrorDomain -11800 … (-16364). The writer died, capture carried on, and the take was lost at Stop.

Cause, measured.

  • What the error is. -16364 is raised only by MediaToolbox's MediaSampleTimingGenerator (disassembled from the macOS 26.5 dyld shared cache). It fires when a video sample's presentation time is not after the previous one.
  • How it plays out. The frame carrying the bad time is accepted, and the next append fails the whole writer. Every frame after that is dropped while ScreenCaptureKit keeps delivering. This is not the fragmentation bug fixed in fix(recording): stop macOS fragments carrying an offset the box cannot hold #375 (-16341).
  • Why a pause produces such a frame. The helper measures a pause on the host clock and shifts every later sample back by that length. But a ScreenCaptureKit frame's time runs a few ms ahead of the host clock, by a varying amount (median 4.8 ms, spread about 20 ms). When that lead drops, from the last frame before the pause to the first frame after it, by more than the delivery gaps on either side of the pause, the shifted frame lands behind the previous one. That happened in about 3 % of measured resumes, by 0.2–1.9 ms.
  • Not a capture-time issue. Every offending frame was captured after resume, so filtering on capture time would not catch them.

Fix.

  • VideoTimestampGate (OpenScreenCaptureCore, unit-tested) refuses a video frame that is invalid or not after the last frame handed to the writer.
    • It refuses frames until time moves forward again. Every overlap measured was under 2 ms, so in practice that is one frame; A/V sync is unchanged.
    • Audio is clocked by AudioTrackMixer, and the writer tolerates audio stepping back.
    • The first refusal emits a video-frame-timestamp-refused warning, with the timestamps and pause offset. finishWriter reports the total. Both are warnings, so Electron's start and stop waits ignore them.
  • Retiming failures. A sample whose retiming fails is now dropped instead of appended unshifted. An unshifted frame would sit a whole pause ahead, and the gate would refuse every correct frame for as long as the pause lasted.

Known limit. A frame is dropped rather than re-timed. If the refused frame carries the only screen change at resume and the screen then stays still, the video shows the pre-pause image until the next change. That needs a complete frame delivered in the ~20 ms before the pause, so it is narrow.

Related issue

Refs #621. It is the same silent-death shape, but that report says nothing about pausing, so whether this is its cause is unconfirmed.

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • Patch
  • Minor
  • Major / breaking change
  • No release note needed

Desktop impact

  • Windows
  • macOS
  • Linux
  • Installer / packaging
  • Not platform-specific

Screenshots / video

None. No UI change.

Testing

All on Mac mini M1, macOS 26.5, with real ScreenCaptureKit capture. A small window repainting at 120 Hz keeps the display producing complete frames.

Real helpers A/B at 3840×2160@60, 76.5 Mbps.

  • Settings: 200 pause/resume cycles (150 ms paused, 250 ms running), driven over stdin.
  • Control: the shipped 1.11.0-rc.1 helper. Its ScreenCaptureRecorder differs from main only in window exclusion and mic selection, not the video path.
Helper System audio Result
1.11.0-rc.1 (control) off writer-failed-during-capture at 7.0 s with the exact reported message; stop → writer-failed; file 4.0 s
this branch off recording-stopped; 13 frames refused; file 52.8 s; 3008 packets, DTS strictly increasing, PTS == DTS
1.11.0-rc.1 (control) on writer-failed-during-capture at 2.5 s, same message; file 1.0 s
this branch on recording-stopped; 8 frames refused; video 52.89 s, AAC 52.93 s (starts −44 ms, normal AAC priming); DTS strictly increasing on both tracks

Combined with #655:

  • This branch (first commit) merged with fix/issue-621-writer-death builds, and swift test passes.
  • Its release helper, run through the same 200 cycles at 4K60 with system audio, gives recording-stopped with 14 frames refused and a 52.3 s file with both tracks.

Timing probe (same SCK config, host-clock pause shift, no gate):

  • 1080p60, no writer: 6 frames not after the previous one, out of 200 cycles.
  • 4K60 with the helper's writer settings: 7 such frames, and the writer died at frame 623 with -11800/-16364.
  • SCK itself never delivered a non-increasing raw timestamp in about 10 000 complete frames.

Synthetic writer probe (320×180, the helper's writer settings):

  • Duplicate PTS, PTS 5 ms back, and PTS before session start → -16364 on the following append.
  • Size, pixel format and colour changes → accepted.
  • Audio stepping back → accepted.

Unit tests: swift test --package-path electron/native/screencapturekit passes, 39 tests including 8 new ones. One of the new tests covers two frames in a row being refused.

Not tested: a real microphone (this Mac has none), Intel Macs, macOS versions other than 26.5.

Summary by CodeRabbit

  • Bug Fixes
    • Improved video recording reliability by rejecting invalid, duplicate, or out-of-order video frames.
    • Prevented improperly retimed frames from disrupting the recording timeline.
    • Added warning events when frames are refused, including a summary when multiple frames are dropped.

A pause/resume could end the recording. The helper shifts every sample after
a resume back by the pause measured on the host clock, but a ScreenCaptureKit
frame's presentation time runs a variable few milliseconds ahead of that clock
(median 4.8 ms, spread ~20 ms). When the last frame before the pause led by
more than the first frame after it, the shifted frame landed just behind the
previous one, and AVAssetWriter rejects that in MediaToolbox's
MediaSampleTimingGenerator with OSStatus -16364. The writer as a whole fails,
one append later, as AVFoundationErrorDomain -11800 wrapping -16364: the
"video append" toast. Every later frame is dropped while capture carries on.

Measured on real ScreenCaptureKit, M1, macOS 26.5, 200 pause/resume cycles
(150 ms paused, 250 ms running): 6 shifted frames behind the previous one at
1080p60 (by 0.2-1.9 ms), 7 at 4K60, and with the helper's writer settings the
4K run died at frame 623 with the exact reported error. Every such frame was
captured after resume, so gating on capture time would not catch it.

VideoTimestampGate refuses a video frame that is invalid or not after the
last one handed to the writer. The overlap is always under one frame
interval, so dropping that one frame is the whole fix for this case; audio
is clocked by AudioTrackMixer and the writer accepts audio that steps back.
The first refusal emits a warning with the timestamps and the pause offset,
and finishWriter reports the total.

A sample whose retiming fails is now dropped instead of appended unshifted:
that frame would sit a whole pause ahead and the gate would then refuse
every correctly shifted frame for as long as the pause lasted.
…ones

Review of the gate pointed out two claims the measurements do not carry.

"The overlap is always under one frame interval" was a sample of 13 events
(0.2-1.9 ms), not a bound: the timestamp lead spreads about 20 ms, more than a
60 fps frame. The gate never relied on it -- it refuses until time moves past
the last frame the writer received -- so this pins that with a test where two
consecutive frames are refused and the third is admitted.

"When the last frame before a pause led by more than the first after it" is
necessary, not sufficient: the lead has to drop by more than the delivery gaps
on either side of the pause, which is why about 3% of resumes trip it rather
than half. The warning's doc now tells the two causes apart by pause offset
instead of by an overlap size nothing bounds.
@coderabbitai

coderabbitai Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: ac24e46d-73a4-45e0-b0c4-db42e61a3c3e

📥 Commits

Reviewing files that changed from the base of the PR and between fedf576 and 6d456b4.

📒 Files selected for processing (3)
  • electron/native/screencapturekit/Sources/OpenScreenCaptureCore/VideoTimestampGate.swift
  • electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift
  • electron/native/screencapturekit/Tests/OpenScreenCaptureCoreTests/VideoTimestampGateTests.swift

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

Changes

Video timestamp gating

Layer / File(s) Summary
Timestamp gate contract and validation
electron/native/screencapturekit/Sources/OpenScreenCaptureCore/VideoTimestampGate.swift
Adds VideoTimestampGate, its verdict cases, recorded timestamp state, and rejection counting. Invalid or non-increasing timestamps are refused.
Recorder admission and refusal reporting
electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift
Gates video frames before writing, records only appended timestamps, reports refusals, summarizes multiple refusals, and drops samples when retiming fails.
Timestamp gate behavior tests
electron/native/screencapturekit/Tests/OpenScreenCaptureCoreTests/VideoTimestampGateTests.swift
Tests increasing, overlapping, duplicate, differently scaled, invalid, and non-mutating timestamp checks.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant ScreenCaptureRecorder
  participant VideoTimestampGate
  participant Writer
  participant WarningReporter
  ScreenCaptureRecorder->>VideoTimestampGate: check presentation timestamp
  VideoTimestampGate-->>ScreenCaptureRecorder: admission verdict
  ScreenCaptureRecorder->>Writer: append admitted frame
  ScreenCaptureRecorder->>VideoTimestampGate: record appended timestamp
  ScreenCaptureRecorder->>WarningReporter: report refused timestamps
Loading

Merge Risk: ⚪ Minimal · up to 6d456

The macOS recording fix validates and drops invalid video timestamps before they reach the writer, with coverage for refusal and recovery behavior. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main macOS fix: dropping video frames whose timestamps do not advance.
Description check ✅ Passed The description follows the repository template and provides a detailed summary, related issue references, change classification, release and platform impact, screenshot status, testing results, and k…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/macos-video-timestamp-gate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot
EtienneLescot merged commit 2134b6b into main Sep 14, 2026
19 checks passed
@EtienneLescot
EtienneLescot deleted the fix/macos-video-timestamp-gate branch September 14, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant