Skip to content

iOS: Video.compress silently drops the video track (audio-only output) since 1.17.0 (#392) #400

@hlog2e

Description

@hlog2e

Summary

Since 1.17.0 (PR #392), on iOS Video.compress can resolve "successfully" with an MP4 that contains only an audio track — the video track is silently dropped. The promise resolves with no error, so callers upload/use an audio-only file. 1.16.2 and earlier are fine.

Environment

  • react-native-compressor: 1.18.2 (the iOS video path is byte-identical across 1.18.0 / 1.18.1 / 1.18.2)
  • iOS (reproduced on the iOS Simulator; real-device confirmation welcome — see note in Root cause)
  • Input: 4K HEVC from Photos (3840×2160, ~32 MB, H.265 + AAC)
  • Options: { compressionMethod: "manual", maxSize: 1080, bitrate: 5000000 } (also reproduces with "auto")

Steps to reproduce

  1. Pick/copy a 4K HEVC video (e.g. an iPhone .mov, 3840×2160).
  2. await Video.compress(uri, { compressionMethod: "manual", maxSize: 1080, bitrate: 5000000 })
  3. ffprobe the output.

Expected

Output MP4 has a video track (H.264) + audio.

Actual

Output has 0 video streams, 1 AAC stream (~70 KB / 4.3 s). No error is thrown. Downstream tools report 0 Frames found / corrupted.

# ffprobe of the input (Video.compress argument)
video: hevc 3840x2160, audio: aac        (~32 MB)
# ffprobe of the output (Video.compress result)
video: <none>,           audio: aac        (~70 KB)

Bisect

Root cause (analysis)

NextLevelSessionExporter.setupVideoOutput only creates the video AVAssetWriterInput when writer.canApply(videoOutputConfiguration, .video) == true; otherwise it logs "Unsupported output configuration" and returns with _videoInput = nil. The export loop then writes audio only, yet writer.status ends as .completed, so the call resolves as .success — a silent audio-only result. (validateVideoOutputConfiguration() only checks for the presence of width/height keys.)

#392 rewrote the videoOutputConfiguration that VideoMain.swift passes to the exporter. Versus 1.16.x it:

  • adds AVVideoExpectedSourceFrameRateKey and AVVideoAverageNonDroppableFrameRateKey to AVVideoCompressionPropertiesKey;
  • changes scaling AVVideoScalingModeResizeAspectFillAVVideoScalingModeResizeAspect;
  • changes AVVideoWidthKey / AVVideoHeightKey value type FloatInt;
  • replaces the dimension/bitrate logic (scaledDimensions / estimateBitrate).

The same configuration is tolerated by macOS AVFoundation (standalone test: canApply == true, video preserved) but drops the video track on the iOS encoder, so the failure is specific to the iOS encoder + the new config. The two added frame-rate keys are the prime suspects (AVVideoAverageNonDroppableFrameRateKey in particular is not a documented compression property for avc1/H.264). Because canApply can still return true, the gate in setupVideoOutput doesn't catch it.

Also: the onFailure fallback #392 added only triggers when the export ends as .failure. Here it ends as .completed (audio succeeded), so the audio-only result is returned as success.

Suggested fix

  • Remove AVVideoAverageNonDroppableFrameRateKey (and likely AVVideoExpectedSourceFrameRateKey) from the H.264 compressionDict, or gate them behind verified-supported values; and/or
  • After export, assert the output actually contains a video track and surface a real error (or fall back to the original) instead of silently returning an audio-only file.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions