Flutter语音录制插件flutter_voice_recorder的使用

Flutter语音录制插件flutter_voice_recorder的使用

flutter_voice_recorder 是一个支持 RecordPauseResumeStop 功能的 Flutter 插件,并且提供了音频级别表征属性 average powerpeak power 的访问。

功能支持

该插件适用于 AndroidiOS 平台。

功能展示

安装

pubspec.yaml 文件中添加 flutter_voice_recorder 依赖:

dependencies:
  flutter_voice_recorder: ^版本号

然后运行 flutter pub get 来安装依赖。

iOS权限配置

  1. Info.plist 文件中添加麦克风使用描述:

    <key>NSMicrophoneUsageDescription</key>
    <string>Can We Use Your Microphone Please</string>
    
  2. 使用 hasPermission API 请求用户授权:

    bool hasPermission = await FlutterVoiceRecorder.hasPermissions;
    

Android权限配置

  1. AndroidManifest.xml 文件中添加录音和存储权限:

    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    
  2. 使用 hasPermission API 请求用户授权:

    bool hasPermission = await FlutterVoiceRecorder.hasPermissions;
    

配置

iOS

iOS 部署目标应为 8.0 或更高版本。

Android

  • 使用最新版本(0.5.x)的 AndroidX。
  • 使用旧版本(0.4.9)的 Legacy Android。

使用方法

推荐的API使用顺序:hasPermission => init => start -> (pause <-> resume) * n -> stop。每次开始新的录制前,应再次调用 init 方法。

检查权限

首先检查权限,如果权限尚未设置为 truefalse,则会请求权限;否则,将返回录制权限的结果。

bool hasPermission = await FlutterVoiceRecorder.hasPermissions;

初始化

在开始录制之前,运行初始化代码,以检查给定名称的文件是否已存在。

var recorder = FlutterVoiceRecorder("file_path.mp4"); // .wav .aac .m4a
await recorder.initialized;

或者指定音频格式:

var recorder = FlutterVoiceRecorder("file_path", audioFormat: AudioFormat.AAC); // or AudioFormat.WAV
await recorder.initialized;

也可以指定采样率:

var recorder = FlutterVoiceRecorder("file_path", audioFormat: AudioFormat.AAC, sampleRate: 22000); // sampleRate 默认为 16000
await recorder.initialized;

开始录制

await recorder.start();
var recording = await recorder.current(channel: 0);

获取录制详情

可以使用定时器每50毫秒访问一次录制详情(录制完成后取消定时器):

new Timer.periodic(tick, (Timer t) async {
    var current = await recording.current(channel: 0);
    // print(current.status);
    setState(() {
    });
});

录制对象的属性

Recording 类

名称 描述
path String
extension String
duration Duration
audioFormat AudioFormat
metering AudioMetering
status RecordingStatus

Recording.metering 类

名称 描述
peakPower double
averagePower double
isMeteringEnabled bool

Recording.status 类

  • Unset
  • Initialized
  • Recording
  • Paused
  • Stopped

暂停录制

await recorder.pause();

恢复录制

await recorder.resume();

停止录制

停止后,需要重新调用 init 方法来创建新的录制:

var result = await recorder.stop();
File file = widget.localFileSystem.file(result.path);

示例代码

以下是一个完整的示例代码,展示了如何使用 flutter_voice_recorder 插件进行语音录制。

// ignore_for_file: library_private_types_in_public_api, avoid_print, use_build_context_synchronously, depend_on_referenced_packages

import 'dart:async';
import 'dart:io' as io;

import 'package:audioplayers/audioplayers.dart';
import 'package:file/file.dart';
import 'package:file/local.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_voice_recorder/flutter_voice_recorder.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: [
    SystemUiOverlay.bottom,
    SystemUiOverlay.top,
  ]);
  return runApp(const MyApp());
}

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

  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: RecorderExample(),
        ),
      ),
    );
  }
}

class RecorderExample extends StatefulWidget {
  final LocalFileSystem localFileSystem;

  const RecorderExample({super.key, localFileSystem})
      : localFileSystem = localFileSystem ?? const LocalFileSystem();

  [@override](/user/override)
  State<StatefulWidget> createState() => RecorderExampleState();
}

class RecorderExampleState extends State<RecorderExample> {
  FlutterVoiceRecorder? _recorder;
  Recording? _current;
  RecordingStatus _currentStatus = RecordingStatus.Unset;

  [@override](/user/override)
  void initState() {
    super.initState();
    _init();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextButton(
                      onPressed: () {
                        switch (_currentStatus) {
                          case RecordingStatus.Initialized:
                            {
                              _start();
                              break;
                            }
                          case RecordingStatus.Recording:
                            {
                              _pause();
                              break;
                            }
                          case RecordingStatus.Paused:
                            {
                              _resume();
                              break;
                            }
                          case RecordingStatus.Stopped:
                            {
                              _init();
                              break;
                            }
                          default:
                            break;
                        }
                      },
                      style: ButtonStyle(
                          backgroundColor: WidgetStateProperty.all<Color>(
                        Colors.lightBlue,
                      )),
                      child: _buildText(_currentStatus),
                    ),
                  ),
                  TextButton(
                    onPressed:
                        _currentStatus != RecordingStatus.Unset ? _stop : null,
                    style: ButtonStyle(
                        backgroundColor: WidgetStateProperty.all<Color>(
                      Colors.blueAccent.withOpacity(0.5),
                    )),
                    child: const Text("Stop",
                        style: TextStyle(color: Colors.white)),
                  ),
                  const SizedBox(
                    width: 8,
                  ),
                  TextButton(
                    onPressed: onPlayAudio,
                    style: ButtonStyle(
                        backgroundColor: WidgetStateProperty.all<Color>(
                      Colors.blueAccent.withOpacity(0.5),
                    )),
                    child: const Text("Play",
                        style: TextStyle(color: Colors.white)),
                  ),
                ],
              ),
              Text("Status : $_currentStatus"),
              Text('Avg Power: ${_current?.metering?.averagePower}'),
              Text('Peak Power: ${_current?.metering?.peakPower}'),
              Text("File path of the record: ${_current?.path}"),
              Text("Format: ${_current?.audioFormat}"),
              Text(
                  "isMeteringEnabled: ${_current?.metering?.isMeteringEnabled}"),
              Text("Extension : ${_current?.extension}"),
              Text(
                  "Audio recording duration : ${_current?.duration.toString()}")
            ]),
      ),
    );
  }

  _init() async {
    try {
      bool hasPermission = await FlutterVoiceRecorder.hasPermissions ?? false;

      if (hasPermission) {
        String customPath = '/flutter_audio_recorder_';
        io.Directory appDocDirectory;
//        io.Directory appDocDirectory = await getApplicationDocumentsDirectory();
        if (io.Platform.isIOS) {
          appDocDirectory = await getApplicationDocumentsDirectory();
        } else {
          appDocDirectory = (await getExternalStorageDirectory())!;
        }

        // 可以添加扩展名如 ".mp4" ".wav" ".m4a" ".aac"
        customPath = appDocDirectory.path +
            customPath +
            DateTime.now().millisecondsSinceEpoch.toString();

        // .wav &lt;---&gt; AudioFormat.WAV
        // .mp4 .m4a .aac &lt;---&gt; AudioFormat.AAC
        // AudioFormat 是可选的,如果给定了值,将会覆盖路径扩展名。
        _recorder =
            FlutterVoiceRecorder(customPath, audioFormat: AudioFormat.WAV);

        await _recorder!.initialized;
        // 初始化后
        var current = await _recorder!.current(channel: 0);
        print(current);
        // 应该是 "Initialized",如果一切正常
        setState(() {
          _current = current;
          _currentStatus = current!.status!;
          print(_currentStatus);
        });
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text("You must accept permissions")));
      }
    } catch (e) {
      print(e);
    }
  }

  _start() async {
    try {
      await _recorder!.start();
      var recording = await _recorder!.current(channel: 0);
      setState(() {
        _current = recording;
      });

      const tick = Duration(milliseconds: 50);
      Timer.periodic(tick, (Timer t) async {
        if (_currentStatus == RecordingStatus.Stopped) {
          t.cancel();
        }

        var current = await _recorder!.current(channel: 0);
        // print(current.status);
        setState(() {
          _current = current;
          _currentStatus = _current!.status!;
        });
      });
    } catch (e) {
      print(e);
    }
  }

  _resume() async {
    await _recorder!.resume();
    setState(() {});
  }

  _pause() async {
    await _recorder!.pause();
    setState(() {});
  }

  _stop() async {
    var result = await _recorder!.stop();
    print("Stop recording: ${result!.path}");
    print("Stop recording: ${result.duration}");
    File file = widget.localFileSystem.file(result.path);
    print("File length: ${await file.length()}");
    setState(() {
      _current = result;
      _currentStatus = _current!.status!;
    });
  }

  Widget _buildText(RecordingStatus status) {
    var text = "";
    switch (_currentStatus) {
      case RecordingStatus.Initialized:
        {
          text = 'Start';
          break;
        }
      case RecordingStatus.Recording:
        {
          text = 'Pause';
          break;
        }
      case RecordingStatus.Paused:
        {
          text = 'Resume';
          break;
        }
      case RecordingStatus.Stopped:
        {
          text = 'Init';
          break;
        }
      default:
        break;
    }
    return Text(text, style: const TextStyle(color: Colors.white));
  }

  void onPlayAudio() async {
    AudioPlayer audioPlayer = AudioPlayer();
    await audioPlayer.setSourceAsset(_current!.path.toString());
  }
}

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

1 回复

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


当然,下面是一个关于如何使用 flutter_voice_recorder 插件进行语音录制的代码示例。这个示例将展示如何初始化插件、开始录制、停止录制以及获取录制的音频文件路径。

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

dependencies:
  flutter:
    sdk: flutter
  flutter_voice_recorder: ^x.y.z  # 请替换为最新版本号

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

接下来,在你的 Flutter 应用中实现语音录制功能。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_voice_recorder/flutter_voice_recorder.dart';
import 'package:path_provider/path_provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Voice Recorder Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: VoiceRecorderScreen(),
    );
  }
}

class VoiceRecorderScreen extends StatefulWidget {
  @override
  _VoiceRecorderScreenState createState() => _VoiceRecorderScreenState();
}

class _VoiceRecorderScreenState extends State<VoiceRecorderScreen> {
  late FlutterVoiceRecorder _voiceRecorder;
  late String _recordingPath;
  bool _isRecording = false;

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

  Future<void> _initRecorder() async {
    final Directory appDir = await getApplicationDocumentsDirectory();
    String dirPath = appDir.path;
    bool hasPermission = await _voiceRecorder.hasPermissions;
    if (!hasPermission) {
      bool requestResult = await _voiceRecorder.requestPermissions;
      if (!requestResult) {
        // Handle permission denied
        return;
      }
    }

    _voiceRecorder = FlutterVoiceRecorder()
      ..startRecorder(dirPath: dirPath, fileName: 'audio_record')
      ..setSubscriptionDurationInMs(500);

    _voiceRecorder.onCurrentPositionChanged.listen((duration) {
      // Handle current position changes
      print("Current position: $duration");
    });

    _voiceRecorder.onCompleted.listen((rec) {
      setState(() {
        _isRecording = false;
        _recordingPath = rec.path;
      });
      print("Recording completed at ${rec.path}");
    });

    _voiceRecorder.onError.listen((err) {
      print("Error: ${err.message}");
    });
  }

  Future<void> _startRecording() async {
    if (!_isRecording) {
      setState(() {
        _isRecording = true;
      });
      await _voiceRecorder.start();
    }
  }

  Future<void> _stopRecording() async {
    if (_isRecording) {
      setState(() {
        _isRecording = false;
      });
      await _voiceRecorder.stop();
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Voice Recorder'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: _isRecording ? _stopRecording : _startRecording,
              child: Text(_isRecording ? 'Stop Recording' : 'Start Recording'),
            ),
            SizedBox(height: 20),
            if (_recordingPath.isNotEmpty)
              Text('Recording saved at: $_recordingPath'),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _voiceRecorder.dispose();
    super.dispose();
  }
}

在这个示例中,我们做了以下几件事情:

  1. 初始化插件:在 initState 方法中,我们请求并检查录音权限,然后初始化 FlutterVoiceRecorder 实例,并设置录音文件保存的路径和文件名。同时,我们订阅了录音的当前位置变化、完成和错误事件。

  2. 开始和停止录音:提供了 _startRecording_stopRecording 方法来控制录音的开始和停止。

  3. UI界面:在界面上,有一个按钮用于控制录音的开始和停止,如果录音完成,会显示录音文件的保存路径。

  4. 资源清理:在 dispose 方法中,我们调用 _voiceRecorder.dispose() 来释放资源。

这个示例应该能帮助你快速上手 flutter_voice_recorder 插件的使用。如果你需要更高级的功能,比如录音格式设置、音量指示器等,可以参考插件的官方文档。

回到顶部