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

A cross-platform Flutter FFI plugin that embeds FFmpeg to decode audio & video and stream raw frames — works on Android, iOS, macOS, Windows & Linux.

example/lib/main.dart

import 'dart:async';
import 'dart:ui' as ui;
import 'package:ffmpeg_streamer/ffmpeg_streamer.dart';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';

void main() {
  runApp(const MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  FfmpegDecoder? _decoder;
  MediaInfo? _mediaInfo;
  ui.Image? _currentFrame;
  bool _isPlaying = false;

  double _lastFrameTimestamp = 0;
  
  @override
  void dispose() {
    _decoder?.dispose();
    super.dispose();
  }

  Future<void> _pickFile() async {
    FilePickerResult? result = await FilePicker.platform.pickFiles();

    if (result != null && result.files.single.path != null) {
      await _openMedia(result.files.single.path!);
    }
  }

  Future<void> _openMedia(String path) async {
    // Cleanup previous
    await _decoder?.close();
    setState(() {
      _mediaInfo = null;
      _currentFrame = null;
      _isPlaying = false;
    });

    try {
      final decoder = FfmpegDecoder();
      await decoder.open(FfmpegMediaSource.fromFile(path));
      
      final info = await decoder.mediaInfo;
      
      decoder.videoFrames.listen((frame) {
        _renderFrame(frame);
      });

      setState(() {
        _decoder = decoder;
        _mediaInfo = info;
      });

      var frame = await decoder.getFrameAtIndex(0);

      if (frame != null) {
        _renderFrame(frame);
      }
      
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error opening media: $e')),
      );
    }
  }

  Future<void> _renderFrame(VideoFrame frame) async {
    final completer = Completer<ui.Image>();
    ui.decodeImageFromPixels(
      frame.rgbaBytes,
      frame.width,
      frame.height,
      ui.PixelFormat.rgba8888,
      (image) {
        completer.complete(image);
      },
    );
    final image = await completer.future;

    _lastFrameTimestamp = frame.frameId / _mediaInfo!.fps;
    if (_lastFrameTimestamp >= (_mediaInfo?.duration.inSeconds ?? 0)) {
      _lastFrameTimestamp = 0;
      _decoder?.seekToFrame(0);
      _decoder?.pause();
      _isPlaying = false;
    }

    if (mounted) {
      setState(() {
        _currentFrame = image;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('FFmpeg Streamer Example')),
      body: Column(
        children: [
          if (_mediaInfo != null)
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Text(
                  '${_mediaInfo!.width}x${_mediaInfo!.height} @ ${_mediaInfo!.fps.toStringAsFixed(2)} fps\nDuration: ${_mediaInfo!.duration} @ ${_mediaInfo?.totalFrames}'),
            ),
          Expanded(
            child: Center(
              child: _currentFrame != null
                  ? AspectRatio(
                      aspectRatio: _mediaInfo!.width / _mediaInfo!.height,
                      child: CustomPaint(
                        painter: VideoPainter(_currentFrame!),
                      ),
                    )
                  : const Text('No video loaded'),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _pickFile,
                  child: const Text('Pick File'),
                ),
                if (_decoder != null) ...[
                  ElevatedButton(
                    onPressed: () {
                      if (_isPlaying) {
                        _decoder!.pause();
                      } else {
                        _decoder!.play();
                      }
                      setState(() => _isPlaying = !_isPlaying);
                    },
                    child: Text(_isPlaying ? 'Pause' : 'Play'),
                  ),
                ]
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class VideoPainter extends CustomPainter {
  final ui.Image image;

  VideoPainter(this.image);

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawImageRect(
      image,
      Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
      Rect.fromLTWH(0, 0, size.width, size.height),
      Paint()..filterQuality = FilterQuality.low,
    );
  }

  @override
  bool shouldRepaint(covariant VideoPainter oldDelegate) {
    return image != oldDelegate.image;
  }
}
1
likes
150
points
155
downloads

Publisher

unverified uploader

Weekly Downloads

A cross-platform Flutter FFI plugin that embeds FFmpeg to decode audio & video and stream raw frames — works on Android, iOS, macOS, Windows & Linux.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

ffi, flutter, plugin_platform_interface

More

Packages that depend on ffmpeg_streamer

Packages that implement ffmpeg_streamer