ffmpeg_streamer
A high-performance Flutter plugin for using FFmpeg to decode video and audio frames via FFI. Now with asynchronous API and native threading for 7-10x faster batch operations!
โจ Features
Version 2.0 - New!
- ๐ Async API with Callbacks: Non-blocking frame retrieval with native threading
- โก Ultra-Fast Batch Processing: 87% faster for 100 frames (15s โ 2s)
- ๐งต Native Threading (pthread): Dedicated worker thread, zero UI blocking
- ๐ Progress Tracking: Real-time progress callbacks for batch operations
- ๐ Request Cancellation: Cancel pending decode requests
Core Features
- ๐ฅ Video Decoding: Access raw RGBA video frames
- ๐ Audio Decoding: Access raw Float32 audio samples
- ๐ฑ Cross-Platform: Android, iOS, macOS, Windows, Linux
- ๐ Backward Compatible: Old sync API still works
Prerequisites & Setup
IMPORTANT: This plugin requires FFmpeg binaries. You must provide them due to licensing and size.
Android
-
Download Android-compatible FFmpeg
.solibraries (e.g., from FFmpegKit or build yourself). -
Place them in your app's
android/source/main/jniLibs/<ABI>/or configure the pluginsrc/main/jniLibs. Required libraries:- libavformat.so
- libavcodec.so
- libavutil.so
- libswscale.so
- libswresample.so
Headers Place the FFmpeg include directories (libavcodec/, libavformat/, etc.) in:
android/src/main/cpp/include/So you should have
android/src/main/cpp/include/libavcodec/avcodec.h, etc.
iOS & macOS
- iOS: Add a Pod dependency on an FFmpeg package or vend
ffmpeg.xcframeworkin your Podfile. - macOS: Ensure FFmpeg is installed via Homebrew (
brew install ffmpeg) or linked in yourmacos/Runner.xcodeproj. - The plugin looks for headers in standard
/usr/local/includeor/opt/homebrew/includeon macOS.
Windows
- Set
FFMPEG_ROOTCMake variable or place FFmpeg headers/libs inwindows/ffmpeg. - Ensure
avcodec-*.dlletc. are in the same folder as your executable when running.
Linux
- Install development packages:
sudo apt-get install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev
๐ Quick Start
Basic Usage (Sync API)
import 'package:ffmpeg_streamer/ffmpeg_streamer.dart';
void main() async {
final decoder = FfmpegDecoder();
// Open media file
await decoder.openMedia('path/to/video.mp4');
// Get media info
print('Resolution: ${decoder.videoWidth}x${decoder.videoHeight}');
print('FPS: ${decoder.fps}');
print('Total frames: ${decoder.totalFrames}');
// Get a single frame (sync)
final frame = await decoder.getFrameAtIndex(42);
if (frame?.video != null) {
// Use frame.video.rgbaBytes (Uint8List)
displayFrame(frame!.video!);
}
// Cleanup
await decoder.dispose();
}
๐ฅ New Async API (Recommended for Performance)
import 'package:ffmpeg_streamer/ffmpeg_streamer.dart';
void main() async {
final decoder = FfmpegDecoder();
await decoder.openMedia('path/to/video.mp4');
// Get a single frame with callback (async, non-blocking)
decoder.getFrameAtIndexAsync(42, (frame) {
if (frame?.video != null) {
displayFrame(frame!.video!);
}
});
// Get multiple frames - ULTRA FAST! (87% faster than loop)
decoder.getFramesRangeByIndexAsync(
0, // start frame
99, // end frame
(frame) {
// Called for EACH frame as it's decoded
print('Received frame ${frame?.video?.frameId}');
processFrame(frame);
},
progressCallback: (current, total) {
// Real-time progress tracking
print('Progress: ${(current/total*100).toInt()}%');
},
);
await decoder.dispose();
}
๐ Performance Comparison
// โ OLD WAY (slow - 15 seconds for 100 frames)
for (int i = 0; i < 100; i++) {
final frame = await decoder.getFrameAtIndex(i);
processFrame(frame);
}
// โ
NEW WAY (fast - 2 seconds for 100 frames!)
decoder.getFramesRangeByIndexAsync(0, 99,
(frame) => processFrame(frame)
);
๐ฌ Real-World Example: Thumbnail Generator
void generateThumbnails(String videoPath, int count) async {
final decoder = FfmpegDecoder();
await decoder.openMedia(videoPath);
final thumbnails = <Image>[];
final step = decoder.totalFrames ~/ count;
decoder.getFramesRangeByIndexAsync(
0,
(count - 1) * step,
(frame) async {
if (frame?.video != null) {
final image = await frameToImage(frame!.video!);
thumbnails.add(image);
}
},
progressCallback: (current, total) {
updateProgressBar(current / total);
},
);
}
๐ Documentation
- Async API Guide - Complete guide to async API
- Migration Summary - Migrating from v1.x to v2.0
- Changelog - What's new in v2.0
- Example App - Full featured example
๐งช Testing Performance
Run the included performance test:
dart run test_async_perf.dart path/to/your/video.mp4
This will compare sync vs async performance and show you the improvements!
License
This plugin code is licensed under the MIT License. FFmpeg is licensed under LGPL or GPL. You are responsible for complying with the FFmpeg license in your final application.