搜索分类、插件与代码仓

video_trimmer 封面
插件详情v5.0.0

video_trimmer

一个用于剪辑视频的Flutter包。该包支持获取、剪辑以及将剪辑后的视频文件存储到文件系统。

AndroidiOS
近 30 天下载
1.3万
喜欢数
505
Pub 分数
150

安装插件

flutter pub add video_trimmer

插件介绍

基于插件公开信息、社区数据与项目使用情况整理的选型参考。

一个专为移动应用程序中剪辑视频而设计的Flutter包。它使用户能够获取视频文件,在自定义的开始和结束时间之间进行剪辑,并将生成的片段保存到设备的文件系统中。该包支持Android和iOS平台,为应用内的多媒体编辑提供了无缝体验。非常适合需要基本视频操作且不依赖外部服务的应用程序。

适合场景

  • 裁剪用户上传的视频
  • 从较长的录制中创建短视频剪辑
  • 将视频编辑功能集成到社交媒体应用中
  • 准备视频内容以供分享
  • 编辑教程或演示视频
  • 将剪辑后的视频本地保存

主要特点

  • 从设备存储中检索视频
  • 通过精确的时间选择裁剪视频
  • 将剪辑后的视频保存到文件系统
  • 支持 Android 和 iOS
  • 简单的集成API
  • 保留原始视频质量

适合对象

  • 需要视频剪辑的移动应用
  • 社交媒体或内容创作工具
  • 教育或教程类应用
  • 处理用户生成视频内容的应用程序
  • 开发多媒体编辑器的开发者

使用建议

  • 需要在 Android 和 iOS 上的文件系统权限
  • 大视频文件可能会影响性能
  • 仅限基本修剪(无高级编辑)
  • 可能需要处理大文件大小
  • 依赖原生平台的视频处理功能

原始 README

以下为关联源码仓库的 README 原文快照,最新内容请访问源码仓库。

Awesome Flutter Pub Version GitHub stars GitHub license

Video Trimmer

A Flutter package for trimming videos

Features
  • Customizable video trimmer.
  • Supports two types of trim viewer, fixed length and scrollable.
  • Video playback control.
  • Retrieving and storing video file.

Also, supports conversion to GIF.

NOTE: Versions 5.0.0 and above uses a native video trimmer without the overhead of FFmpeg. Have a look at the Changelog for breaking changes if you are below version 5.0.0.

Following image shows the structure of the TrimViewer. It consists of the Duration on top (displaying the start, end, and scrubber time), TrimArea consisting of the thumbnails, and TrimEditor which is an overlay that let's you select a portion from the video.

Example

The example app running on an iPhone 13 Pro device:

Trimmer

Usage

Add the dependency video_trimmer to your pubspec.yaml file:

For using main version of FFmpeg package:

dependencies:
  video_trimmer: ^5.0.0
Android configuration

No additional configuration is needed for using on Android platform. You are good to go!

iOS configuration
  • Add the following keys to your Info.plist file, located in <project root>/ios/Runner/Info.plist:
    <key>NSCameraUsageDescription</key>
    <string>Used to demonstrate image picker plugin</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Used to capture audio for image picker plugin</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>Used to demonstrate image picker plugin</string>
    

Functionalities

Loading input video file
final Trimmer _trimmer = Trimmer();
await _trimmer.loadVideo(videoFile: file);
Saving trimmed video

Returns a string to indicate whether the saving operation was successful.

await _trimmer
    .saveTrimmedVideo(startValue: _startValue, endValue: _endValue)
    .then((value) => setState(() => _value = value));
Video playback state

Returns the video playback state. If true then the video is playing, otherwise it is paused.

await _trimmer.videoPlaybackControl(
  startValue: _startValue,
  endValue: _endValue,
);

Widgets

Display a video playback area
VideoViewer(trimmer: _trimmer)
Display the video trimmer area
TrimViewer(
  trimmer: _trimmer,
  viewerHeight: 50.0,
  viewerWidth: MediaQuery.of(context).size.width,
  maxVideoLength: const Duration(seconds: 10),
  onChangeStart: (value) => _startValue = value,
  onChangeEnd: (value) => _endValue = value,
  onChangePlaybackState: (value) =>
      setState(() => _isPlaying = value),
)

Example

Before using this example directly in a Flutter app, don't forget to add the video_trimmer & file_picker packages to your pubspec.yaml file.

You can try out this example by replacing the entire content of main.dart file of a newly created Flutter project.

import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:video_trimmer/video_trimmer.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Video Trimmer',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Video Trimmer"),
      ),
      body: Center(
        child: Container(
          child: ElevatedButton(
            child: Text("LOAD VIDEO"),
            onPressed: () async {
              FilePickerResult? result = await FilePicker.platform.pickFiles(
                type: FileType.video,
                allowCompression: false,
              );
              if (result != null) {
                File file = File(result.files.single.path!);
                Navigator.of(context).push(
                  MaterialPageRoute(builder: (context) {
                    return TrimmerView(file);
                  }),
                );
              }
            },
          ),
        ),
      ),
    );
  }
}

class TrimmerView extends StatefulWidget {
  final File file;

  TrimmerView(this.file);

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

class _TrimmerViewState extends State<TrimmerView> {
  final Trimmer _trimmer = Trimmer();

  double _startValue = 0.0;
  double _endValue = 0.0;

  bool _isPlaying = false;
  bool _progressVisibility = false;

  Future<String?> _saveVideo() async {
    setState(() {
      _progressVisibility = true;
    });

    String? _value;

    await _trimmer
        .saveTrimmedVideo(startValue: _startValue, endValue: _endValue)
        .then((value) {
      setState(() {
        _progressVisibility = false;
        _value = value;
      });
    });

    return _value;
  }

  void _loadVideo() {
    _trimmer.loadVideo(videoFile: widget.file);
  }

  @override
  void initState() {
    super.initState();

    _loadVideo();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Video Trimmer"),
      ),
      body: Builder(
        builder: (context) => Center(
          child: Container(
            padding: EdgeInsets.only(bottom: 30.0),
            color: Colors.black,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Visibility(
                  visible: _progressVisibility,
                  child: LinearProgressIndicator(
                    backgroundColor: Colors.red,
                  ),
                ),
                ElevatedButton(
                  onPressed: _progressVisibility
                      ? null
                      : () async {
                          _saveVideo().then((outputPath) {
                            print('OUTPUT PATH: $outputPath');
                            final snackBar = SnackBar(
                                content: Text('Video Saved successfully'));
                            ScaffoldMessenger.of(context).showSnackBar(
                              snackBar,
                            );
                          });
                        },
                  child: Text("SAVE"),
                ),
                Expanded(
                  child: VideoViewer(trimmer: _trimmer),
                ),
                Center(
                  child: TrimViewer(
                    trimmer: _trimmer,
                    viewerHeight: 50.0,
                    viewerWidth: MediaQuery.of(context).size.width,
                    maxVideoLength: const Duration(seconds: 10),
                    onChangeStart: (value) => _startValue = value,
                    onChangeEnd: (value) => _endValue = value,
                    onChangePlaybackState: (value) =>
                        setState(() => _isPlaying = value),
                  ),
                ),
                TextButton(
                  child: _isPlaying
                      ? Icon(
                          Icons.pause,
                          size: 80.0,
                          color: Colors.white,
                        )
                      : Icon(
                          Icons.play_arrow,
                          size: 80.0,
                          color: Colors.white,
                        ),
                  onPressed: () async {
                    bool playbackState = await _trimmer.videoPlaybackControl(
                      startValue: _startValue,
                      endValue: _endValue,
                    );
                    setState(() {
                      _isPlaying = playbackState;
                    });
                  },
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

License

Copyright (c) 2025 Souvik Biswas

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

所属分类

依赖

依赖清单 8 项

源码仓库

使用此插件的项目