107 lines
2.7 KiB
Dart
107 lines
2.7 KiB
Dart
|
|
import 'dart:async';
|
||
|
|
import 'dart:io';
|
||
|
|
import 'dart:typed_data';
|
||
|
|
import 'package:flutter/material.dart';
|
||
|
|
import 'package:record/record.dart';
|
||
|
|
import 'package:path_provider/path_provider.dart';
|
||
|
|
|
||
|
|
class AudioRecorderButton extends StatefulWidget {
|
||
|
|
final void Function(Uint8List bytes, String nombre) onRecorded;
|
||
|
|
|
||
|
|
const AudioRecorderButton({super.key, required this.onRecorded});
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<AudioRecorderButton> createState() => _AudioRecorderButtonState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _AudioRecorderButtonState extends State<AudioRecorderButton> {
|
||
|
|
final _recorder = AudioRecorder();
|
||
|
|
bool _isRecording = false;
|
||
|
|
int _seconds = 0;
|
||
|
|
Timer? _timer;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void dispose() {
|
||
|
|
_timer?.cancel();
|
||
|
|
_recorder.dispose();
|
||
|
|
super.dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _toggleRecording() async {
|
||
|
|
if (_isRecording) {
|
||
|
|
await _stopRecording();
|
||
|
|
} else {
|
||
|
|
await _startRecording();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _startRecording() async {
|
||
|
|
final hasPermission = await _recorder.hasPermission();
|
||
|
|
if (!hasPermission) return;
|
||
|
|
|
||
|
|
final dir = await getTemporaryDirectory();
|
||
|
|
final path = '${dir.path}/audio_${DateTime.now().millisecondsSinceEpoch}.m4a';
|
||
|
|
|
||
|
|
await _recorder.start(
|
||
|
|
const RecordConfig(encoder: AudioEncoder.aacLc),
|
||
|
|
path: path,
|
||
|
|
);
|
||
|
|
|
||
|
|
setState(() {
|
||
|
|
_isRecording = true;
|
||
|
|
_seconds = 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
_timer = Timer.periodic(const Duration(seconds: 1), (t) {
|
||
|
|
setState(() => _seconds++);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
Future<void> _stopRecording() async {
|
||
|
|
_timer?.cancel();
|
||
|
|
final path = await _recorder.stop();
|
||
|
|
|
||
|
|
setState(() {
|
||
|
|
_isRecording = false;
|
||
|
|
_seconds = 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
if (path != null) {
|
||
|
|
final file = File(path);
|
||
|
|
final bytes = await file.readAsBytes();
|
||
|
|
final nombre = 'audio_${DateTime.now().millisecondsSinceEpoch}.m4a';
|
||
|
|
widget.onRecorded(bytes, nombre);
|
||
|
|
await file.delete();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
String _formatDuration(int seconds) {
|
||
|
|
final mins = seconds ~/ 60;
|
||
|
|
final secs = seconds % 60;
|
||
|
|
return '${mins.toString().padLeft(2, '0')}:${secs.toString().padLeft(2, '0')}';
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return FilledButton.tonal(
|
||
|
|
onPressed: _toggleRecording,
|
||
|
|
style: FilledButton.styleFrom(
|
||
|
|
backgroundColor:
|
||
|
|
_isRecording ? Theme.of(context).colorScheme.errorContainer : null,
|
||
|
|
),
|
||
|
|
child: Row(
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Icon(
|
||
|
|
_isRecording ? Icons.stop : Icons.mic,
|
||
|
|
size: 18,
|
||
|
|
color: _isRecording ? Theme.of(context).colorScheme.error : null,
|
||
|
|
),
|
||
|
|
const SizedBox(width: 4),
|
||
|
|
Text(_isRecording ? _formatDuration(_seconds) : 'Audio'),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|