Use Web Audio for browser voice meter
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m22s
All checks were successful
Build and deploy Flutter Web / build (push) Successful in 2m22s
This commit is contained in:
@@ -11,6 +11,7 @@ import 'package:record/record.dart';
|
||||
import '../api/mapflow_api.dart';
|
||||
import '../auth/telegram_login_button.dart';
|
||||
import '../auth/telegram_session.dart' as telegram_session;
|
||||
import '../audio/browser_voice_meter.dart';
|
||||
import '../models/place_models.dart';
|
||||
import '../state/place_controller.dart';
|
||||
|
||||
@@ -724,6 +725,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
|
||||
final _api = MapflowApi();
|
||||
final _recorder = AudioRecorder();
|
||||
final _browserVoiceMeter = BrowserVoiceMeter();
|
||||
final _waveSamples = List<double>.filled(160, 0.0);
|
||||
final _visualRandom = math.Random();
|
||||
|
||||
@@ -738,6 +740,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
var _recording = false;
|
||||
var _submitting = false;
|
||||
var _micAllowed = true;
|
||||
var _usingBrowserVoiceMeter = false;
|
||||
var _ambientLevel = 0.008;
|
||||
var _voiceCeiling = 0.045;
|
||||
var _noiseDb = -72.0;
|
||||
@@ -758,6 +761,7 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
_visualTimer?.cancel();
|
||||
_amplitudeTimer?.cancel();
|
||||
_audioStreamSub?.cancel();
|
||||
_browserVoiceMeter.dispose();
|
||||
_recorder.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -784,6 +788,22 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
}
|
||||
|
||||
Future<void> _startRecording() async {
|
||||
if (_browserVoiceMeter.isSupported) {
|
||||
await _browserVoiceMeter.start(_handleBrowserVoiceFrame);
|
||||
_lastAudioChunkAt = DateTime.now();
|
||||
_lastInformationAt = _lastAudioChunkAt;
|
||||
_startIdleWave();
|
||||
_startDurationTimer();
|
||||
setState(() {
|
||||
_micAllowed = true;
|
||||
_recording = true;
|
||||
_usingBrowserVoiceMeter = true;
|
||||
_liveLevel = 0;
|
||||
_informationUnits = 0;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final hasPermission = await _recorder.hasPermission();
|
||||
if (!hasPermission) {
|
||||
setState(() => _micAllowed = false);
|
||||
@@ -805,19 +825,12 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
_lastInformationAt = _lastAudioChunkAt;
|
||||
_startIdleWave();
|
||||
_startAmplitudePolling();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() => _seconds += 1);
|
||||
ref
|
||||
.read(placeControllerProvider.notifier)
|
||||
.setReviewDuration(Duration(seconds: _seconds));
|
||||
});
|
||||
_startDurationTimer();
|
||||
|
||||
setState(() {
|
||||
_micAllowed = true;
|
||||
_recording = true;
|
||||
_usingBrowserVoiceMeter = false;
|
||||
_liveLevel = 0;
|
||||
_informationUnits = 0;
|
||||
});
|
||||
@@ -827,9 +840,13 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
_timer?.cancel();
|
||||
_visualTimer?.cancel();
|
||||
_amplitudeTimer?.cancel();
|
||||
await _audioStreamSub?.cancel();
|
||||
_audioStreamSub = null;
|
||||
await _recorder.stop();
|
||||
if (_usingBrowserVoiceMeter) {
|
||||
await _browserVoiceMeter.stop();
|
||||
} else {
|
||||
await _audioStreamSub?.cancel();
|
||||
_audioStreamSub = null;
|
||||
await _recorder.stop();
|
||||
}
|
||||
_lastAudioChunkAt = null;
|
||||
_lastInformationAt = null;
|
||||
if (!mounted) {
|
||||
@@ -837,10 +854,24 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
}
|
||||
setState(() {
|
||||
_recording = false;
|
||||
_usingBrowserVoiceMeter = false;
|
||||
_liveLevel = 0;
|
||||
});
|
||||
}
|
||||
|
||||
void _startDurationTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() => _seconds += 1);
|
||||
ref
|
||||
.read(placeControllerProvider.notifier)
|
||||
.setReviewDuration(Duration(seconds: _seconds));
|
||||
});
|
||||
}
|
||||
|
||||
void _startIdleWave() {
|
||||
_visualTimer?.cancel();
|
||||
_visualTimer = Timer.periodic(const Duration(milliseconds: 70), (_) {
|
||||
@@ -910,6 +941,28 @@ class _AddExperienceFlowState extends ConsumerState<AddExperienceFlow> {
|
||||
});
|
||||
}
|
||||
|
||||
void _handleBrowserVoiceFrame(VoiceMeterFrame frame) {
|
||||
if (!mounted || !_recording) {
|
||||
return;
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
final informationDelta = _consumeInformationDelta(frame.level, now);
|
||||
_lastAudioChunkAt = now;
|
||||
setState(() {
|
||||
if (frame.samples.isNotEmpty) {
|
||||
_waveSamples
|
||||
..removeRange(0, frame.samples.length)
|
||||
..addAll(frame.samples);
|
||||
}
|
||||
_liveLevel = _smoothLevel(_liveLevel, frame.level);
|
||||
_informationUnits = math.min(
|
||||
_minimumInformationUnits,
|
||||
_informationUnits + informationDelta,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
double _normalizeDbLevel(double currentDb) {
|
||||
final db = currentDb.clamp(-160.0, 0.0);
|
||||
if (db < _noiseDb) {
|
||||
|
||||
Reference in New Issue
Block a user