-
Notifications
You must be signed in to change notification settings - Fork 616
Open
Description
When recording is paused and then resumed using toStream, toStream will no longer be called back when there is audio data.
flutter_sound: ^9.30.0
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:permission_handler/permission_handler.dart';
/*
This is a very simple example for Flutter Sound beginners,
hat shows how to record, and then playback a file.
This example is really basic.
*/
typedef _Fn = void Function();
enum RecordMethod { toFile, toStream }
/* This does not work. on Android we must have the Manifest.permission.CAPTURE_AUDIO_OUTPUT permission.
* But this permission is _is reserved for use by system components and is not available to third-party applications._
* Pleaser look to [this](https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_UPLINK)
*
* I think that the problem is because it is illegal to record a communication in many countries.
* Probably this stands also on iOS.
* Actually I am unable to record DOWNLINK on my Xiaomi Chinese phone.
*
*/
//const theSource = AudioSource.voiceUpLink;
//const theSource = AudioSource.voiceDownlink;
const theSource = AudioSource.microphone;
// Example app.
class SimpleRecorder extends StatefulWidget {
const SimpleRecorder({super.key});
@override
State<SimpleRecorder> createState() => _SimpleRecorderState();
}
class _SimpleRecorderState extends State<SimpleRecorder> {
@override
void initState() {
_mPlayer!.openPlayer().then((value) {
setState(() {
_mPlayerIsInited = true;
});
});
openTheRecorder().then((value) {
setState(() {
_mRecorderIsInited = true;
});
});
super.initState();
}
@override
void dispose() {
_subscription?.cancel();
_subscription = null;
_mPlayer!.closePlayer();
_mPlayer = null;
_mRecorder!.closeRecorder();
_mRecorder = null;
super.dispose();
}
// ------------------------------ This is the recorder stuff -----------------------------
Codec _codec = Codec.aacMP4;
String _mPath = 'tau_file.mp4';
bool _mRecorderIsInited = false;
/// Our player
FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer();
/// Our recorder
FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder();
/// Request permission to record something and open the recorder
Future<void> openTheRecorder() async {
if (!kIsWeb) {
var status = await Permission.microphone.request();
if (status != PermissionStatus.granted) {
throw RecordingPermissionException('Microphone permission not granted');
}
}
await _mRecorder!.openRecorder();
if (!await _mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
_codec = Codec.opusWebM;
_mPath = 'tau_file.webm';
if (!await _mRecorder!.isEncoderSupported(_codec) && kIsWeb) {
_mRecorderIsInited = true;
return;
}
}
_mRecorderIsInited = true;
}
final List<int> tempBuffer = [];
/// Begin to record.
/// This is our main function.
/// We ask Flutter Sound to record to a File.
void record() {
if (_method == RecordMethod.toFile) {
_mRecorder!.startRecorder(toFile: _mPath, codec: _codec, audioSource: theSource).then((value) {
setState(() {});
});
} else {
var toStream = StreamController<Uint8List>();
_subscription = toStream.stream.listen((buffer) {
// buffer: Uint8List → PCM 数据
print("收到 PCM 数据: ${buffer.length} 字节");
tempBuffer.addAll(buffer);
});
_mRecorder!.startRecorder(toStream: toStream.sink, codec: Codec.pcm16, audioSource: theSource).then((value) {
setState(() {});
});
}
}
/// Stop the recorder
void stopRecorder() async {
await _mRecorder!.stopRecorder().then((value) {
setState(() {
//var url = value;
_mplaybackReady = true;
});
});
}
void pauseRecorder() {
_mRecorder!.pauseRecorder().then((value) {
setState(() {});
});
}
void resumeRecorder() {
_mRecorder!.resumeRecorder().then((value) {
setState(() {});
});
}
// ----------------------------- This is the player stuff ---------------------------------
bool _mplaybackReady = false;
bool _mPlayerIsInited = false;
/// Begin to play the recorded sound
void play() {
assert(_mPlayerIsInited && _mplaybackReady && _mRecorder!.isStopped && _mPlayer!.isStopped);
if (_method == RecordMethod.toFile) {
_mPlayer!
.startPlayer(
fromURI: _mPath,
whenFinished: () {
setState(() {});
},
)
.then((value) {
setState(() {});
});
} else {
_mPlayer!
.startPlayer(
fromDataBuffer: Uint8List.fromList(tempBuffer),
codec: Codec.pcm16,
whenFinished: () {
setState(() {});
},
)
.then((value) {
setState(() {});
});
}
}
/// Stop the player
void stopPlayer() {
_mPlayer!.stopPlayer().then((value) {
setState(() {});
});
}
// ----------------------------- UI --------------------------------------------
_Fn? getRecorderFn() {
if (!_mRecorderIsInited || !_mPlayer!.isStopped) {
return null;
}
return _mRecorder!.isStopped ? record : stopRecorder;
}
_Fn? getPauseFn() {
if (!_mRecorderIsInited || !_mPlayer!.isStopped) {
return null;
}
return _mRecorder!.isPaused ? resumeRecorder : pauseRecorder;
}
_Fn? getPlaybackFn() {
if (!_mPlayerIsInited || !_mplaybackReady || !_mRecorder!.isStopped) {
return null;
}
return _mPlayer!.isStopped ? play : stopPlayer;
}
var _method = RecordMethod.toFile;
StreamSubscription<Uint8List>? _subscription;
_onChanged(RecordMethod? value) {
return () {
setState(() {
_method = value ?? RecordMethod.toFile;
});
};
}
@override
Widget build(BuildContext context) {
Widget makeBody() {
return Column(
children: [
Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
decoration: BoxDecoration(color: const Color(0xFFFAF0E6), border: Border.all(color: Colors.indigo, width: 3)),
child: Row(
spacing: 20,
children: [
GestureDetector(
onTap: _onChanged(RecordMethod.toFile),
child: Padding(padding: const EdgeInsets.all(8.0), child: Row(children: [Icon(_method == RecordMethod.toFile ? Icons.radio_button_on : Icons.radio_button_off), Text("toFile")])),
), //
GestureDetector(
onTap: _onChanged(RecordMethod.toStream),
child: Padding(padding: const EdgeInsets.all(8.0), child: Row(children: [Icon(_method == RecordMethod.toStream ? Icons.radio_button_on : Icons.radio_button_off), Text("toStream")])),
), //
],
),
),
Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
height: 80,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(color: const Color(0xFFFAF0E6), border: Border.all(color: Colors.indigo, width: 3)),
child: Row(
spacing: 12,
children: [
if (!_mRecorder!.isStopped) ElevatedButton(onPressed: getPauseFn(), child: Text(_mRecorder!.isPaused ? 'Resume' : 'Pause')),
ElevatedButton(onPressed: getRecorderFn(), child: Text(_mRecorder!.isRecording ? 'Stop' : 'Record')),
Text(_mRecorder!.isRecording ? 'Recording in progress' : 'Recorder is stopped'),
],
),
),
Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(3),
height: 80,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(color: const Color(0xFFFAF0E6), border: Border.all(color: Colors.indigo, width: 3)),
child: Row(children: [ElevatedButton(onPressed: getPlaybackFn(), child: Text(_mPlayer!.isPlaying ? 'Stop' : 'Play')), const SizedBox(width: 20), Text(_mPlayer!.isPlaying ? 'Playback in progress' : 'Player is stopped')]),
),
],
);
}
return Scaffold(backgroundColor: Colors.blue, appBar: AppBar(title: const Text('Simple Recorder')), body: makeBody());
}
}
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels