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:convert';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:cross_file/cross_file.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:latlong2/latlong.dart' hide Path;
|
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 '../api/mapflow_api.dart';
|
||||||
import '../auth/telegram_login_button.dart';
|
import '../auth/telegram_login_button.dart';
|
||||||
@@ -711,30 +711,26 @@ class AddExperienceFlow extends ConsumerStatefulWidget {
|
|||||||
class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||||
static const _minimumInformationUnits = 16.0;
|
static const _minimumInformationUnits = 16.0;
|
||||||
static const _nearbyPlaceRadiusMeters = 200;
|
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 _api = MapflowApi();
|
||||||
final _audioRecorder = record.AudioRecorder();
|
final _waveController = WaveformRecorderController(
|
||||||
final _recordStopwatch = Stopwatch();
|
interval: const Duration(milliseconds: 45),
|
||||||
|
config: const RecordConfig(
|
||||||
|
numChannels: 1,
|
||||||
|
sampleRate: 44100,
|
||||||
|
autoGain: true,
|
||||||
|
echoCancel: true,
|
||||||
|
noiseSuppress: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
|
Future<List<PlaceRecommendation>>? _nearbyPlacesFuture;
|
||||||
StreamSubscription<record.Amplitude>? _amplitudeSub;
|
StreamSubscription<Amplitude>? _amplitudeSub;
|
||||||
Timer? _progressTimer;
|
|
||||||
XFile? _recordedFile;
|
|
||||||
var _step = 0;
|
var _step = 0;
|
||||||
var _informationUnits = 0.0;
|
var _informationUnits = 0.0;
|
||||||
var _recording = false;
|
var _recording = false;
|
||||||
var _submitting = false;
|
var _submitting = false;
|
||||||
var _micAllowed = true;
|
var _micAllowed = true;
|
||||||
var _heardVoice = false;
|
|
||||||
var _noiseDb = -72.0;
|
var _noiseDb = -72.0;
|
||||||
var _voicePeakDb = -34.0;
|
var _voicePeakDb = -34.0;
|
||||||
var _liveLevel = 0.0;
|
var _liveLevel = 0.0;
|
||||||
@@ -748,8 +744,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_amplitudeSub?.cancel();
|
_amplitudeSub?.cancel();
|
||||||
_progressTimer?.cancel();
|
_waveController.dispose();
|
||||||
_audioRecorder.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -775,27 +770,14 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _startRecording() async {
|
Future<void> _startRecording() async {
|
||||||
_recordedFile = null;
|
await _waveController.startRecording();
|
||||||
final path = 'mapflow-${DateTime.now().microsecondsSinceEpoch}.wav';
|
|
||||||
await _audioRecorder.start(_recordConfig, path: path);
|
|
||||||
await _amplitudeSub?.cancel();
|
await _amplitudeSub?.cancel();
|
||||||
_recordStopwatch
|
|
||||||
..reset()
|
|
||||||
..start();
|
|
||||||
_lastInformationAt = DateTime.now();
|
_lastInformationAt = DateTime.now();
|
||||||
_amplitudeSub = _audioRecorder
|
_amplitudeSub = _waveController.amplitudeStream.listen(_handleAmplitude);
|
||||||
.onAmplitudeChanged(_amplitudeInterval)
|
|
||||||
.listen(_handleAmplitude);
|
|
||||||
_progressTimer?.cancel();
|
|
||||||
_progressTimer = Timer.periodic(
|
|
||||||
_amplitudeInterval,
|
|
||||||
(_) => _tickRecordingProgress(),
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_micAllowed = true;
|
_micAllowed = true;
|
||||||
_recording = true;
|
_recording = true;
|
||||||
_heardVoice = false;
|
|
||||||
_liveLevel = 0;
|
_liveLevel = 0;
|
||||||
_informationUnits = 0;
|
_informationUnits = 0;
|
||||||
});
|
});
|
||||||
@@ -804,13 +786,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
Future<void> _stopRecording() async {
|
Future<void> _stopRecording() async {
|
||||||
await _amplitudeSub?.cancel();
|
await _amplitudeSub?.cancel();
|
||||||
_amplitudeSub = null;
|
_amplitudeSub = null;
|
||||||
_progressTimer?.cancel();
|
await _waveController.stopRecording();
|
||||||
_progressTimer = null;
|
|
||||||
final path = await _audioRecorder.stop();
|
|
||||||
_recordStopwatch.stop();
|
|
||||||
if (path != null && path.isNotEmpty) {
|
|
||||||
_recordedFile = XFile(path, mimeType: 'audio/wav');
|
|
||||||
}
|
|
||||||
_lastInformationAt = null;
|
_lastInformationAt = null;
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
@@ -821,28 +797,14 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleAmplitude(record.Amplitude amplitude) {
|
void _handleAmplitude(Amplitude amplitude) {
|
||||||
final currentDb = amplitude.current;
|
final currentDb = amplitude.current;
|
||||||
|
final now = DateTime.now();
|
||||||
final level = _normalizeDbLevel(currentDb);
|
final level = _normalizeDbLevel(currentDb);
|
||||||
|
final informationDelta = _consumeInformationDelta(level, now);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_liveLevel = _smoothLevel(_liveLevel, level);
|
_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(
|
_informationUnits = math.min(
|
||||||
_minimumInformationUnits,
|
_minimumInformationUnits,
|
||||||
_informationUnits + informationDelta,
|
_informationUnits + informationDelta,
|
||||||
@@ -850,7 +812,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
});
|
});
|
||||||
ref
|
ref
|
||||||
.read(placeControllerProvider.notifier)
|
.read(placeControllerProvider.notifier)
|
||||||
.setReviewDuration(_recordStopwatch.elapsed);
|
.setReviewDuration(_waveController.timeElapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
double _normalizeDbLevel(double currentDb) {
|
double _normalizeDbLevel(double currentDb) {
|
||||||
@@ -918,7 +880,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
|||||||
onSelect: (place) async {
|
onSelect: (place) async {
|
||||||
setState(() => _submitting = true);
|
setState(() => _submitting = true);
|
||||||
controller.setReviewPlace(place.name);
|
controller.setReviewPlace(place.name);
|
||||||
final file = _recordedFile;
|
final file = _waveController.file;
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw StateError('Voice recording file is required.');
|
throw StateError('Voice recording file is required.');
|
||||||
}
|
}
|
||||||
|
|||||||
20
pubspec.lock
20
pubspec.lock
@@ -106,7 +106,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937"
|
||||||
@@ -641,7 +641,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
record:
|
record:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record
|
name: record
|
||||||
sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277
|
sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277
|
||||||
@@ -925,6 +925,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
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:
|
web:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -40,9 +40,9 @@ dependencies:
|
|||||||
http: ^1.6.0
|
http: ^1.6.0
|
||||||
web: ^1.1.1
|
web: ^1.1.1
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
record: ^6.2.0
|
|
||||||
flutter_svg: ^2.3.0
|
flutter_svg: ^2.3.0
|
||||||
cross_file: ^0.3.5+2
|
waveform_recorder: ^1.8.0
|
||||||
|
waveform_flutter: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user