Flutter音频编辑插件sound_edit的使用

Flutter音频编辑插件sound_edit的使用

功能介绍

此功能允许您不受文件扩展限制地合并音频文件,并且可以编辑播放时间。

sound_edit 图片

开始使用

运行命令以启动应用

运行以下命令以输出在assets目录中注册的音频文件到屏幕。

./build.sh && cd example && flutter run

如果你想修剪或录制声音,请决定文件名

示例代码

示例代码位置

示例代码位于 example/lib/main.dart

示例代码

import 'dart:async' show Future, StreamController;

import 'package:flutter/material.dart';
import "package:sound_edit/sound_edit_animation_view.dart";
import 'package:sound_edit/sound_edit_channel.dart';
import 'package:sound_edit/sound_edit_dialog.dart';
import 'package:sound_edit/sound_edit_gifImage.dart';
import 'package:sound_edit/sound_edit_override_widget.dart';
import 'package:sound_edit/sound_edit_slider.dart';

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

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

  // 这个小部件是你的应用的根。
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const MyHomePage(),
      theme: ThemeData(
        splashColor: Colors.transparent,
      ),
    );
  }
}

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

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with ChangeNotifier, TickerProviderStateMixin, WidgetsBindingObserver {
  var _time = 0.0;
  var _max = 1.0;
  var _currentRangeValues = const RangeValues(
    0.0,
    1.0,
  );
  var _soundName = '';
  var _rangeController = StreamController.broadcast();
  final _dialog = SoundEditDialog();
  final SoundEditChanel _methodChanel = SoundEditChanel();
  final SoundEditOverrideWidget _slideUpController = SoundEditOverrideWidget();

  [@override](/user/override)
  void didChangeDependencies() {
    super.didChangeDependencies();
    WidgetsBinding.instance.addObserver(this);
    Future(() async {
      _methodChanel.getAppDocumentDirectoryContent();
      _methodChanel.fileList = await _methodChanel.getAudioFiles();
    });
    Future.delayed(const Duration(milliseconds: 200), () {
      _notify();
    });
  }

  [@override](/user/override)
  void dispose() {
    _rangeController.close();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  [@override](/user/override)
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.paused) {
      _playSound(['audioStop', 'music']);
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        centerTitle: true,
        title: GestureDetector(
          onTap: () => _showActionSheet(context),
          child: _appBar(),
        ),
        backgroundColor: Colors.black,
      ),
      body: Stack(
        alignment: Alignment.center,
        children: [
          SizedBox(
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Image.asset(
              'assets/backImage.png',
              fit: BoxFit.contain,
            ),
          ),
          _listFile(),
          if (_methodChanel.animationFlg) ...[
            SizedBox(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              child: SoundEditAnimationView(
                  frameCount: 149, c: SoundEditGifController(vsync: this)),
            ),
          ],
        ],
      ),
    );
  }

  _listFile() {
    return GridView.builder(
      itemCount: _methodChanel.fileList.length,
      itemBuilder: (context, index) {
        return ListTile(
            onTap: () async {
              if (!_methodChanel.animationFlg) {
                _methodChanel.animationFlg = true;
                _notify();
                await _methodChanel
                    .playSoundChoice(
                        'play/${_methodChanel.fileList[index].path.split("/").last}',
                        'music')
                    .whenComplete(
                      () async =>
                          {_methodChanel.animationFlg = false, _notify()},
                    );
              }
            },
            title: Center(
              child: _dragItem(index),
            ));
      },
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 1.5,
      ),
    );
  }

  /// 拖拽 StreamBuilder
  _dragTarget() {
    return StreamBuilder(
        stream: _rangeController.stream,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          return DragTarget<String>(
              builder: (context, candidateData, rejectedData) {
            return Material(
                color: Colors.transparent,
                child: Padding(
                  padding: const EdgeInsets.only(
                    top: 24,
                  ),
                  child: Text(
                    _methodChanel.dragdrop.isEmpty
                        ? 'Drop here'
                        : _methodChanel.dragdrop.join(','),
                    style: const TextStyle(fontSize: 28, color: Colors.green),
                  ),
                ));
          }, onAccept: (data) {
            _methodChanel
              ..dragdrop.add(data)
              ..playSoundChoice(
                      'drag/${_methodChanel.dragdrop.join(',')},$_soundName.wav',
                      'drag')
                  .then(
                (value) {
                  if (value == 1.0) {
                    _dialog.errorSameAlertDialog(context);
                    _reload();
                  } else {
                    _reloadDragdrop(value);
                  }
                },
              );
          });
        });
  }

  _dragItem(int index) {
    var t = Text(
      _methodChanel.fileList[index].path.split("/").last,
      style: const TextStyle(
        color: Colors.green,
      ),
    );
    return Draggable<String>(
      data: _methodChanel.fileList[index].path.split("/").last,
      feedback: Material(
        child: ColoredBox(
          color: Colors.black,
          child: t,
        ),
      ),
      childWhenDragging: t,
      child: t,
    );
  }

  _widget(Map<String, String> listMap) {
    final chanelPath = listMap['chanelPath'] ?? '';
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceAround,
      children: [
        TextButton(
          onPressed: () async {
            switch (chanelPath) {
              case 'changeName':
                _dialog.nameSelectAlertDialog(
                    context, (p0) => {_soundName = p0, _notify()});
                break;
              case 'trim':
                _judgeName(
                  () => _methodChanel
                      .playSoundChoice(
                    'trim/${_methodChanel.dragdrop.join(',')}, ${(_currentRangeValues.start).toDouble()}, ${(_currentRangeValues.end).toDouble()}, $_soundName.wav',
                    'trim',
                  )
                      .then(
                    (value) {
                      if (value == 1.0) {
                        _dialog.errorSameAlertDialog(context);
                        _reload();
                      } else {
                        _reload(flg: true);
                        _reloadDragdrop(value);
                      }
                    },
                  ),
                );
                break;
              case 'audioPause':
                await _playSound(['audioPause', 'music']);
                break;
              case 'audioStop':
                await _playSound(['audioStop', 'music']);
                break;
              case 'record':
                await _judgeName(
                  () async => await _playSound([
                    '$_soundName.wav',
                    'record'
                  ]),
                );
                break;
              case 'recordStop':
                await _playSound(['recordStop', 'record']);
                break;
            }
          },
          child: Text(
            chanelPath,
            style: const TextStyle(color: Colors.green),
          ),
        ),
      ],
    );
  }

  _appBar() {
    return Container(
      width: 40,
      height: 40,
      decoration: const BoxDecoration(
        shape: BoxShape.circle,
        image: DecorationImage(
          fit: BoxFit.contain,
          image: AssetImage('assets/icon.png'),
        ),
      ),
    );
  }

  _sliderBar() {
    return StreamBuilder(
        stream: _rangeController.stream,
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          return Material(
            color: Colors.transparent,
            child: Column(
              children: [
                SoundEditSlider(
                  start: _currentRangeValues.start,
                  end: _currentRangeValues.end,
                  min: 0,
                  max: _max,
                  color: Colors.green,
                  callback: (p0) {
                    _currentRangeValues = RangeValues(
                      p0.start,
                      p0.end,
                    );
                  },
                ),
                Container(
                  color: Colors.transparent,
                  padding: const EdgeInsets.only(
                    top: 24,
                  ),
                  child: Text('playTime:$_time'),
                )
              ],
            ),
          );
        });
  }

  _playSound(List<String> list) {
    if (list.last == 'music') {
      _methodChanel.animationFlg = false;
      _notify();
    }
    _methodChanel
        .playSoundChoice(list.first, list.last)
        .whenComplete(() => _reload());
  }

  /// 初始化重新加载
  Future<void> _reload({bool? flg}) async {
    _methodChanel.fileList = await _methodChanel.getAudioFiles();
    _methodChanel.dragdrop.clear();
    _soundName = '';
    _time = 0.0;
    if (flg == null) {
      _max = 1.0;
      _currentRangeValues = const RangeValues(0.0, 1.0);
      _notify();
    }
  }

  /// 重新加载新数据
  Future<void> _reloadDragdrop(double value) async {
    _currentRangeValues = RangeValues(0.0, value * 100);
    _max = value * 100;
    _time = value;
    _rangeController.add(_currentRangeValues);
    _notify();
  }

  /// 声音名称检查
  _judgeName(VoidCallback call) {
    if (_soundName.isEmpty) {
      _dialog.errorAlertDialog(context);
    } else {
      call();
    }
  }

  /// setState
  _notify() {
    setState(
      () => notifyListeners(),
    );
  }

  _showActionSheet(BuildContext context) {
    if (_rangeController.hasListener) {
      _rangeController.close();
    } else {
      _rangeController = StreamController.broadcast();
    }
    _slideUpController.show(
      context,
      Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          _dragTarget(),
          _sliderBar(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _widget({'chanelPath': 'changeName'}),
              _widget({'chanelPath': 'trim'}),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _widget({'chanelPath': 'audioPause'}),
              _widget({'chanelPath': 'audioStop'}),
            ],
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _widget({'chanelPath': 'record'}),
              _widget({'chanelPath': 'recordStop'}),
            ],
          ),
          const SizedBox(
            height: 40,
          )
        ],
      ),
      200, // 设置速度
    );
  }
}

更多关于Flutter音频编辑插件sound_edit的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter音频编辑插件sound_edit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用Flutter音频编辑插件sound_edit的代码示例。这个插件提供了多种音频编辑功能,如裁剪、合并、静音等。请注意,实际使用前需要确保已在pubspec.yaml文件中添加了该依赖并运行flutter pub get

1. 添加依赖

首先,在你的pubspec.yaml文件中添加sound_edit依赖:

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

2. 导入插件

在你的Dart文件中导入sound_edit插件:

import 'package:sound_edit/sound_edit.dart';

3. 使用示例

以下是一个简单的示例,展示如何使用sound_edit插件进行音频裁剪:

import 'package:flutter/material.dart';
import 'package:sound_edit/sound_edit.dart';
import 'dart:io';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  File? _audioFile;
  File? _trimmedAudioFile;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Audio Edit Example'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              _audioFile == null
                  ? Text('No audio file selected.')
                  : Text('Audio file selected: ${_audioFile!.path}'),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () async {
                  // Open file picker to select an audio file
                  FilePickerResult? result = await FilePicker.platform.pickFiles(
                    type: FileType.audio,
                  );

                  if (result != null && result.files.isNotEmpty) {
                    File file = File(result.files.first.path!);
                    setState(() {
                      _audioFile = file;
                    });

                    // Trim audio example: from 5 seconds to 10 seconds
                    double startTime = 5.0; // in seconds
                    double endTime = 10.0; // in seconds
                    _trimmedAudioFile = await SoundEdit.trim(
                      audioPath: _audioFile!.path,
                      startTime: startTime,
                      endTime: endTime,
                    );

                    if (_trimmedAudioFile != null) {
                      print('Trimmed audio saved to: ${_trimmedAudioFile!.path}');
                    } else {
                      print('Failed to trim audio.');
                    }
                  }
                },
                child: Text('Select Audio File'),
              ),
              SizedBox(height: 20),
              _trimmedAudioFile == null
                  ? Container()
                  : ElevatedButton(
                      onPressed: () {
                        // Here you can do something with the trimmed audio file, e.g., play it or share it
                        print('Play or share trimmed audio: ${_trimmedAudioFile!.path}');
                      },
                      child: Text('Play/Share Trimmed Audio'),
                    ),
            ],
          ),
        ),
      ),
    );
  }
}

4. 注意事项

  • 确保你的应用有访问存储的权限(尤其是在Android和iOS上)。
  • 插件的最新版本和API可能会有所变化,请参考官方文档以获取最新信息。
  • 在实际项目中,处理文件时应考虑异常处理和用户反馈,以提高应用的健壮性和用户体验。

这个示例展示了如何使用sound_edit插件进行音频裁剪。根据需求,你还可以探索插件提供的其他功能,如合并、静音等。

回到顶部