fft_recorder_ui 1.0.1
fft_recorder_ui: ^1.0.1 copied to clipboard
audio recorder + FFT bar visualizer Flutter package. Built on top of flutter_recorder.
example/lib/main.dart
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:fft_recorder_ui/fft_recorder_ui.dart';
import 'package:path_provider/path_provider.dart';
void main() {
runApp(const RecorderExampleApp());
}
class RecorderExampleApp extends StatelessWidget {
const RecorderExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FFT Recorder UI Example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
home: const RecorderExamplePage(),
);
}
}
class RecorderExamplePage extends StatefulWidget {
const RecorderExamplePage({super.key});
@override
State<RecorderExamplePage> createState() => _RecorderExamplePageState();
}
class _RecorderExamplePageState extends State<RecorderExamplePage> {
late final FftRecorderController _controller;
StreamSubscription<List<double>>? _fftSubscription;
List<double> _fftData = [];
late final AudioPlayer _player;
String? _lastSavedPath;
@override
void initState() {
super.initState();
_controller = FftRecorderController();
_controller.requestMicPermission();
_player = AudioPlayer();
_fftSubscription = _controller.fftStream.listen((data) {
if (!mounted) return;
setState(() {
_fftData = data;
});
});
}
@override
void dispose() {
_fftSubscription?.cancel();
_player.dispose();
_controller.dispose();
super.dispose();
}
Future<void> _startRecording() async {
final dir = await getApplicationDocumentsDirectory();
final filePath = '${dir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav';
await _controller.startRecording(filePath: filePath);
setState(() => _lastSavedPath = filePath);
}
Future<void> _playRecording(String path) async {
await _player.stop();
await _player.play(DeviceFileSource(path));
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Recorder + Bar Visualizer')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Wrap(
spacing: 8,
runSpacing: 8,
children: [
ElevatedButton(
onPressed: _lastSavedPath == null
? null
: () {
_playRecording(_lastSavedPath!);
},
child: const Text('재생'),
),
ElevatedButton(
onPressed: _controller.recordingStatus.value == RecordingStatus.recording
? null
: _startRecording,
child: const Text('녹음 시작'),
),
ElevatedButton(
onPressed: _controller.recordingStatus.value == RecordingStatus.recording
? _controller.pauseRecording
: null,
child: const Text('일시정지'),
),
ElevatedButton(
onPressed: _controller.recordingStatus.value == RecordingStatus.paused
? _controller.resumeRecording
: null,
child: const Text('재개'),
),
ElevatedButton(
onPressed: _controller.recordingStatus.value == RecordingStatus.idle
? null
: () {
final path = _controller.stopRecording();
setState(() {
_lastSavedPath = path ?? _lastSavedPath;
});
final playPath = path ?? _lastSavedPath;
if (playPath != null) {
_playRecording(playPath);
}
},
child: const Text('정지'),
),
],
),
const SizedBox(height: 12),
if (_lastSavedPath != null)
Align(
alignment: Alignment.centerLeft,
child: Text('Saved: $_lastSavedPath', style: theme.textTheme.bodySmall),
),
const SizedBox(height: 16),
Container(
width: 170,
height: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.grey.shade900,
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.all(12),
child: BarVisualizer(
data: _fftData,
barColor: Colors.white,
barCount: 9,
barWidth: 5.33,
maxHeight: 48,
spacing: 8,
emptyText: 'FFT 데이터 대기 중',
),
),
const SizedBox(height: 16),
],
),
),
);
}
}