light_compressor_v2 1.4.0 copy "light_compressor_v2: ^1.4.0" to clipboard
light_compressor_v2: ^1.4.0 copied to clipboard

A powerful and easy-to-use video compression plugin for Flutter.

light_compressor_v2 #

Pub Version CI Pub Platforms Pub Likes License: MIT

A powerful, easy-to-use video compression plugin for Flutter โ€” for single videos and batches.

It generates a compressed MP4 with reduced width, height, and bitrate while keeping good visual quality, and also exposes media metadata, thumbnail extraction, and cache cleanup.

๐Ÿ› ๏ธ How it Works #

Extreme high bitrates are reduced while maintaining good video quality, resulting in a much smaller file size.

  • Quality presets: choose between 5 compression qualities โ€” very_low, low, medium, high, very_high. The plugin automatically computes the target bitrate for the output.
  • Minimum bitrate guard: with isMinBitrateCheckEnabled (default 2 Mbps threshold), the plugin skips compression for low-bitrate or already-compressed videos, avoiding cumulative quality degradation.

โœจ Features #

  • Single & batch compression โ€” compress one video, or many in a single call with per-item results and progress.
  • Five quality presets โ€” the plugin calculates the optimal bitrate automatically.
  • H.264 & H.265 (HEVC) โ€” pick the output codec via videoFormat; H.265 produces smaller files and automatically falls back to H.264 when the device can't encode it. OnSuccess.usedFormat reports the codec actually used.
  • Custom resolution & bitrate โ€” override width, height, and bitrate when presets aren't enough.
  • Structured success result โ€” OnSuccess carries originalSize, compressedSize, duration, and ratio (percentage reduction).
  • Media info โ€” read width, height, duration, bitrate, rotation, frame rate, and MIME type via getMediaInfo.
  • Thumbnail extraction โ€” grab a JPEG frame at any timecode via getVideoThumbnail.
  • Progress streams โ€” real-time percentage for single (onProgressUpdated) and per-item + overall for batch (onBatchUpdate).
  • Cancellation โ€” cancel any in-progress compression with a single call.
  • Background execution โ€” keep compressing while the app is backgrounded or the screen is off via BackgroundConfig (Android foreground service; macOS App Nap suppression; not supported on iOS).
  • Typed exceptions โ€” PermissionDeniedException, UnsupportedVideoException, VideoNotFoundException, and more โ€” react programmatically instead of parsing strings.
  • Minimum bitrate guard โ€” optionally skip compression for already-low-bitrate videos.
  • Disable audio โ€” generate silent videos when audio isn't needed.
  • Cache cleanup โ€” remove temporary files with clearCache.
  • iOS / macOS: Swift Package Manager (SPM) support alongside CocoaPods.
  • Android: fully Kotlin native layer, Gradle KTS build script.

๐Ÿ“ธ Demo #

Demo GIF


๐Ÿ“ฑ Platform Support #

iOS Android macOS
โœ… โœ… โœ…

Minimum versions: iOS 11 ยท Android API 24 ยท macOS 10.15


๐Ÿ“ฆ Installation #

Add the dependency to your pubspec.yaml:

dependencies:
  light_compressor_v2: ^1.3.0

Then run:

flutter pub get

iOS / macOS โ€” Podfile #

No extra Podfile configuration is required. The plugin ships with both a .podspec (CocoaPods) and a Package.swift (SPM); Flutter picks the appropriate integration automatically.

Android โ€” minSdk #

The plugin requires minSdk 24. If your app targets a lower SDK, update your android/app/build.gradle:

android {
    defaultConfig {
        minSdk = 24
    }
}

๐Ÿš€ Usage #

import 'package:light_compressor_v2/light_compressor_v2.dart';

final compressor = LightCompressor();

Compress a single video #

final Result result = await compressor.compressVideo(
  path: '/path/to/source.mp4',
  videoQuality: VideoQuality.medium,
  isMinBitrateCheckEnabled: false,
  video: Video(videoName: 'compressed.mp4'),
  android: AndroidConfig(isSharedStorage: true, saveAt: SaveAt.Movies),
  ios: IOSConfig(saveInGallery: true),
);

if (result is OnSuccess) {
  print('Saved to ${result.destinationPath}');
  print('Reduced by ${result.ratio.toStringAsFixed(1)}% '
      '(${result.originalSize} โ†’ ${result.compressedSize} bytes)');
} else if (result is OnFailure) {
  print('Failed: ${result.message}');
} else if (result is OnCancelled) {
  print('Cancelled');
}

Compress a batch of videos #

A single failing video does not stop the others โ€” its slot in the returned list becomes an OnFailure. Results are returned in the same order as paths.

final List<Result> results = await compressor.compressVideos(
  paths: ['/path/a.mp4', '/path/b.mp4'],
  videoNames: ['a_compressed.mp4', 'b_compressed.mp4'],
  videoQuality: VideoQuality.medium,
  android: AndroidConfig(saveAt: SaveAt.Movies),
  ios: IOSConfig(saveInGallery: false),
);

for (final (int i, Result r) in results.indexed) {
  if (r is OnSuccess) print('Video $i โ†’ ${r.destinationPath}');
}

Run in the background #

Pass a BackgroundConfig to keep a compression running while the app is backgrounded or the screen turns off. Works for both compressVideo and compressVideos:

final result = await compressor.compressVideo(
  path: '/path/to/source.mp4',
  videoQuality: VideoQuality.medium,
  video: Video(videoName: 'compressed.mp4'),
  android: AndroidConfig(saveAt: SaveAt.Movies),
  ios: IOSConfig(saveInGallery: true),
  background: const BackgroundConfig(
    notificationTitle: 'Compressing video',
  ),
);

Platform behaviour differs significantly:

  • Android โ€” runs under a foreground service. The ongoing notification shows live progress (bar + %), elapsed time, the current file (single) or a done / total count (batch) and a Cancel action. The title comes from BackgroundConfig. The plugin declares the service + receiver and requests POST_NOTIFICATIONS (Android 13+) automatically โ€” no host-app manifest changes are needed.
  • macOS โ€” suppresses App Nap so the process keeps full CPU in the background. The notification fields are ignored.
  • iOS โ€” not supported. iOS suspends backgrounded apps within seconds and offers no sanctioned way to keep video transcoding running, so passing a BackgroundConfig has no effect; the compression pauses and resumes when the app returns to the foreground.

Choose the output codec (H.265 / HEVC) #

By default the output is H.264 (AVC). Pass videoFormat: VideoFormat.h265 to request HEVC, which yields smaller files at comparable quality. It applies to both compressVideo and compressVideos:

final result = await compressor.compressVideo(
  path: '/path/to/source.mp4',
  videoQuality: VideoQuality.medium,
  videoFormat: VideoFormat.h265,
  video: Video(videoName: 'compressed.mp4'),
  android: AndroidConfig(saveAt: SaveAt.Movies),
  ios: IOSConfig(saveInGallery: true),
);

if (result is OnSuccess) {
  // Tells you whether H.265 was honoured or fell back to H.264.
  print('Encoded with ${result.usedFormat.name}');
}

VideoFormat.h265 is used only when the device has a hardware HEVC encoder (Android excludes software-only encoders; iOS/macOS check the platform's advertised HEVC support). When it isn't available the compressor silently falls back to H.264 rather than failing โ€” always read OnSuccess.usedFormat to know what you got.

Listen to progress #

Single video โ€” a Stream<double> from 0 to 100:

StreamBuilder<double>(
  stream: compressor.onProgressUpdated,
  builder: (context, snapshot) {
    final percent = snapshot.data ?? 0;
    return Text('${percent.toStringAsFixed(0)}%');
  },
);

Batch โ€” per-video and overall progress, plus a completion event per item:

compressor.onBatchUpdate.listen((BatchEvent event) {
  switch (event) {
    case BatchProgress(:final index, :final overallPercent):
      print('Video $index โ€” overall ${overallPercent.toStringAsFixed(0)}%');
    case BatchItemCompleted(:final index, :final result):
      print('Video $index finished: $result');
  }
});

Cancel compression #

await compressor.cancelCompression();

The cancelled job is reported as an OnCancelled result on the pending compressVideo / compressVideos call.

Read media info #

final MediaInfo info = await compressor.getMediaInfo('/path/to/video.mp4');
print('${info.displayWidth} ร— ${info.displayHeight}');
print('Duration: ${info.duration}, bitrate: ${info.bitrate} bps');

Extract a thumbnail #

final String thumbnailPath = await compressor.getVideoThumbnail(
  '/path/to/video.mp4',
  positionInMs: 2000, // grab the frame at 2s
  quality: 80,
);

Clear cached files #

await compressor.clearCache();

Handle errors #

Recognized native failures are thrown as typed exceptions; unclassified failures are returned as OnFailure instead.

try {
  final info = await compressor.getMediaInfo('/path/to/video.mp4');
  // use info...
} on VideoNotFoundException catch (e) {
  print(e.message);
} on PermissionDeniedException catch (e) {
  print(e.message);
} on LightCompressorException catch (e) {
  print(e.message); // base type โ€” catches any of the above
}

๐Ÿ“– API Reference #

compressVideo() โ†’ Future<Result> #

Parameter Type Required Default Description
path String โœ… โ€” Absolute path to the source video file.
videoQuality VideoQuality โœ… โ€” Quality preset: very_low, low, medium, high, very_high.
android AndroidConfig โœ… โ€” Android-specific storage configuration.
ios IOSConfig โœ… โ€” iOS/macOS-specific storage configuration.
video Video โœ… โ€” Output video configuration (name, resolution, bitrate).
isMinBitrateCheckEnabled bool true Skip compression when source bitrate is below 2 Mbps.
disableAudio bool? false Strip the audio track from the output.
videoFormat VideoFormat h264 Output codec: h264 or h265 (HEVC). Falls back to H.264 when HEVC isn't supported. See VideoFormat.
background BackgroundConfig? null Keep running while the app is backgrounded. See BackgroundConfig.

compressVideos() โ†’ Future<List<Result>> #

Parameter Type Required Default Description
paths List<String> โœ… โ€” Source video paths.
videoNames List<String> โœ… โ€” Output file names; must match paths length.
videoQuality VideoQuality โœ… โ€” Quality preset, shared by every video.
android AndroidConfig โœ… โ€” Android-specific storage configuration.
ios IOSConfig โœ… โ€” iOS/macOS-specific storage configuration.
keepOriginalResolution bool false Keep source dimensions instead of downscaling.
videoWidth / videoHeight int? null Custom output size (set both together).
videoBitrateInMbps int? null Custom bitrate in Mbps (overrides the preset).
disableAudio bool false Strip the audio track from every output.
isMinBitrateCheckEnabled bool true Skip compression when source bitrate is below 2 Mbps.
videoFormat VideoFormat h264 Output codec for every video: h264 or h265 (HEVC). See VideoFormat.
background BackgroundConfig? null Keep the whole batch running while backgrounded. See BackgroundConfig.

Video #

Parameter Type Required Default Description
videoName String โœ… โ€” Output filename (.mp4 appended automatically if missing).
keepOriginalResolution bool? false Keep source dimensions instead of downscaling.
videoBitrateInMbps int? null Custom bitrate in Mbps (overrides the quality preset).
videoHeight int? null Custom height in pixels. Must be set with videoWidth.
videoWidth int? null Custom width in pixels. Must be set with videoHeight.

AndroidConfig #

Parameter Type Default Description
isSharedStorage bool true true = shared storage (MediaStore); false = app-specific directory.
saveAt SaveAt Movies Target collection: Pictures, Movies, or Downloads. Ignored when isSharedStorage is false.

IOSConfig #

Parameter Type Default Description
saveInGallery bool true Save the compressed video to the photo library.

BackgroundConfig #

Opt into background execution. notificationTitle is the Android foreground-service notification title; iOS and macOS ignore it.

Parameter Type Default Description
notificationTitle String 'Compressing video' Title of the Android foreground-service notification.

VideoFormat #

Output codec, written into an MP4/QuickTime container.

Value Description
h264 H.264 / AVC. The widely compatible default.
h265 H.265 / HEVC. Smaller files at comparable quality; requires a hardware HEVC encoder and automatically falls back to h264 otherwise. Check OnSuccess.usedFormat for the codec actually used.

Result types #

Type Properties Description
OnSuccess destinationPath: String, originalSize: int, compressedSize: int, duration: double, ratio: double, usedFormat: VideoFormat Output path, byte sizes, duration (seconds), percentage size reduction and the codec actually used.
OnFailure message: String, failureType: CompressionFailureType A failure: a human-readable message plus a CompressionFailureType category for reacting in code without parsing text.
OnCancelled isCancelled: bool Compression was cancelled via cancelCompression().

CompressionFailureType #

The category carried by OnFailure.failureType, for reacting to why a video failed (including per-item in a batch) without parsing message. Defaults to unknown.

Value Description
permission A required permission (e.g. storage) was denied.
unsupported The source could not be processed โ€” e.g. no decodable video track or an unsupported format.
notFound The source file could not be found or opened.
unknown Any other or unclassified failure.

BatchEvent (from onBatchUpdate) #

Type Properties Description
BatchProgress index: int, percent: double, overallPercent: double Progress of one video and the batch average.
BatchItemCompleted index: int, result: Result A video finished; result is OnSuccess / OnFailure / OnCancelled.

MediaInfo (from getMediaInfo) #

All fields are nullable โ€” a container/device may not expose every value.

Property Type Description
width / height int? Encoded dimensions in pixels (before rotation).
displayWidth / displayHeight int? Dimensions as displayed (rotation-aware).
duration Duration? Total duration.
fileSize int? File size in bytes.
bitrate int? Bitrate in bits per second.
rotation int? Rotation in degrees (0, 90, 180, 270).
frameRate double? Frames per second.
mimeType String? Container MIME type.

Exceptions #

All extend LightCompressorException (catch the base type to handle any):

Exception Thrown when
PermissionDeniedException Missing read/write permission.
UnsupportedVideoException Unsupported format/codec or missing track.
VideoNotFoundException The source video was not found.
MediaInfoException Metadata could not be read (getMediaInfo).
ThumbnailException A frame could not be extracted (getVideoThumbnail).

Other members #

Member Signature Description
onProgressUpdated Stream<double> Single-video progress, 0โ€“100.
onBatchUpdate Stream<BatchEvent> Per-video + overall progress and completion events during compressVideos.
getMediaInfo() Future<MediaInfo> Read video metadata.
getVideoThumbnail() Future<String> Extract a JPEG frame; returns its file path.
clearCache() Future<void> Delete temporary .mp4 files created during compression.
cancelCompression() Future<void> Cancel any running compression.

โš™๏ธ Configuration #

iOS / macOS #

SPM vs CocoaPods โ€” the plugin includes both Package.swift and .podspec. Flutter โ‰ฅ 3.24 uses SPM by default; older versions fall back to CocoaPods automatically.

Info.plist โ€” if you use IOSConfig(saveInGallery: true), add the photo library usage description:

<key>NSPhotoLibraryUsageDescription</key>
<string>Used to save compressed videos.</string>

Android #

Permissions โ€” add the appropriate permissions to AndroidManifest.xml based on your target API level:

<!-- API < 29 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"
    tools:ignore="ScopedStorage" />

<!-- API 29โ€“32 -->
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

<!-- API โ‰ฅ 33 -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

Background execution โ€” when you pass a BackgroundConfig, no manifest changes are required on your side. The plugin already declares the foreground service and the FOREGROUND_SERVICE, FOREGROUND_SERVICE_DATA_SYNC and POST_NOTIFICATIONS permissions, which merge into your app automatically; the POST_NOTIFICATIONS runtime prompt (Android 13+) is requested for you.

ProGuard โ€” no special ProGuard or R8 rules are required.


๐Ÿงช Testing #

The plugin ships with two layers of tests:

  • Unit tests (test/) cover the Dart surface: argument forwarding over the method channel, result/event parsing, batch ordering and failure typing, progress-stream coercion, and the typed-exception mapping. They need no device:

    flutter test
    
  • Integration tests (example/integration_test/) exercise the real native pipeline (Android MediaCodec / MediaMuxer, Apple AVFoundation) on a device, emulator, or simulator โ€” metadata, thumbnails, compression options, progress streams, cancellation, batch resilience, and H.264 / H.265 codec selection with automatic fallback:

    cd example
    flutter test integration_test/plugin_integration_test.dart -d <deviceId>
    flutter test integration_test/hevc_compression_test.dart   -d <deviceId>
    

    A short sample clip is bundled at example/integration_test/assets/sample.mp4; the tests skip cleanly when it is absent.

flutter analyze, formatting, and the unit tests run in CI on every push and pull request. Integration tests are run manually, since they need a device.


๐Ÿค Contributing #

Contributions are welcome! To get started:

  1. Fork the repository: github.com/Farid023/light_compressor_v2
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make your changes and run the example app to verify:
    cd example
    flutter run
    
  4. Open a Pull Request with a clear description of the change.

Please report bugs via GitHub Issues. Include the device name, OS version, and whether the issue reproduces in the example app.


๐Ÿ“„ License #

License: MIT

Released under the MIT License โ€” see LICENSE for the full text.

MIT ยฉ 2025 Farid Gurbanov

10
likes
160
points
5.87k
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A powerful and easy-to-use video compression plugin for Flutter.

Repository (GitHub)
View/report issues

Topics

#video #compression #video-compression #media #compressor

License

MIT (license)

Dependencies

flutter

More

Packages that depend on light_compressor_v2

Packages that implement light_compressor_v2