Restore waveform recording controller
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m52s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m52s
This commit is contained in:
@@ -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,
|
||||
|
||||
final _api = MapflowApi();
|
||||
final _waveController = WaveformRecorderController(
|
||||
interval: const Duration(milliseconds: 45),
|
||||
config: const RecordConfig(
|
||||
numChannels: 1,
|
||||
sampleRate: 44100,
|
||||
autoGain: true,
|
||||
echoCancel: true,
|
||||
noiseSuppress: true,
|
||||
),
|
||||
);
|
||||
|
||||
final _api = MapflowApi();
|
||||
final _audioRecorder = record.AudioRecorder();
|
||||
final _recordStopwatch = Stopwatch();
|
||||
|
||||
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.');
|
||||
}
|
||||
|
||||
20
pubspec.lock
20
pubspec.lock
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user