From 86525027fa99b9c41c0c6de5b2bdb76425947765 Mon Sep 17 00:00:00 2001 From: Ian Lavery Date: Thu, 10 Aug 2023 09:53:50 -0700 Subject: [PATCH] Flutter vp updates (#568) --- binding/flutter/CHANGELOG.md | 5 +- binding/flutter/lib/rhino_manager.dart | 112 ++++++++++++------------- binding/flutter/pubspec.lock | 2 +- binding/flutter/pubspec.yaml | 4 +- demo/flutter/ios/Podfile.lock | 15 ++-- demo/flutter/lib/main.dart | 12 ++- demo/flutter/pubspec.lock | 4 +- demo/flutter/pubspec.yaml | 2 +- 8 files changed, 79 insertions(+), 77 deletions(-) diff --git a/binding/flutter/CHANGELOG.md b/binding/flutter/CHANGELOG.md index 4370da00c..392698f9f 100644 --- a/binding/flutter/CHANGELOG.md +++ b/binding/flutter/CHANGELOG.md @@ -39,4 +39,7 @@ * Additional language support added (ar, hi, nl, pl, ru, sv, vn, zh) ## [2.2.1] - 2023-06-01 -* Expose Rhino properties through RhinoManager class \ No newline at end of file +* Expose Rhino properties through RhinoManager class + +## [2.2.2] - 2023-08-09 +* Update flutter-voice-processor \ No newline at end of file diff --git a/binding/flutter/lib/rhino_manager.dart b/binding/flutter/lib/rhino_manager.dart index 9df71f055..967a59320 100644 --- a/binding/flutter/lib/rhino_manager.dart +++ b/binding/flutter/lib/rhino_manager.dart @@ -23,14 +23,13 @@ typedef InferenceCallback = Function(RhinoInference inference); typedef ProcessErrorCallback = Function(RhinoException error); class RhinoManager { - final VoiceProcessor? _voiceProcessor; + VoiceProcessor? _voiceProcessor; Rhino? _rhino; - final InferenceCallback _inferenceCallback; - RemoveListener? _removeVoiceProcessorListener; - RemoveListener? _removeErrorListener; + late VoiceProcessorFrameListener _frameListener; + late VoiceProcessorErrorListener _errorListener; - bool _awaitingStop = false; + bool _isListening; /// Rhino version string String? get version => _rhino?.version; @@ -91,88 +90,89 @@ class RhinoManager { } // private constructor - RhinoManager._(this._rhino, this._inferenceCallback, + RhinoManager._(this._rhino, InferenceCallback inferenceCallback, ProcessErrorCallback? processErrorCallback) - : _voiceProcessor = VoiceProcessor.getVoiceProcessor( - _rhino!.frameLength, _rhino.sampleRate) { - if (_voiceProcessor == null) { - throw RhinoRuntimeException("flutter_voice_processor not available."); - } - _removeVoiceProcessorListener = - _voiceProcessor!.addListener((buffer) async { - if (_awaitingStop) { - return; - } - - // cast from dynamic to int array - List rhinoFrame; - try { - rhinoFrame = (buffer as List).cast(); - } on Error { - RhinoException castError = RhinoException( - "flutter_voice_processor sent an unexpected data type."); - processErrorCallback == null - ? print(castError.message) - : processErrorCallback(castError); + : _voiceProcessor = VoiceProcessor.instance, + _isListening = false { + _frameListener = (List frame) async { + if (!_isListening) { return; } - // process frame with Rhino try { - RhinoInference? rhinoResult = await _rhino?.process(rhinoFrame); + RhinoInference? rhinoResult = await _rhino?.process(frame); if ((rhinoResult != null) && (rhinoResult.isFinalized)) { - _awaitingStop = true; - - _inferenceCallback(rhinoResult); - // stop audio processing - await _voiceProcessor?.stop(); + inferenceCallback(rhinoResult); + await _stop(); } } on RhinoException catch (error) { processErrorCallback == null - ? print(error.message) + ? print("RhinoException: ${error.message}") : processErrorCallback(error); - } finally { - _awaitingStop = false; } - }); - - _removeErrorListener = _voiceProcessor!.addErrorListener((errorMsg) { - RhinoException nativeError = RhinoException(errorMsg as String); + }; + _errorListener = (VoiceProcessorException error) { processErrorCallback == null - ? print(nativeError.message) - : processErrorCallback(nativeError); - }); + ? print("RhinoException: ${error.message}") + : processErrorCallback(RhinoException(error.message)); + }; + } + + Future _stop() async { + if (!_isListening) { + return; + } + + _voiceProcessor?.removeErrorListener(_errorListener); + _voiceProcessor?.removeFrameListener(_frameListener); + + if (_voiceProcessor?.numFrameListeners == 0) { + try { + await _voiceProcessor?.stop(); + } on PlatformException catch (e) { + throw RhinoRuntimeException( + "Failed to stop audio recording: ${e.message}"); + } + } + + _isListening = false; } - /// Opens audio input stream and sends audio frames to Rhino until a inference - /// result is sent via inference callback - /// Throws a `RhinoException` if there was a problem starting the audio engine + /// Starts audio recording and processing with the Rhino engine until a + /// inference result is sent via the `inferenceCallback` + /// Throws a `RhinoException` if there was a problem starting audio recording. Future process() async { + if (_isListening) { + return; + } if (_rhino == null || _voiceProcessor == null) { throw RhinoInvalidStateException( "Cannot start Rhino - resources have already been released"); } if (await _voiceProcessor?.hasRecordAudioPermission() ?? false) { + _voiceProcessor?.addFrameListener(_frameListener); + _voiceProcessor?.addErrorListener(_errorListener); try { - await _voiceProcessor!.start(); - } on PlatformException { + await _voiceProcessor?.start(_rhino!.frameLength, _rhino!.sampleRate); + } on PlatformException catch (e) { throw RhinoRuntimeException( - "Audio engine failed to start. Hardware may not be supported."); + "Failed to start audio recording: ${e.message}"); } } else { throw RhinoRuntimeException( "User did not give permission to record audio."); } + + _isListening = true; } - /// Releases Rhino and audio resources + /// Releases Rhino and audio resources. + /// Throws a `RhinoException` if there was a problem stopping audio recording. Future delete() async { - if (_voiceProcessor?.isRecording ?? false) { - await _voiceProcessor!.stop(); - } - _removeVoiceProcessorListener?.call(); - _removeErrorListener?.call(); + await _stop(); + _voiceProcessor = null; + _rhino?.delete(); _rhino = null; } diff --git a/binding/flutter/pubspec.lock b/binding/flutter/pubspec.lock index c90682a1e..5e8d34cc8 100644 --- a/binding/flutter/pubspec.lock +++ b/binding/flutter/pubspec.lock @@ -80,7 +80,7 @@ packages: name: flutter_voice_processor url: "https://pub.dartlang.org" source: hosted - version: "1.0.10" + version: "1.1.0" lints: dependency: "direct dev" description: diff --git a/binding/flutter/pubspec.yaml b/binding/flutter/pubspec.yaml index 7432106ee..570e90b0b 100644 --- a/binding/flutter/pubspec.yaml +++ b/binding/flutter/pubspec.yaml @@ -1,6 +1,6 @@ name: rhino_flutter description: A Flutter plugin for Picovoice's Rhino Speech-to-Intent engine -version: 2.2.1 +version: 2.2.2 homepage: https://picovoice.ai/ repository: https://github.com/Picovoice/rhino/ documentation: https://picovoice.ai/docs/rhino/ @@ -14,7 +14,7 @@ dependencies: flutter: sdk: flutter - flutter_voice_processor: ^1.0.10 + flutter_voice_processor: ^1.1.0 dev_dependencies: flutter_test: diff --git a/demo/flutter/ios/Podfile.lock b/demo/flutter/ios/Podfile.lock index 9374141de..7cc998f52 100644 --- a/demo/flutter/ios/Podfile.lock +++ b/demo/flutter/ios/Podfile.lock @@ -1,15 +1,16 @@ PODS: - Flutter (1.0.0) - - flutter_voice_processor (1.0.6): + - flutter_voice_processor (1.1.0): - Flutter + - ios-voice-processor (~> 1.1.0) - integration_test (0.0.1): - Flutter - - ios-voice-processor (1.0.3) + - ios-voice-processor (1.1.0) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - Rhino-iOS (2.2.0): - - ios-voice-processor (~> 1.0.2) + - Rhino-iOS (2.2.2): + - ios-voice-processor (~> 1.1.0) - rhino_flutter (2.2.0): - Flutter - Rhino-iOS (~> 2.2.0) @@ -40,11 +41,11 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - flutter_voice_processor: afb59b604d99397a1ccf15c935ac8f2c2327f09f + flutter_voice_processor: 53afbf59ad3feb82f4a379fea9ed8dc98495210f integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 - ios-voice-processor: 65b25a8db69ea25ffba0eeef37bae71a982f34cc + ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1 path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 - Rhino-iOS: 5f6c2edc809a913f7559da93fc92c0e66c7bf711 + Rhino-iOS: 0fad86b28d35f67ccb6bd0a2efbbcc0d88b05124 rhino_flutter: 4c3b4bbe00206617c38ea6f7c2f17d23b2879e90 PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d diff --git a/demo/flutter/lib/main.dart b/demo/flutter/lib/main.dart index 20e149cf0..2478faf17 100644 --- a/demo/flutter/lib/main.dart +++ b/demo/flutter/lib/main.dart @@ -154,10 +154,7 @@ class _MyAppState extends State { rhinoText = "Listening..."; }); } on RhinoException catch (ex) { - print("Failed to start audio capture: ${ex.message}"); - setState(() { - isButtonDisabled = false; - }); + errorCallback(ex); } } @@ -223,7 +220,7 @@ class _MyAppState extends State { margin: EdgeInsets.only(right: 10, top: 10), child: ElevatedButton( style: buttonStyle, - onPressed: (isButtonDisabled || isError) + onPressed: (isProcessing || isButtonDisabled || isError) ? null : () { _showContextInfo(context); @@ -249,8 +246,9 @@ class _MyAppState extends State { height: 130, child: ElevatedButton( style: buttonStyle, - onPressed: - (isButtonDisabled || isError) ? null : _startProcessing, + onPressed: (isProcessing || isButtonDisabled || isError) + ? null + : _startProcessing, child: Text(isProcessing ? "..." : "Start", style: TextStyle(fontSize: 30)), ))), diff --git a/demo/flutter/pubspec.lock b/demo/flutter/pubspec.lock index f0e6757e6..c7c61207c 100644 --- a/demo/flutter/pubspec.lock +++ b/demo/flutter/pubspec.lock @@ -99,7 +99,7 @@ packages: name: flutter_voice_processor url: "https://pub.dartlang.org" source: hosted - version: "1.0.10" + version: "1.1.0" fuchsia_remote_debug_protocol: dependency: transitive description: flutter @@ -214,7 +214,7 @@ packages: name: rhino_flutter url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "2.2.2" sky_engine: dependency: transitive description: flutter diff --git a/demo/flutter/pubspec.yaml b/demo/flutter/pubspec.yaml index 9fa05a5eb..52303ba09 100644 --- a/demo/flutter/pubspec.yaml +++ b/demo/flutter/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: flutter: sdk: flutter - rhino_flutter: ^2.2.1 + rhino_flutter: ^2.2.2 dev_dependencies: integration_test: