Flutter视频帧导出插件export_video_frame的使用

Flutter视频帧导出插件export_video_frame的使用

export_video_frame是一个用于iOS和Android平台从视频文件中导出图片的Flutter插件。本文将介绍如何安装、配置以及使用这个插件,并提供一个完整的示例Demo。

安装

首先,在你的pubspec.yaml文件中添加export_video_frame作为依赖项:

dependencies:
  export_video_frame: ^latest_version

请确保将^latest_version替换为实际的最新版本号。

使用

导入包

在开始之前,你需要导入必要的包:

import 'package:export_video_frame/export_video_frame.dart';
import 'package:image_picker/image_picker.dart';

示例代码

下面是一个完整的示例Demo,展示了如何使用export_video_frame来导出视频帧并显示在UI上。

import 'dart:io';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:export_video_frame/export_video_frame.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "Plugin Example App",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(images: <Image>[]),
    );
  }
}

class ImageItem extends StatelessWidget {
  ImageItem({required this.image}) : super(key: ObjectKey(image));
  final Image image;

  @override
  Widget build(BuildContext context) {
    return Container(child: image);
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.images}) : super(key: key);

  final List<Image> images;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var _isClean = false;
  final ImagePicker _picker = ImagePicker();
  Stream<File>? _imagesStream;

  Future _getImagesByTime() async {
    final PickedFile? file = await _picker.getVideo(
        source: ImageSource.gallery, maxDuration: const Duration(seconds: 10));
    _imagesStream = ExportVideoFrame.exportImagesFromFile(
      File(file!.path),
      const Duration(milliseconds: 500),
      pi / 2,
    );

    setState(() {
      _isClean = true;
    });

    _imagesStream!.listen((image) {
      setState(() {
        widget.images.add(Image.file(image));
      });
    });
  }

  Future _getImages() async {
    final PickedFile? file = await _picker.getVideo(
        source: ImageSource.gallery, maxDuration: const Duration(seconds: 10));
    var images = await ExportVideoFrame.exportImage(file!.path, 10, 0);
    var result = images.map((file) => Image.file(file)).toList();
    setState(() {
      widget.images.addAll(result);
      _isClean = true;
    });
  }

  Future _getGifImages() async {
    final PickedFile? file =
        await _picker.getImage(source: ImageSource.gallery);

    var images = await ExportVideoFrame.exportGifImage(file!.path, 0);
    var result = images.map((file) => Image.file(file)).toList();
    setState(() {
      widget.images.addAll(result);
      _isClean = true;
    });
  }

  Future _getImagesByDuration() async {
    final PickedFile? pickedFile =
        await _picker.getImage(source: ImageSource.gallery);
    final File file = File(pickedFile!.path);
    var duration = Duration(seconds: 1);
    var image =
        await ExportVideoFrame.exportImageBySeconds(file, duration, pi / 2);
    setState(() {
      widget.images.add(Image.file(image));
      _isClean = true;
    });
    await ExportVideoFrame.saveImage(image, "Video Export Demo",
        waterMark: "images/water_mark.png",
        alignment: Alignment.bottomLeft,
        scale: 2.0);
  }

  Future _cleanCache() async {
    var result = await ExportVideoFrame.cleanImageCache();
    print(result);
    setState(() {
      widget.images.clear();
      _isClean = false;
    });
  }

  Future _handleClickFirst() async {
    if (_isClean) {
      await _cleanCache();
    } else {
      await _getImages();
    }
  }

  Future _handleClickSecond() async {
    if (_isClean) {
      await _cleanCache();
    } else {
      await _getImagesByDuration();
    }
  }

  Future _handleClickThird() async {
    if (_isClean) {
      await _cleanCache();
    } else {
      await _getGifImages();
    }
  }

  Future _handleClickFourth() async {
    if (_isClean) {
      await _cleanCache();
    } else {
      await _getImagesByTime();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Export Image"),
      ),
      body: Container(
        padding: EdgeInsets.zero,
        child: Column(
          children: <Widget>[
            Expanded(
              flex: 1,
              child: GridView.extent(
                  maxCrossAxisExtent: 400,
                  childAspectRatio: 1.0,
                  padding: const EdgeInsets.all(4),
                  mainAxisSpacing: 4,
                  crossAxisSpacing: 4,
                  children: widget.images.length > 0
                      ? widget.images
                          .map((image) => ImageItem(image: image))
                          .toList()
                      : [Container()]),
            ),
            Expanded(
              flex: 0,
              child: Center(
                child: MaterialButton(
                  padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
                  height: 40,
                  minWidth: 100,
                  onPressed: () {
                    _handleClickFirst();
                  },
                  color: Colors.orange,
                  child: Text(_isClean ? "Clean" : "Export image list"),
                ),
              ),
            ),
            Expanded(
              flex: 0,
              child: Center(
                child: MaterialButton(
                  padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
                  height: 40,
                  minWidth: 150,
                  onPressed: () {
                    _handleClickSecond();
                  },
                  color: Colors.orange,
                  child: Text(_isClean ? "Clean" : "Export one image and save"),
                ),
              ),
            ),
            Expanded(
              flex: 0,
              child: Center(
                child: MaterialButton(
                  padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
                  height: 40,
                  minWidth: 150,
                  onPressed: () {
                    _handleClickThird();
                  },
                  color: Colors.orange,
                  child: Text(_isClean ? "Clean" : "Export gif image"),
                ),
              ),
            ),
            Expanded(
              flex: 0,
              child: Center(
                child: MaterialButton(
                  padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
                  height: 40,
                  minWidth: 150,
                  onPressed: () {
                    _handleClickFourth();
                  },
                  color: Colors.orange,
                  child: Text(_isClean ? "Clean" : "Export images by interval"),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

iOS配置

如果你在编译iOS应用时遇到错误,请编辑ios/Podfile文件如下:

target 'Runner' do
  use_frameworks! # <-- add this
  ...
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ENABLE_BITCODE'] = 'NO'
      config.build_settings['SWIFT_VERSION'] = '4.2' # <-- add this
    end
  end
end

如果你使用了保存相册的功能,还需要在ios/Runner/Info.plist中添加以下键值:

<key>NSPhotoLibraryUsageDescription</key>
<string>Use Ablum For your purpose</string>

Android配置

确保你在AndroidManifest.xml中添加了所需的权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

更多关于Flutter视频帧导出插件export_video_frame的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视频帧导出插件export_video_frame的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用export_video_frame插件来导出视频帧的示例代码。这个插件允许你从视频文件中提取帧并保存为图像文件。

首先,确保你已经在pubspec.yaml文件中添加了export_video_frame依赖:

dependencies:
  flutter:
    sdk: flutter
  export_video_frame: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来获取依赖。

接下来,以下是一个完整的示例代码,展示如何使用export_video_frame插件来导出视频帧:

import 'package:flutter/material.dart';
import 'package:export_video_frame/export_video_frame.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: VideoFrameExporterPage(),
    );
  }
}

class VideoFrameExporterPage extends StatefulWidget {
  @override
  _VideoFrameExporterPageState createState() => _VideoFrameExporterPageState();
}

class _VideoFrameExporterPageState extends State<VideoFrameExporterPage> {
  String? _outputDirectory;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Video Frame Exporter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: _exportVideoFrame,
              child: Text('Export Video Frame'),
            ),
            Text(_outputDirectory ?? 'No frames exported yet.'),
          ],
        ),
      ),
    );
  }

  Future<void> _exportVideoFrame() async {
    // 指定视频文件路径(这里使用本地资源路径,实际应用中可以是任何有效的视频文件路径)
    final videoPath = 'assets/sample_video.mp4'; // 请确保在pubspec.yaml中声明了assets
    // 或者使用设备上的文件路径,例如:
    // final videoPath = '/storage/emulated/0/DCIM/Camera/sample_video.mp4';

    // 获取应用文档目录路径
    final directory = await getApplicationDocumentsDirectory();
    final outputPath = directory.path;

    setState(() {
      _outputDirectory = outputPath;
    });

    // 创建导出器实例
    final exporter = VideoFrameExporter();

    try {
      // 导出第10秒(10000毫秒)的视频帧
      final frame = await exporter.exportFrameAtTime(
        videoPath: videoPath,
        time: Duration(seconds: 10),
        outputPath: '$outputPath/frame_${DateTime.now().millisecondsSinceEpoch}.png',
      );

      // 输出帧文件路径
      print('Exported frame to: $frame');
      setState(() {
        _outputDirectory = 'Frame exported to: $frame';
      });
    } catch (e) {
      print('Error exporting frame: $e');
      setState(() {
        _outputDirectory = 'Error: $e';
      });
    }
  }
}

注意事项:

  1. 视频文件路径:在示例中,视频路径被硬编码为assets/sample_video.mp4。如果你使用设备上的视频文件,请确保你有文件的访问权限,并修改路径为实际文件路径。
  2. 权限问题:如果视频文件位于设备的存储上,你可能需要在AndroidManifest.xml中请求存储权限,并在运行时请求权限。
  3. assets配置:如果你使用Flutter assets中的视频文件,请确保在pubspec.yaml中正确声明了assets。
flutter:
  assets:
    - assets/sample_video.mp4
  1. 错误处理:示例中包含了基本的错误处理,但在实际应用中,你可能需要更详细的错误处理逻辑。

  2. 依赖:示例代码使用了path_provider插件来获取应用的文档目录路径。如果你还没有添加这个依赖,请确保在pubspec.yaml中添加它。

dependencies:
  path_provider: ^最新版本号  # 请替换为实际的最新版本号

这个示例展示了如何使用export_video_frame插件从视频中导出帧,并将其保存到应用的文档目录中。希望这对你有所帮助!

回到顶部