搜索插件、代码仓与 APP 分类

easy_audio_trimmer 封面
插件详情v2.0.0

easy_audio_trimmer

一个用于剪辑音频的Flutter包。该包支持从文件系统中检索、剪辑和存储剪辑后的音频文件。

AndroidiOSmacOS
近 30 天下载
57
喜欢数
32
Pub 分数
160

安装插件

flutter pub add easy_audio_trimmer

插件介绍

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

Easy Audio Trimmer 是一个专为简化音频剪辑任务而设计的 Flutter 包。它使用户能够获取音频文件,在指定的开始和结束时间范围内进行剪辑,并将生成的片段保存到设备的文件系统中。该包支持 Android、iOS 和 macOS,适用于跨平台音频编辑应用程序。通过简单的 API,它可轻松集成到需要基本音频操作的应用程序中,无需复杂的设置。

适合场景

  • 音频片段创建
  • 播客编辑
  • 语音备忘录剪裁
  • 应用内音频处理
  • 社交媒体内容准备

主要特点

  • 简单的 API 用于修剪
  • 文件系统存储
  • 跨平台支持
  • 开始/结束时间选择
  • 音频文件检索

适合对象

  • 开发音频应用的开发者
  • 带有语音录制功能的教育工具
  • 需要快速编辑的内容创作者
  • 带有音频功能的移动应用
  • 需要轻量级音频编辑的应用程序

使用建议

  • 需要 Android 和 iOS 的文件权限
  • 仅限基本修剪操作
  • 无音频格式转换
  • 依赖原生平台音频处理
  • 可能需要为大文件添加额外的错误处理

原始 README

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

Pub Version GitHub stars GitHub license

Welcome to Easy Audio Trimmer! This is a Flutter package that allows you to easily trim audio files within your Flutter app.

Features
  • Trim audio files within your Flutter app
  • Specify the start and end times for the trimmed audio file
  • Audio playback control.
  • Retrieving and storing audio file.
  • Trimmed audio files are saved on the device
  • Easy to use with a simple function call
  • Compatible with most audio file formats (mp3, wav, aac, etc.)
  • Support for both iOS and Android platforms

Example

The example app running on android OnePlus 7:

Trimmer

Usage

Add the dependency easy_audio_trimmer to your pubspec.yaml file:

dependencies:
  easy_audio_trimmer: ^2.0.0
Android configuration

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

iOS configuration
  • Set the platform version in ios/Podfile to 10.

    Refer to the FFmpeg Release section.

    platform :ios, '10'
    

FFmpeg Release

This package uses LTS version of the FFmpeg implementation.

LTS Release
Android API Level 16
Android Camera Access -
Android Architectures arm-v7a
arm-v7a-neon
arm64-v8a
x86
x86-64
iOS Min SDK 10
iOS Architectures armv7
arm64
i386
x86-64

Functionalities

Loading input audio file
final Trimmer _trimmer = Trimmer();
await _trimmer.loadAudio(audioFile: widget.file);
Saving trimmed audio

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

_trimmer.saveTrimmedAudio(
      startValue: _startValue,
      endValue: _endValue,
      audioFileName: DateTime.now().millisecondsSinceEpoch.toString(),
      onSave: (outputPath) {
        setState(() {
          _progressVisibility = false;
        });
        debugPrint('OUTPUT PATH: $outputPath');
      },
    );
Audio playback state

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

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

Widgets

Display the audio trimmer area
TrimViewer(
  trimmer: _trimmer,
  viewerHeight: 50.0,
  viewerWidth: MediaQuery.of(context).size.width,
  maxAudioLength: const Duration(seconds: 10),
  onChangeStart: (value) => _startValue = value,
  onChangeEnd: (value) => _endValue = value,
  allowAudioSelection: true,
  onChangePlaybackState: (value) =>
      setState(() => _isPlaying = value),
)

Example

Before using this example directly in a Flutter app, don't forget to add the easy_audio_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:example/trimmer_view.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: createMaterialColor(const Color(0xFF332FD0)),
        ),
        home: const FileSelectorWidget());
  }
}

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

  @override
  State<FileSelectorWidget> createState() => _FileSelectorWidgetState();
}

class _FileSelectorWidgetState extends State<FileSelectorWidget> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Audio Trimmer"),
      ),
      body: Center(
        child: ElevatedButton(
            onPressed: () async {
              FilePickerResult? result = await FilePicker.pickFiles(
                type: FileType.audio,
              );
              if (result != null) {
                File file = File(result.files.single.path!);
                // ignore: use_build_context_synchronously
                Navigator.of(context).push(
                  MaterialPageRoute(builder: (context) {
                    return AudioTrimmerView(file);
                  }),
                );
              }
            },
            child: const Text("Select File")),
      ),
    );
  }
}

class AudioTrimmerView extends StatefulWidget {
  final File file;

  const AudioTrimmerView(this.file, {Key? key}) : super(key: key);
  @override
  State<AudioTrimmerView> createState() => _AudioTrimmerViewState();
}

class _AudioTrimmerViewState extends State<AudioTrimmerView> {
  final Trimmer _trimmer = Trimmer();

  double _startValue = 0.0;
  double _endValue = 0.0;

  bool _isPlaying = false;
  bool _progressVisibility = false;
  bool isLoading = false;

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

    _loadAudio();
  }

  void _loadAudio() async {
    setState(() {
      isLoading = true;
    });
    await _trimmer.loadAudio(audioFile: widget.file);
    setState(() {
      isLoading = false;
    });
  }

  _saveAudio() {
    setState(() {
      _progressVisibility = true;
    });

    _trimmer.saveTrimmedAudio(
      startValue: _startValue,
      endValue: _endValue,
      audioFileName: DateTime.now().millisecondsSinceEpoch.toString(),
      onSave: (outputPath) {
        setState(() {
          _progressVisibility = false;
        });
        debugPrint('OUTPUT PATH: $outputPath');
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        if (Navigator.of(context).userGestureInProgress) {
          return false;
        } else {
          return true;
        }
      },
      child: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(
          title: const Text("Audio Trimmer"),
        ),
        body: isLoading
            ? const CircularProgressIndicator()
            : Center(
                child: Container(
                  padding: const EdgeInsets.only(bottom: 30.0),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.max,
                    children: <Widget>[
                      Visibility(
                        visible: _progressVisibility,
                        child: LinearProgressIndicator(
                          backgroundColor:
                              Theme.of(context).primaryColor.withOpacity(0.5),
                        ),
                      ),
                      ElevatedButton(
                        onPressed:
                            _progressVisibility ? null : () => _saveAudio(),
                        child: const Text("SAVE"),
                      ),
                      AudioViewer(trimmer: _trimmer),
                      Center(
                        child: Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: TrimViewer(
                            trimmer: _trimmer,
                            viewerHeight: 100,
                            viewerWidth: MediaQuery.of(context).size.width,
                            durationStyle: DurationStyle.FORMAT_MM_SS,
                            backgroundColor: Theme.of(context).primaryColor,
                            barColor: Colors.white,
                            durationTextStyle: TextStyle(
                                color: Theme.of(context).primaryColor),
                            allowAudioSelection: true,
                            editorProperties: TrimEditorProperties(
                              circleSize: 10,
                              borderPaintColor: Colors.pink,
                              borderWidth: 4,
                              borderRadius: 5,
                              circlePaintColor: Colors.pink.shade800,
                            ),
                            areaProperties:
                                TrimAreaProperties.edgeBlur(blurEdges: true),
                            onChangeStart: (value) => _startValue = value,
                            onChangeEnd: (value) => _endValue = value,
                            onChangePlaybackState: (value) {
                              if (mounted) {
                                setState(() => _isPlaying = value);
                              }
                            },
                          ),
                        ),
                      ),
                      TextButton(
                        child: _isPlaying
                            ? Icon(
                                Icons.pause,
                                size: 80.0,
                                color: Theme.of(context).primaryColor,
                              )
                            : Icon(
                                Icons.play_arrow,
                                size: 80.0,
                                color: Theme.of(context).primaryColor,
                              ),
                        onPressed: () async {
                          bool playbackState =
                              await _trimmer.audioPlaybackControl(
                            startValue: _startValue,
                            endValue: _endValue,
                          );
                          setState(() => _isPlaying = playbackState);
                        },
                      )
                    ],
                  ),
                ),
              ),
      ),
    );
  }
}

Troubleshooting

While running on the Android platform if it gives an error that the minSdkVersion needs to be 24, or on iOS platform that the Podfile platform version should be 11, first go to pubspec.lock file and see if the version of ffmpeg_kit_flutter has -LTS suffix. This should fix all issues for iOS platform.

On Android, if you still face the same issue, try adding the following to the <project_directory>/android/app/src/main/AndroidManifest.xml:

<manifest xmlns:tools="http://schemas.android.com/tools" ....... >
    <uses-sdk tools:overrideLibrary="com.arthenica.ffmpegkit.flutter, com.arthenica.ffmpegkit" />
</manifest>

Sponsored by

Agochar Tech LLP https://agochar.com/
Credit and Acknowledgment

"We would like to thank the contributors to the Video Trimmer repository for their invaluable assistance in creating this project. We used code from the Video Trimmer tool to implement the trimming and editing features in this project, and we are grateful for the opportunity to use it in our work. Thank you to the team behind Video Trimmer for their valuable contributions."

License

Copyright (c) 2022 Mayur Pitroda

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.

所属分类

依赖

依赖清单 5 项

源码仓库

使用此插件的项目

暂无相关数据。