Skip to content

Stabilize video export on Windows#210

Merged
siddharthvaddem merged 2 commits intosiddharthvaddem:mainfrom
linyqh:codex/exporter-timeout-fallback
Mar 20, 2026
Merged

Stabilize video export on Windows#210
siddharthvaddem merged 2 commits intosiddharthvaddem:mainfrom
linyqh:codex/exporter-timeout-fallback

Conversation

@linyqh
Copy link
Contributor

@linyqh linyqh commented Mar 14, 2026

What changed

This PR improves export stability, especially on Windows, by adding timeout protection and safer encoder fallback behavior.

Key updates

  • add source-loading timeouts in streamingDecoder

    • protect fetch, blob reading, demuxer load, and metadata parsing
    • surface clearer timeout errors when the source video cannot be loaded or parsed in time
  • harden the video export pipeline in videoExporter

    • split export flow into encoder-preference attempts
    • retry with a safer encoder preference when an export attempt fails
    • track encoder output activity and fail fast when the encoder stalls
    • add timeout protection when flushing the encoder
    • propagate fatal encoder errors immediately instead of letting export hang
  • improve Windows export behavior

    • prefer software encoding first on Windows to reduce instability
    • cap the encode queue more aggressively for software encoding to avoid memory growth
    • keep hardware/software fallback logic explicit and easier to reason about
  • small cleanup/refactor

    • centralize progress reporting
    • reset encoder state more consistently during cleanup

Files changed

  • src/lib/exporter/streamingDecoder.ts
  • src/lib/exporter/videoExporter.ts

Why

This round mainly addresses export hangs, long waits, and encoder stall issues during video export, with extra attention to Windows stability and clearer failure paths.

Validation

  • git diff --check passed
  • npm run lint and npm test could not be completed locally because dependencies were not in a clean usable state
  • npm ci --cache .npm-cache --foreground-scripts hit EBUSY on node_modules/electron, and the current environment also showed Node engine warnings

Merge request

@author This change is ready for review. If it looks good, please help merge it so the Windows export stability fixes can ship soon. Thanks!

Summary by CodeRabbit

  • Bug Fixes

    • Added timeouts to source loading, metadata parsing and encoder flushing to prevent hangs.
    • Improved stall detection and clearer error handling so exports fail fast and respond to cancellation.
  • New Features

    • Multi-attempt export that automatically falls back between encoder preferences for greater reliability.
    • Per-attempt progress reporting and more robust retry behavior when encoders become unresponsive.

@siddharthvaddem
Copy link
Owner

siddharthvaddem commented Mar 17, 2026

@codex
@coderabbitai

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

@siddharthvaddem Sure! I'll review the changes in this PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5e0e3a7-c34a-4639-8327-f78e6d77d799

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2d7e7 and 459b71f.

📒 Files selected for processing (1)
  • src/lib/exporter/videoExporter.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/exporter/videoExporter.ts

📝 Walkthrough

Walkthrough

Added bounded timeout handling to streaming source loading and metadata parsing; reworked video export into per-encoder-preference attempts with encoder initialization per-attempt, stall detection, timed flushes, fatal-error propagation, and centralized progress reporting to enable retries across encoder preferences.

Changes

Cohort / File(s) Summary
Streaming decoder
src/lib/exporter/streamingDecoder.ts
Added SOURCE_LOAD_TIMEOUT_MS and private withTimeout helper; wrapped source read (fetch, electron read, response.blob()) and demuxer operations (load, getMediaInfo) with timeouts and descriptive errors.
Video exporter / encoder flow
src/lib/exporter/videoExporter.ts
Added encoder timeout constants and state (lastEncoderOutputAt, fatalEncoderError); split export into multi-attempt loop over getEncoderPreferences() using exportWithEncoderPreference; added initializeEncoder(hardwareAcceleration), stall detection (based on encode queue and lastEncoderOutputAt), withTimeout for encoder.flush(), encoder error handling that marks fatal errors and cancels decoders, per-attempt cleanup and progress reporting.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StreamingDecoder
  participant FrameRenderer
  participant VideoEncoder
  participant Muxer

  Client->>StreamingDecoder: loadSourceFile() with timeout
  StreamingDecoder->>StreamingDecoder: parse metadata (with timeout)
  Client->>Client: getEncoderPreferences()
  loop for each encoderPreference
    Client->>VideoEncoder: initializeEncoder(hardwareAcceleration)
    Client->>FrameRenderer: create renderer
    loop per frame
      StreamingDecoder->>Client: decoded frame
      Client->>FrameRenderer: render(frame)
      FrameRenderer->>VideoEncoder: encode(renderedFrame)
      VideoEncoder-->>Client: output callback (update lastEncoderOutputAt)
      VideoEncoder->>Muxer: push encoded chunk
      Muxer->>Client: segment muxed (progress)
      alt encode queue > threshold for long time
        Client->>Client: detect stall (compare lastEncoderOutputAt)
        Client->>VideoEncoder: abort/close encoder
        Client-->>Client: throw to try next preference
      end
    end
    Client->>VideoEncoder: flush() with timeout
    VideoEncoder->>Muxer: final segments
    Client->>Muxer: finalize
    Muxer->>Client: completed
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

"I hopped through bytes and timed each load,
retried encoders down the winding road.
When frames would stall I gave a little thump,
flushed with a timer and finished the jump.
— a joyful rabbit 🐇"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary objective of the changeset—stabilizing video export behavior on Windows through timeout protection and encoder fallback logic.
Description check ✅ Passed The description covers the key changes, motivation (export hangs and encoder stalls), affected files, and validation efforts, though it does not follow the provided template structure with explicit sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib/exporter/videoExporter.ts (1)

413-427: Consider extracting withTimeout to a shared utility.

This implementation is identical to the one in streamingDecoder.ts. Extracting to a shared utility (e.g., src/lib/utils/withTimeout.ts) would reduce duplication and ensure consistent behavior.

♻️ Proposed shared utility

Create a new file src/lib/utils/withTimeout.ts:

export function withTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number,
  message: string,
): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const timer = window.setTimeout(() => reject(new Error(message)), timeoutMs);
    promise.then(
      (value) => {
        window.clearTimeout(timer);
        resolve(value);
      },
      (error) => {
        window.clearTimeout(timer);
        reject(error);
      },
    );
  });
}

Then import and use in both files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/exporter/videoExporter.ts` around lines 413 - 427, Extract the
duplicate withTimeout<T> implementation into a single shared utility function
named withTimeout and export it from a new module, then replace the local
implementations in videoExporter.ts (method withTimeout) and streamingDecoder.ts
with imports of that shared withTimeout to remove duplication and ensure
consistent behavior; keep the same signature (promise: Promise<T>, timeoutMs:
number, message: string) and the same timer/clear logic so callers (e.g., the
withTimeout call sites in VideoExporter and StreamingDecoder) continue to work
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/lib/exporter/videoExporter.ts`:
- Around line 413-427: Extract the duplicate withTimeout<T> implementation into
a single shared utility function named withTimeout and export it from a new
module, then replace the local implementations in videoExporter.ts (method
withTimeout) and streamingDecoder.ts with imports of that shared withTimeout to
remove duplication and ensure consistent behavior; keep the same signature
(promise: Promise<T>, timeoutMs: number, message: string) and the same
timer/clear logic so callers (e.g., the withTimeout call sites in VideoExporter
and StreamingDecoder) continue to work unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 851ef0a9-eb04-4211-a61a-f48f3295f45e

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8bb99 and 3726ed7.

📒 Files selected for processing (2)
  • src/lib/exporter/streamingDecoder.ts
  • src/lib/exporter/videoExporter.ts

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3726ed73c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@siddharthvaddem
Copy link
Owner

Looks reasonable to me. Have you tested it thoroughly with exporting?

@linyqh
Copy link
Contributor Author

linyqh commented Mar 18, 2026

Looks reasonable to me. Have you tested it thoroughly with exporting?

Yes, I have. Initially, I found that it would get stuck on the export screen when running on Windows 11, making it unusable. After fixing the bug, I've verified that the export process now works correctly.

@siddharthvaddem
Copy link
Owner

can we have the conflicts resolved?

@siddharthvaddem
Copy link
Owner

and also the linting is failing

@linyqh linyqh force-pushed the codex/exporter-timeout-fallback branch from 3726ed7 to 2a2d7e7 Compare March 19, 2026 16:06
@siddharthvaddem siddharthvaddem merged commit dd0b7d6 into siddharthvaddem:main Mar 20, 2026
5 checks passed
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.

2 participants