Media Compressor
A Flutter plugin for compressing images and videos efficiently using native platform implementations โ now on Android, iOS, and Web.
Demo
See the plugin in action:
| Image Compression Demo | Video Compression Demo |
|---|---|
![]() |
![]() |
Latest Updates
v1.1.0-beta.1
- ๐ Web support (beta) โ image (Canvas) and video (ffmpeg.wasm or MediaRecorder) compression in the browser
- ๐
cancel()โ abort an in-flight video compression on every platform - ๐งน
release()/releaseResult()โ free a compressed result (revokes blob URL on web, deletes temp file on mobile) - ๐ Hardened: cache-scoped file deletion, single-flight video, managed native lifecycle
- No breaking changes to the existing Dart API
v1.0.1
- ๐ Fixed iOS compilation error by adding the missing Flutter framework import
- โ All functionality working as expected on Android and iOS
Features
โ
Image Compression โ quality and dimension control
โ
Video Compression โ quality presets (low / medium / high)
โ
Native Performance โ platform-specific compression for optimal results
โ
Web Support (beta) โ in-browser compression via Canvas + ffmpeg.wasm / MediaRecorder
โ
Progress Tracking โ real-time video progress on Android and Web
โ
Cancel & Release โ abort jobs and free outputs
โ
Error Handling โ comprehensive, typed error codes
โ
Cross-platform โ Android, iOS, and Web
โ
EXIF Orientation โ automatic image orientation correction
Platform Support
| Feature | Android | iOS | Web (beta) |
|---|---|---|---|
| Image compression | โ | โ | โ |
| Video compression | โ Media3 (bitrate + resolution) | โ AVAssetExportSession (presets) | โ ffmpeg.wasm* or MediaRecorder |
| Progress | โ | ๐ง | โ |
| Cancel | โ | โ | โ |
| Output | MP4 / JPEG (file path) | MP4 / JPEG (file path) | MP4 / WebM / JPEG (blob URL) |
* ffmpeg.wasm is opt-in โ see Web video backends.
Installation
Stable (Android + iOS):
dependencies:
media_compressor: ^1.0.1
Beta (adds Web support):
dependencies:
media_compressor: 1.1.0-beta.1
Then run:
flutter pub get
Usage
Import the Package
import 'package:media_compressor/media_compressor.dart';
Compress an Image
final result = await MediaCompressor.compressImage(
ImageCompressionConfig(
path: '/path/to/image.jpg', // a blob URL on web (from image_picker)
quality: 80, // 0-100, where 100 is best quality
maxWidth: 1920, // Optional: max width in pixels
maxHeight: 1080, // Optional: max height in pixels
),
);
if (result.isSuccess) {
print('Compressed image saved at: ${result.path}');
} else {
print('Compression failed: ${result.error?.message}');
}
Compress a Video
final result = await MediaCompressor.compressVideo(
VideoCompressionConfig(
path: '/path/to/video.mp4', // a blob URL on web
quality: VideoQuality.medium, // low, medium, high
),
);
if (result.isSuccess) {
print('Compressed video saved at: ${result.path}');
} else {
print('Compression failed: ${result.error?.message}');
}
Only one video compression runs at a time on every platform. A concurrent call returns the error code
BUSY.
Cancel & Release
// Abort the in-flight video compression (safe no-op if none).
await MediaCompressor.cancel();
// Free a result when you no longer need it
// (revokes the blob URL on web, deletes the temp file on mobile).
await MediaCompressor.release(result.path!);
await MediaCompressor.releaseResult(result);
โ ๏ธ Don't release a result that's still in use โ a revoked blob URL will no longer load in
Image.networkor a video player.
Progress Tracking (Android & Web)
final eventChannel = EventChannel('native_compressor/progress');
eventChannel.receiveBroadcastStream().listen((event) {
final percentage = event['percentage'] as int;
print('Compression progress: $percentage%');
});
Video Quality Presets
enum VideoQuality {
low, // 480p โ smaller file
medium, // 720p โ balanced (recommended)
high, // 1080p โ higher quality
}
Compression Result
class CompressionResult {
final bool isSuccess;
final String? path; // file path (mobile) or blob URL (web)
final CompressionError? error;
bool get isFailure => !isSuccess;
}
Error Handling
class CompressionError {
final String code; // programmatic code
final String message; // human-readable message
final dynamic details; // optional platform details
}
Common error codes:
INVALID_ARGUMENTโ invalid arguments (missing path, quality out of range, bad dimensions)FILE_NOT_FOUNDโ input file doesn't existCOMPRESSION_ERRORโ native/browser compression failedNULL_RESULTโ compression returned null/emptyTIMEOUTโ exceeded the timeoutCANCELLEDโ aborted viacancel()BUSYโ another video compression is already in progressUNSUPPORTED/UNSUPPORTED_PLATFORMโ no encoder available on this platformLOAD_ERROR/PLAYBACK_ERRORโ web: failed to load/play the sourceINPUT_TOO_LARGEโ web: source exceeds the in-browser size limitUNKNOWN_ERRORโ unexpected error
Working with Results on Web
On web, result.path is a blob object URL (e.g. blob:http://...), not a
filesystem path. Use network/blob-aware APIs:
// Preview an image
Image.network(result.path!);
// Read bytes / size cross-platform (works for mobile paths AND web blob URLs)
final xfile = XFile(result.path!);
final bytes = await xfile.readAsBytes();
final size = await xfile.length();
File(result.path) does not work on web โ avoid dart:io in shared code.
Web video backends
- MediaRecorder (default, zero setup): canvas re-encode. WebM on Chromium/Firefox, MP4 on Safari/iOS, best-effort audio. Real-time; bitrate is a hint; progress is a time estimate.
- ffmpeg.wasm (opt-in, recommended for production): register a global
window.mediaCompressorFfmpeg(shim provided in the header ofmedia_compressor_web.dart) for off-thread H.264/MP4 with enforced bitrate and exact progress. Requires cross-origin isolation inweb/index.html(COOP: same-origin,COEP: require-corp) and a review of the libx264 (LGPL/GPL) licensing for your use case.
Platform-specific Setup
Android
Add the following permissions to your AndroidManifest.xml (only if reading
shared storage on API โค 32):
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
</manifest>
Note: For Android 13+ (API 33+), no storage permissions are needed for app-specific directories.
iOS
Add the following to your Info.plist:
<key>NSPhotoLibraryUsageDescription</key>
<string>This app needs access to compress photos and videos.</string>
Web
No setup is required for the default MediaRecorder backend. For the ffmpeg.wasm backend, add the COOP/COEP headers and the shim (see Web video backends).
API Reference
MediaCompressor
Static entry point for all compression operations.
| Method | Returns | Description |
|---|---|---|
compressImage(ImageCompressionConfig) |
Future<CompressionResult> |
Compress an image |
compressVideo(VideoCompressionConfig, {Duration? timeout}) |
Future<CompressionResult> |
Compress a video (default timeout 5 min) |
cancel() |
Future<void> |
Abort the in-flight video job |
release(String path) |
Future<void> |
Free a compressed result |
releaseResult(CompressionResult) |
Future<void> |
Release result.path if successful |
ImageCompressionConfig
ImageCompressionConfig({
required String path,
int quality = 80, // 0-100
int? maxWidth,
int? maxHeight,
})
VideoCompressionConfig
VideoCompressionConfig({
required String path,
VideoQuality quality = VideoQuality.medium,
})
Video Compression Details
- Android: AndroidX Media3 Transformer โ H.264/MP4, target resolution and bitrate per preset, audio preserved, progress events.
- iOS: AVAssetExportSession โ system presets (low/medium/high), MP4, audio preserved, network-optimized.
- Web: ffmpeg.wasm (H.264/MP4, enforced bitrate) when available; otherwise MediaRecorder (WebM/MP4, best-effort audio, real-time).
| Quality | Resolution | Use Case |
|---|---|---|
low |
480p | Quick sharing, minimal size |
medium |
720p | General sharing (recommended) |
high |
1080p | High-quality archival |
Complete Example
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:media_compressor/media_compressor.dart';
class CompressionExample extends StatefulWidget {
const CompressionExample({super.key});
@override
State<CompressionExample> createState() => _CompressionExampleState();
}
class _CompressionExampleState extends State<CompressionExample> {
Uint8List? _bytes;
String? _status;
Future<void> _compressImage() async {
final image = await ImagePicker().pickImage(source: ImageSource.gallery);
if (image == null) return;
final result = await MediaCompressor.compressImage(
ImageCompressionConfig(
path: image.path,
quality: 80,
maxWidth: 1920,
maxHeight: 1080,
),
);
if (result.isSuccess) {
// Cross-platform read (file path on mobile, blob URL on web).
final bytes = await XFile(result.path!).readAsBytes();
setState(() {
_bytes = bytes;
_status = 'Compressed: ${(bytes.length / 1024).toStringAsFixed(1)} KB';
});
await MediaCompressor.release(result.path!);
} else {
setState(() => _status = 'Error: ${result.error?.message}');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Media Compressor')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_bytes != null) SizedBox(height: 240, child: Image.memory(_bytes!)),
if (_status != null)
Padding(padding: const EdgeInsets.all(16), child: Text(_status!)),
ElevatedButton(
onPressed: _compressImage,
child: const Text('Compress Image'),
),
],
),
),
);
}
}
Best Practices
- Quality Settings โ start with 80-85 for images: good compression, minimal loss.
- Dimension Limits โ set
maxWidth/maxHeightto avoid memory spikes on large images. - Error Handling โ always check
result.isSuccessbefore using the output. - Release Outputs โ call
release()when done, especially on web. - Timeout โ increase the timeout for large videos.
- Web Video โ output may be WebM on the fallback path; encoding runs in real time. Use the ffmpeg.wasm backend for MP4 + faster-than-realtime + exact progress.
Performance Tips
- Image compression is fast (typically milliseconds).
- Video compression can take seconds to minutes depending on file size.
- Compress files sequentially โ concurrent video calls return
BUSY. - Show a progress indicator during video compression.
Troubleshooting
"File not found" error โ verify the path and permissions.
Video compression timeout โ increase the timeout, lower the quality, check storage.
Out-of-memory errors โ reduce maxWidth/maxHeight; process sequentially; on web, keep source videos within the in-browser size limit.
Web: result.path won't load โ it's a blob URL; use Image.network / a network video source, not File(...).
Platform-Specific Features
For detailed bitrates, resolutions, codecs, and per-platform behavior, see PLATFORM_FEATURES.md.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License โ see the LICENSE file for details.
Support
For issues, feature requests, or questions, please file an issue on the GitHub repository.
Media Credits
The demo uses sample media for showcasing compression features.
๐ท Image Credit
Photo by Zhen Yao
from Unsplash
๐ฅ Video Credit
Video by tham nguyen
from Pixabay
Made with โค๏ธ for the Flutter community

