Skip to content

Releases: farid023/light_compressor_v2

1.9.1

Choose a tag to compare

@farid023 farid023 released this 09 Aug 09:28
b441adc

Several paths through the AVFoundation pull loop could end a compression
with no result at all — no success, no failure, no crash — leaving the
caller waiting forever:

  • The terminal branches did not return, so the loop could re-enter, find
    the audio reader already reading, take the no-audio branch and call
    finishWriting while the audio input was still unfinished. A writer in
    that state never completes.
  • A failed reader returned quietly through a bare guard.
  • The audio loop had the same missing return, so a second
    markAsFinished/finishWriting was reachable.
  • The audio phase began with a redundant second startSession.
  • The audio phase never checked for cancellation.

Every terminal path now returns, a failed reader reports a failure, and
the terminal reply and finishWriting each run exactly once behind
single-shot guards. These are latent defects found by inspection: a
physical iPhone 13 Pro Max (iOS 26.5.2) and macOS 15.7 both complete 4K
camera clips in either audio mode, and native probes show each terminal
branch running exactly once.

Also closes a coverage gap: the bundled sample clip has no audio track,
so the Apple audio mux (passthrough and AAC re-encode) was never
exercised — which is how the addInput crash fixed in 1.9.0 reached a
release. Adds an audio-bearing fixture, generated with the plugin's own
pipeline, and real coverage for both paths. Verified by reverting that
fix on a physical device: the new test crashes the app and passes with
the fix, where the previous one passed either way.

Bumps to 1.9.1.

1.9.0

Choose a tag to compare

@farid023 farid023 released this 20 Jul 10:51
b27e864

New

  • Tap the foreground notification to reopen the app (Android, #16): the ongoing compression notification now carries a content intent, so tapping it brings the running app forward (FLAG_ACTIVITY_REORDER_TO_FRONT). With a task-reusing host activity (Flutter's default launchMode="singleTop") the existing Flutter state is kept; after a full process kill it cold-starts. Thanks @khlebobul (#18).

Fixed

  • iOS/macOS crash when compressing videos that have audio (#17): with no AudioConfig (the default), the source audio is muxed through unchanged. The native audio writer input was created without the source format description, which made AVAssetWriter throw NSInvalidArgumentException ("provide a format hint") on a physical iOS device — reproducing 100% on .mov camera recordings (they always carry an audio track). The input now carries the source CMFormatDescription, so passthrough muxing to the .mp4 container succeeds. The simulator and macOS did not surface the crash, which is why it slipped past.
  • Batch progress bounds: BatchProgress.percent and overallPercent are now clamped to 0..100, matching single-video progress — a native value that rounds just over 100 (or a transient negative) no longer leaks into batch progress UIs.

1.8.2

Choose a tag to compare

@farid023 farid023 released this 06 Jul 08:49
8f1a4d5

Changed

  • Wider compatibility: lowered the minimum toolchain to Flutter 3.24 / Dart 3.5 (from 3.41 / 3.11) — no code changes; the floor now matches the SwiftPM baseline and Android compileSdk 34 (AGP 8.1.1+).
  • Docs: refreshed the pub.dev package description. No code or API changes.

1.8.0

Choose a tag to compare

@farid023 farid023 released this 02 Jul 13:37
61dded2

New

  • Opt-in debug logging — pass debugLogging: true to compressVideo /
    compressVideos to have the native side emit a couple of structured log lines
    per video (the resolved encode plan and the outcome). File paths are reduced to
    their base names. Off by default; intended for diagnosing a single run.
  • Progress detail (ETA + bytes) — a new onProgressDetail
    (Stream<CompressionProgress>) reports, alongside the percentage, the
    estimated time remaining (etaMs), elapsed time (elapsedMs) and encoded
    output bytes written so far (bytesProcessed) for the single-video flow. The
    same fields are now also on BatchProgress (via onBatchUpdate). The
    existing onProgressUpdated (Stream<double>) is unchanged — it stays the
    simplest option for just the percentage. etaMs is a rough projection (an
    indicator, not a guarantee) and is null until it becomes estimable.
  • Configurable batch concurrency — pass maxConcurrent to compressVideos
    to cap how many videos transcode at the same time. Leaving it unset keeps each
    platform's historic default (Android compresses up to 2 at once; Apple starts
    them all); setting it (>= 1) compresses strictly one-at-a-time (1) or trades
    memory and device heat for throughput at higher values. Honoured on Android,
    iOS and macOS; has no effect on a single compressVideo.
  • Example app gains a "Max concurrent" (Auto / 1 / 2 / 3) selector in the
    batch flow.

1.7.0

Choose a tag to compare

@farid023 farid023 released this 02 Jul 12:59
ec1d1b8

New

  • Lightweight native editing — pass an optional VideoEdit as edit: to
    compressVideo / compressVideos to trim and/or rotate while compressing
    (still 100% native — no ffmpeg):
    • TrimtrimStartMs / trimEndMs keep a time range; the output timeline
      is rebased to start at 0 and the reported duration reflects the trimmed
      length. Frame-accurate (the clip is re-encoded). Either bound is optional.
    • RotaterotationDegrees (0 / 90 / 180 / 270) applies a
      quarter-turn on top of the source orientation. This is a cheap
      container-metadata rotation (no extra pixel pass) that players honour — a 90°
      turn swaps the displayed dimensions.
    • Colour adjustbrightness (-1..1), contrast (0..2) and
      saturation (0..2) tweak the picture (CIColorControls semantics; 0 / 1
      / 1 = no change). Baked into the output pixels — Android via a GL shader,
      Apple via a CIColorControls video composition. Exact cross-platform pixel
      parity is not guaranteed.
  • Example app gains trim start/end (ms) fields, a 0/90/180/270 rotate selector and
    brightness/contrast/saturation sliders in the single-video flow.

All additions are additive and fully backward compatible — existing APIs are
unchanged.

1.6.0

Choose a tag to compare

@farid023 farid023 released this 02 Jul 06:17
6683198

New

  • Target output size — pass targetSizeMb to compressVideo (on Video) or
    to compressVideos to compress toward a maximum file size in megabytes. The
    compressor solves for the video bitrate that lands the output at or below the
    target (reserving room for audio + ~3% container overhead), clamped to a 2 Mbps
    quality floor and never above the source bitrate. Mutually exclusive with
    videoBitrateInMbps. The new OnSuccess.targetSizeMet reports whether the
    target was achievable — false when the floor forced a larger output.
    Single-pass and approximate (typically within ~10–15%).
  • Two-pass encoding — pass twoPass: true (on Video / compressVideos,
    alongside targetSizeMb) to land closer to the target size: the compressor
    encodes once, and only if the output overshot the target does it re-encode a
    second time at a corrected (lower) bitrate. An undershoot is kept as-is, so it
    re-encodes only when needed (roughly doubling the time on overshooting clips).
    The new OnSuccess.passesUsed reports how many passes ran (1 or 2). Ignored
    without a targetSizeMb.
  • Frame-rate control — pass videoFps (on Video / compressVideos) to
    downsample the output frame rate (e.g. 30 → 24). Downsample-only: a value at or
    above the source rate leaves it unchanged (frames are never duplicated).
  • Audio re-encoding — pass an AudioConfig(bitrate:, sampleRate:) as
    audio: to re-encode the audio track as AAC with a custom bitrate (and, on
    Apple, sample rate). Omitting it copies the source audio through untouched.
    • Platform note: audioSampleRate is applied on iOS/macOS; Android
      re-encodes at the source sample rate
      (no resampler), so only bitrate
      takes effect there.
  • Example app gains "max output size (MB)", a "Two-pass (precise size)" toggle,
    "output FPS" and "audio bitrate (kbps)" fields in the single-video flow.

All additions are additive and fully backward compatible — existing APIs are
unchanged.

1.5.0

Choose a tag to compare

@farid023 farid023 released this 01 Jul 08:38
f8c7804

New

  • getCompressionEstimate() — predict a compression's output (size, bitrate, output resolution, % reduction) without transcoding, via the new CompressionEstimate model. It reuses the same bitrate/resize math the compressor uses, so the figures track the real output (approximate — single-pass).
  • getVideoThumbnails() — extract several frames in a single native round-trip, returning the JPEG paths in request order, via the new ThumbnailRequest model. More efficient than calling getVideoThumbnail repeatedly.
  • isCompressing() — query whether a compression (single or batch) is currently running (e.g. to gate UI).
  • New EstimateException (extends LightCompressorException) for estimate failures.
  • Example app gains a pre-flight estimate card and a multi-thumbnail filmstrip in the single-video flow.

All additions are additive and fully backward compatible — existing APIs are unchanged.

1.4.0

Choose a tag to compare

@farid023 farid023 released this 25 Jun 11:59

New

  • H.265 / HEVC output — pass an optional videoFormat to compressVideo / compressVideos to choose the output codec (VideoFormat.h264 — the default — or VideoFormat.h265). HEVC produces noticeably smaller files at comparable quality. Omitting the parameter keeps the previous H.264 behaviour and is fully backwards compatible.
    • Automatic fallbackVideoFormat.h265 is used only when the device can encode HEVC in hardware (Android: a non-software video/hevc encoder; iOS/macOS: an advertised HEVC encoder). On devices without it, the compressor transparently falls back to H.264 instead of failing.
    • OnSuccess.usedFormat — every successful result now reports the codec actually used, so you can tell whether an H.265 request was honoured or fell back to H.264.
  • OnFailure.failureType — failures now carry a CompressionFailureType (permission, unsupported, notFound, unknown) so you can react to why a video failed — including per-item in a batch — without parsing message text. Defaults to unknown and OnFailure.message is unchanged, so this is fully backwards compatible.
  • Example app gains a “Use H.265 (HEVC)” toggle in both the single and batch flows, and the single-video result now shows the codec used.