Skip to content

feat: add startTime/endTime to compressVideo for single-pass trim + compress - #13

Merged
thangdevalone merged 5 commits into
thangdevalone:mainfrom
FrikkieSnyman:feat/compress-video-trim-range
Oct 15, 2026
Merged

thangdevalone merged 5 commits into
thangdevalone:mainfrom
FrikkieSnyman:feat/compress-video-trim-range

Conversation

@FrikkieSnyman

@FrikkieSnyman FrikkieSnyman commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Closes #12.

compressVideo accepts optional startTime / endTime bounds in milliseconds, so a selected range can be trimmed and compressed in one encode instead of creating a re-encoded intermediate.

The Android MediaItem and both iOS export paths use the selected range, and size budgets use its duration. Bounds remain optional through the native bridge: omitted values default to the source start/end; supplied values must be finite and satisfy 0 <= startTime < endTime <= duration, spanning at least one millisecond. Invalid ranges reject before starting an export or building Android's clipping configuration.

Trimmed outputs are never replaced with the full source when encoding increases their size. Both iOS pipelines use the same tested fallback helper, including the explicit-bitrate reader/writer pipeline. Existing fallback behavior remains available for untrimmed, unmuted exports.

The Nitro bridge files and API documentation are updated. This branch includes the merged software-encoder fallback from #14.

Validation:

  • TypeScript passes; Nitrogen 0.35.5 regeneration matches the committed bridge files after normalizing line endings.
  • A shared fixture covers 25 range cases on Android/JUnit and Swift, including omitted and one-sided bounds, explicit zero, reversed/empty/out-of-bounds ranges, and non-finite values.
  • Five Swift file-content regressions verify that trimmed and muted exports retain their output bytes, while an untrimmed, unmuted export can still use the smaller source.
  • Android runs the range regressions as part of the example build. The iOS CI job runs the production Foundation helpers and fallback regressions before building the app.
  • Turbo inputs now include the library's native source, generated bridges, podspec, and Android regression fixtures, so native changes invalidate the build cache.
  • Package inspection confirms the native helpers are included and test sources/fixtures are excluded from the published package.
  • Lint diagnostics match the existing main baseline (278 formatting errors and 122 warnings when line endings are normalized).

The library, iOS, and Android builds all passed for commit 54b9b03cb851c582098efd1ad2bd0cfe014cb04d: https://github.com/thangdevalone/react-native-media-toolkit/actions/runs/38731915633. Logs confirm native build cache misses, execution of the Android unit-test task, and all 30 Swift regression cases passing.

Device codec end-to-end tests have not been run locally; the regression coverage above checks range validation and actual fallback file contents, and CI builds the complete native implementations.

…ompress

Trim-then-compress runs two full encodes on Android, the first at the
source resolution (Media3 transcodes any clip that does not start on a
keyframe). A range on compressVideo clips inside the same transform:
ClippingConfiguration on Android, timeRange on the AVAssetExportSession
and AVAssetReader on iOS. The size budget uses the trimmed duration, the
smaller-than-original check and the inflation fallback are skipped for
a range, and an empty range rejects before a session starts.

Closes thangdevalone#12
@thangdevalone

Copy link
Copy Markdown
Owner

Thanks for the contribution! The single-pass approach makes sense. Before updating this PR, I want to explain two correctness issues found during review and the changes I plan to make.

  1. Keep the selected range when the explicit-bitrate iOS export grows in size. compressWithBitrate still has an anti-inflation fallback that copies the entire source file over the encoded output. With a trim range, an explicit bitrate high enough for the output to exceed the source size, and muteAudio: false, this can return the full original video, including content outside the selected range. The export-session path already skips this fallback for trimmed exports. I plan to apply the same timeRange == nil guard to the reader/writer path. Returning a larger trimmed clip preserves the requested content; replacing it with the full source does not.

  2. Preserve omitted options and validate supplied bounds before starting an export. The bridge currently converts missing values to 0, while the native implementations use positive values to detect a trim request. This makes { startTime: 0, endTime: 0 } behave like an untrimmed export, ignores some negative values, and silently clamps an end beyond the asset duration. Issue Feature: optional startTime/endTime on compressVideo so trim + compress is a single encode #12 explicitly asks for invalid ranges to be rejected. I plan to preserve undefined as native nil/null, default only omitted bounds to the start/end of the asset, and require finite values satisfying 0 <= startTime < endTime <= duration. Android validation also needs to run before building the clipping configuration so reversed ranges reject consistently as invalid input.

The intended behavior is unchanged when neither bound is supplied, and one-sided ranges remain supported. I will add regression coverage for omitted, empty, negative, reversed, out-of-bounds, and non-finite ranges, and rerun the checks after the update. The trimmed explicit-bitrate iOS path also needs verification that the output keeps only the requested range even when encoding increases the size.

PR #14 has passed all three build checks and has been merged separately. I will bring this PR up to date with main as part of the fixes.

@thangdevalone

Copy link
Copy Markdown
Owner

One additional CI correction is needed to make the verification reliable: Turbo resolves the build task inputs relative to the example workspace. I checked turbo run build:android build:ios --dry=json; the current input lists do not include the library's VideoProcessor, the new native helpers, or the podspec.

I will qualify the library source, generated bridges, and podspec paths with $TURBO_ROOT$, while keeping the example app inputs relative to its workspace. This makes changes to the code under review invalidate the native build cache, so a green build cannot simply reuse an earlier result that ignored those changes. The shared regression fixture will also participate in the Android test/build cache.

@thangdevalone
thangdevalone merged commit 1bf2e33 into thangdevalone:main Oct 15, 2026
3 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.

Feature: optional startTime/endTime on compressVideo so trim + compress is a single encode

2 participants