media_reduce 0.1.0 copy "media_reduce: ^0.1.0" to clipboard
media_reduce: ^0.1.0 copied to clipboard

Iteratively compresses images and videos toward a maximum byte budget on Android and iOS, so files fit API upload limits before sending.

media_reduce #

A Flutter plugin that iteratively shrinks images and videos on the device until they fit a maximum file size you specify (e.g. 4 MB), so the output can be uploaded to APIs with strict body-size limits.

The plugin runs a binary-search loop in Dart over native encoders:

  • Android: BitmapFactory (images) and Media3 Transformer with VideoEncoderSettings (videos).
  • iOS: ImageIO / UIGraphicsImageRenderer (images) and AVAssetReader + AVAssetWriter with AVVideoAverageBitRateKey (videos).

If the source is already under the target, it is returned untouched. If the budget cannot be reached, the smallest valid output is returned with achievedTarget == false.

Features #

  • Single high-level API: reduceToMaxBytes(ReductionRequest).
  • Works for both images and videos behind one entry point; type is detected from path / native metadata.
  • Progress callback (attempt, stage, currentBytes, targetMaxBytes).
  • Tolerance band so the search can stop early once "close enough".
  • Clean architecture (domain / data split, port + adapter, dependency injection) so it is easy to mock and test.

Install #

dependencies:
  media_reduce: ^0.1.0

Then:

flutter pub get

Platform requirements #

  • Android: minSdk 24+, AGP 8+, Kotlin 1.9+, internet not required.
  • iOS: iOS 13+. Add the standard photo / camera usage strings if your app picks media:
<key>NSPhotoLibraryUsageDescription</key>
<string>Select photos and videos to compress before upload.</string>
<key>NSCameraUsageDescription</key>
<string>Capture photos or videos to compress before upload.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Record videos with audio to compress before upload.</string>

Usage #

import 'dart:io';
import 'package:media_reduce/media_reduce.dart';

final plugin = MediaReduce();

final result = await plugin.reduceToMaxBytes(
  ReductionRequest(
    sourcePath: pickedFile.path,
    maxBytes: 4 * 1024 * 1024, // 4 MB cap
    tolerancePercent: 5,       // optional, default 5%
  ),
  onProgress: (attempt, stage, bytes, target) {
    debugPrint('attempt $attempt | $stage | $bytes / $target bytes');
  },
);

if (result.success && result.outputPath != null) {
  final upload = File(result.outputPath!);
  // hand `upload` to your http multipart request...
}

result (ReductionResult) carries:

Field Meaning
success A usable output exists
outputPath Absolute path of the produced file
originalBytes Source size in bytes
outputBytes Compressed size in bytes
kind MediaKind.image / video / unknown
attempts How many native encode passes ran
achievedTarget true if outputBytes <= maxAcceptableBytes
stageLog Diagnostic log of each stage attempted
message Failure or BEST_EFFORT marker
ReductionRequest(
  sourcePath: file.path,
  maxBytes: 4 * 1024 * 1024,
  tolerancePercent: 8,
  maxAttemptsPerStage: 18,
  minImageQuality: 5,
  maxImageQuality: 95,
  minVideoBitrate: 200_000,    // 200 kbps floor
  maxVideoBitrate: 8_000_000,  // 8 Mbps ceiling
);

Other helpers #

final info = await plugin.getMediaInfo(path); // sizeBytes, kind, w/h, durationMs
await plugin.clearCache();                    // removes temp files this plugin created

How the search works #

Per kind:

  1. Read native MediaInfo (size, dimensions, duration).
  2. If originalBytes is already inside the tolerance band, return it.
  3. Walk through descending resolution stages (videos and images), and for each stage binary-search the native parameter (image quality or video bitrate) until an output fits the band, or the search converges.
  4. Keep the best result seen so far so a "best effort" output is always available.

Limitations #

  • Android scaling for videos is not yet wired through Media3 Effects; resolution downscaling currently runs only on iOS. Bitrate-only transcode on Android is sufficient for typical "fit under N MB" use cases.
  • Audio bitrate is exposed end-to-end, but on Android the Transformer encoder factory is currently configured for video bitrate; audio uses Media3 defaults.
  • Extremely aggressive ratios (e.g. 1 GB → 1 MB) will return a "best effort" result; the plugin does not lie about hitting the target when the encoder cannot.

Architecture #

lib/
  media_reduce.dart                          # public API
  src/
    domain/
      entities/                              # ReductionRequest, ReductionResult, MediaInfo, MediaKind
      repositories/native_media_gateway.dart # port (interface)
      use_cases/reduce_media_to_max_bytes_use_case.dart
    data/
      method_channel_native_media_gateway.dart # adapter
android/  # Kotlin: ImageCompressor, VideoCompressor (Media3), MediaInfoReader
ios/      # Swift:  ImageIO + AVAssetReader/Writer pipeline

License #

MIT

0
likes
150
points
10
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Iteratively compresses images and videos toward a maximum byte budget on Android and iOS, so files fit API upload limits before sending.

Homepage

Topics

#media #compression #video #image #upload

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on media_reduce

Packages that implement media_reduce