Skip to content

fix: diagnose split-export stalls and serialize encoder-heavy exports#176

Merged
hm21 merged 3 commits into
stablefrom
fix/split-export-stall-diagnostics
Jul 11, 2026
Merged

fix: diagnose split-export stalls and serialize encoder-heavy exports#176
hm21 merged 3 commits into
stablefrom
fix/split-export-stall-diagnostics

Conversation

@hm21

@hm21 hm21 commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Problem

A frame-accurate clip split (splitVideo) could hang until the fixed 120 s watchdog and fail with a generic message:

[ProVideoEditor] ❌ Split failed: Split export timed out after 120s
→ PlatformException(SPLIT_ERROR, Split export timed out after 120s)

No context on which half, what progress, or why — so the failure was un-actionable. Reported on iOS (26.5, release); the affected clip was the product of ~5 sequential splits of the same source, with parallel speed-renders running over the same export path.

What I measured (iPad + macOS, on-device)

The split feature itself is correct (all integration tests pass). The timings tell the real story — same SplitVideo.swift on iOS/macOS:

Asset 1 split alone Nature
720p H264 ~0.37 s AVFoundation smart-trim (near-instant)
1080p HEVC ~0.83 s smart-trim
4K H264 (31 s) ~19 s genuine full re-encode
4K H264 (6 s) ~1.4 s genuine re-encode

Concurrency contention (macOS, 3× concurrent 4K):

baseline 3× concurrent (each)
before 19 s 43 / 44 / 43 s
after (gate) 14 s 28 / 35 / 42 s (serialized)

So the root cause is genuine encoder-heavy work starved by contention: concurrent split + render encodes fight over the one hardware encoder, each ~2–3× slower, and on a throttled device a starved session sits at progress == 0 until the watchdog.

Changes

  1. Diagnostic timeouts (iOS, macOS, Android). Stall and hard-timeout failures now carry identical, parseable context, e.g.
    Split export stalled after 12s with no progress [half=end progress=0.00 segment=0.52s split=0.53s total=1.05s preset=… audio=false]
    (preset on Darwin, mime on Android).
  2. Stall detection. If an export makes no forward progress for stallTimeout (default 12 s) it is force-cancelled early, instead of waiting out the full 120 s. The hard exportTimeout (default 120 s) remains the outer bound.
  3. Configurable timeouts on SplitVideoModel (exportTimeout, stallTimeout). Defaults preserve prior behavior; successful splits are unaffected.
  4. ExportGate (iOS/macOS). A process-wide gate serializes encoder-heavy exports (split halves + renders) so they no longer starve each other. The queue wait happens before the per-export watchdog starts, so it never counts as a stall. Passthrough/stream-copy exports stay ungated.
  5. Tests. Dart method-channel param tests, Android SplitExportDiagnostics unit tests, a stacked-re-encode integration regression probe, and a "healthy split honors custom timeouts" integration test.
  6. Example build fix. example/ios deployment target 12.1 → 15.0 (it was inconsistent with the 15.0 Podfile and broke the SPM build against file_picker, which now requires 14.0).

Verification

  • Dart: flutter test (255 tests) + flutter analyze clean.
  • Android: :pro_video_editor:testDebugUnitTest green (incl. new diagnostics tests); module compiles.
  • iOS + macOS: full split_video_test.dart integration suite green on a connected iPad and on macOS with the gate, including cancel-mid-split.
  • Gate A/B measured before/after (tables above); serialization confirmed via a clean staircase of completion times.

Honest limitations

  • I could not reproduce the literal 120 s freeze on the available hardware. Even 12× concurrent 4K splits on the iPad only slowed each to ~22 s (no stall) — the iPad's encoder is strong and schedules fairly. The freeze needs a weaker/throttled iPhone (users' devices). So the gate's freeze-cure is inferred (it bounds each session's watchdog exposure to its solo time), not directly demonstrated as freeze→cured.
  • At very high concurrency the gate does not reduce total wall time (throughput-bound); its benefit is bounding per-session watchdog exposure. A large serial batch on a slow device can still take a while (but completes rather than tripping the watchdog).
  • The diagnostics + stall detection are the directly-useful, low-risk win: they turn any future freeze into a fast, labeled failure and will confirm on a real user device whether it is starvation-at-0% (half=…, progress=0.00).

Follow-ups

  • Mirror ExportGate on Android (Media3 Transformer) for parity — to be verified on a physical Android device.
  • If the diagnostics confirm starvation-at-0% on a real user device, the gate is validated; if they point elsewhere, fix that instead.

hm21 added 3 commits July 11, 2026 13:26
Frame-accurate clip splits could hang until the fixed 120s watchdog with no
context on why. Measured on a connected iPad + macOS: the split does a
genuine multi-second re-encode for high-res clips (~19s for a 31s 4K clip,
~1.4s for a 6s 4K clip), and concurrent split + render encodes starve each
other on the shared hardware encoder (3x concurrent 4K: ~43s each vs ~19s
alone; 12x: up to ~22s each). On a throttled device a starved session can
sit at 0% until the watchdog — the reported split "freeze".

- Diagnostic timeouts (iOS, macOS, Android): the stall (no forward progress
  for stallTimeout, default 12s) and hard (exportTimeout, default 120s)
  failures now carry context - which half, last progress, segment/split/total
  durations, preset/mime, audio - with identical message structure across
  platforms.
- exportTimeout / stallTimeout are configurable on SplitVideoModel; defaults
  preserve the previous behavior and successful splits are unaffected.
- iOS, macOS: a process-wide ExportGate serializes encoder-heavy exports
  (split halves + renders) so they no longer starve each other; the queue
  wait happens before the per-export watchdog starts. Passthrough/stream-copy
  exports (no encoder) stay ungated.
- Tests: Dart method-channel param tests, Android SplitExportDiagnostics unit
  tests, a stacked-re-encode integration regression probe, and an integration
  test that a healthy split honors custom timeouts.

Also raises the example iOS deployment target 12.1 -> 15.0 (it was
inconsistent with the 15.0 Podfile and broke the SPM build against
file_picker, which now requires 14.0).
@hm21 hm21 merged commit 3908543 into stable Jul 11, 2026
1 check passed
@hm21 hm21 deleted the fix/split-export-stall-diagnostics branch July 11, 2026 11:44
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