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.');
}

View File

@@ -106,7 +106,7 @@ packages:
source: hosted
version: "1.15.0"
cross_file:
dependency: "direct main"
dependency: transitive
description:
name: cross_file
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
@@ -641,7 +641,7 @@ packages:
source: hosted
version: "2.2.0"
record:
dependency: "direct main"
dependency: transitive
description:
name: record
sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277
@@ -925,6 +925,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
waveform_flutter:
dependency: "direct main"
description:
name: waveform_flutter
sha256: "08c9e98d4cf119428d8b3c083ed42c11c468623eaffdf30420ae38e36662922a"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
waveform_recorder:
dependency: "direct main"
description:
name: waveform_recorder
sha256: "1ca0a19b143d1bdef2adfb3d28f0627c18aee5285235c8cf81a89bf29a0420e1"
url: "https://pub.dev"
source: hosted
version: "1.8.0"
web:
dependency: "direct main"
description:

View File

@@ -40,9 +40,9 @@ dependencies:
http: ^1.6.0
web: ^1.1.1
geolocator: ^14.0.2
record: ^6.2.0
flutter_svg: ^2.3.0
cross_file: ^0.3.5+2
waveform_recorder: ^1.8.0
waveform_flutter: ^1.2.0
dev_dependencies:
flutter_test: