Restore waveform recording controller
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m52s

This commit is contained in:
Ruslan Bakiev
2026-05-14 16:35:28 +07:00
parent 34a197f786
commit 0532f6aa88
3 changed files with 42 additions and 64 deletions

View File

@@ -2,13 +2,13 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math' as math;
import 'package:cross_file/cross_file.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:latlong2/latlong.dart' hide Path;
import 'package:record/record.dart' as record;
import 'package:waveform_flutter/waveform_flutter.dart' show Amplitude;
import 'package:waveform_recorder/waveform_recorder.dart';
import '../api/mapflow_api.dart';
import '../auth/telegram_login_button.dart';
@@ -711,30 +711,26 @@ class AddExperienceFlow extends ConsumerStatefulWidget {
class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
static const _minimumInformationUnits = 16.0;
static const _nearbyPlaceRadiusMeters = 200;
static const _amplitudeInterval = Duration(milliseconds: 140);
static const _recordConfig = record.RecordConfig(
encoder: record.AudioEncoder.wav,
numChannels: 1,
sampleRate: 44100,
autoGain: true,
echoCancel: true,
noiseSuppress: true,
);
final _api = MapflowApi();
final _audioRecorder = record.AudioRecorder();
final _recordStopwatch = Stopwatch();
final _waveController = WaveformRecorderController(
interval: const Duration(milliseconds: 45),
config: const RecordConfig(
numChannels: 1,
sampleRate: 44100,
autoGain: true,
echoCancel: true,
noiseSuppress: true,
),
);
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
StreamSubscription<record.Amplitude>? _amplitudeSub;
Timer? _progressTimer;
XFile? _recordedFile;
StreamSubscription<Amplitude>? _amplitudeSub;
var _step = 0;
var _informationUnits = 0.0;
var _recording = false;
var _submitting = false;
var _micAllowed = true;
var _heardVoice = false;
var _noiseDb = -72.0;
var _voicePeakDb = -34.0;
var _liveLevel = 0.0;
@@ -748,8 +744,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
@override
void dispose() {
_amplitudeSub?.cancel();
_progressTimer?.cancel();
_audioRecorder.dispose();
_waveController.dispose();
super.dispose();
}
@@ -775,27 +770,14 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
}
Future<void> _startRecording() async {
_recordedFile = null;
final path = 'mapflow-${DateTime.now().microsecondsSinceEpoch}.wav';
await _audioRecorder.start(_recordConfig, path: path);
await _waveController.startRecording();
await _amplitudeSub?.cancel();
_recordStopwatch
..reset()
..start();
_lastInformationAt = DateTime.now();
_amplitudeSub = _audioRecorder
.onAmplitudeChanged(_amplitudeInterval)
.listen(_handleAmplitude);
_progressTimer?.cancel();
_progressTimer = Timer.periodic(
_amplitudeInterval,
(_) => _tickRecordingProgress(),
);
_amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude);
setState(() {
_micAllowed = true;
_recording = true;
_heardVoice = false;
_liveLevel = 0;
_informationUnits = 0;
});
@@ -804,13 +786,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
Future<void> _stopRecording() async {
await _amplitudeSub?.cancel();
_amplitudeSub = null;
_progressTimer?.cancel();
_progressTimer = null;
final path = await _audioRecorder.stop();
_recordStopwatch.stop();
if (path != null && path.isNotEmpty) {
_recordedFile = XFile(path, mimeType: 'audio/wav');
}
await _waveController.stopRecording();
_lastInformationAt = null;
if (!mounted) {
return;
@@ -821,28 +797,14 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
});
}
void _handleAmplitude(record.Amplitude amplitude) {
void _handleAmplitude(Amplitude amplitude) {
final currentDb = amplitude.current;
final now = DateTime.now();
final level = _normalizeDbLevel(currentDb);
final informationDelta = _consumeInformationDelta(level, now);
setState(() {
_liveLevel = _smoothLevel(_liveLevel, level);
_heardVoice = _heardVoice || level > 0.08;
});
}
void _tickRecordingProgress() {
if (!_recording) {
return;
}
final effectiveLevel = _heardVoice ? math.max(_liveLevel, 0.34) : _liveLevel;
final informationDelta = _consumeInformationDelta(
effectiveLevel,
DateTime.now(),
);
setState(() {
_informationUnits = math.min(
_minimumInformationUnits,
_informationUnits + informationDelta,
@@ -850,7 +812,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
});
ref
.read(placeControllerProvider.notifier)
.setReviewDuration(_recordStopwatch.elapsed);
.setReviewDuration(_waveController.timeElapsed);
}
double _normalizeDbLevel(double currentDb) {
@@ -918,7 +880,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
onSelect: (place) async {
setState(() => _submitting = true);
controller.setReviewPlace(place.name);
final file = _recordedFile;
final file = _waveController.file;
if (file == null) {
throw StateError('Voice recording file is required.');
}