media_compressor 1.1.0-beta.2
media_compressor: ^1.1.0-beta.2 copied to clipboard
A Flutter plugin for efficient image and video compression on Android, iOS, and Web.
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

