light_compressor_v2
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.
- Custom resolution & bitrate โ override width, height, and bitrate when presets aren't enough.
- Structured success result โ
OnSuccesscarriesoriginalSize,compressedSize,duration, andratio(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
๐ฑ 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 / totalcount (batch) and a Cancel action. The title comes fromBackgroundConfig. The plugin declares the service + receiver and requestsPOST_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
BackgroundConfighas no effect; the compression pauses and resumes when the app returns to the foreground.
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. | |
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. | |
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. |
Result types
| Type | Properties | Description |
|---|---|---|
OnSuccess |
destinationPath: String, originalSize: int, compressedSize: int, duration: double, ratio: double |
Output path, byte sizes, duration (seconds) and percentage size reduction. |
OnFailure |
message: String |
Unclassified failure with an error message. |
OnCancelled |
isCancelled: bool |
Compression was cancelled via cancelCompression(). |
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.
๐ค Contributing
Contributions are welcome! To get started:
- Fork the repository: github.com/Farid023/light_compressor_v2
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes and run the example app to verify:
cd example flutter run - 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
Released under the MIT License โ see LICENSE for the full text.
MIT ยฉ 2025 Farid Gurbanov