From dd21394a05842119592c3abe973c380c726c44bd Mon Sep 17 00:00:00 2001 From: Ian Lavery Date: Wed, 18 Oct 2023 15:44:35 -0700 Subject: [PATCH] flutter --- binding/flutter/CHANGELOG.md | 7 +++- binding/flutter/android/build.gradle | 10 ++++- .../picovoice/flutter/rhino/RhinoPlugin.java | 25 +++++++++++ .../ios/Classes/SwiftRhinoPlugin.swift | 23 ++++++++++- binding/flutter/ios/rhino_flutter.podspec | 4 +- binding/flutter/lib/rhino.dart | 21 ++++++++++ binding/flutter/lib/rhino_manager.dart | 1 + binding/flutter/pubspec.yaml | 2 +- demo/flutter/android/build.gradle | 6 +++ demo/flutter/integration_test/app_test.dart | 41 +++++++++++++++++++ demo/flutter/ios/Podfile | 2 + demo/flutter/ios/Podfile.lock | 16 ++++---- demo/flutter/pubspec.lock | 8 ++-- demo/flutter/pubspec.yaml | 3 +- 14 files changed, 150 insertions(+), 19 deletions(-) diff --git a/binding/flutter/CHANGELOG.md b/binding/flutter/CHANGELOG.md index b3927d372..211de8692 100644 --- a/binding/flutter/CHANGELOG.md +++ b/binding/flutter/CHANGELOG.md @@ -45,4 +45,9 @@ * Update flutter-voice-processor ## [2.2.3] - 2023-08-24 -* Update native packages \ No newline at end of file +* Update native packages + +## [3.0.0] - 2023-10-18 +* Engine improvements +* Improved error reporting +* Added reset function \ No newline at end of file diff --git a/binding/flutter/android/build.gradle b/binding/flutter/android/build.gradle index ee719613d..29f82d680 100644 --- a/binding/flutter/android/build.gradle +++ b/binding/flutter/android/build.gradle @@ -1,10 +1,13 @@ group 'ai.picovoice.flutter.rhino' -version '2.2.3' +version '3.0.0' buildscript { repositories { google() jcenter() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1267/' + } } dependencies { @@ -16,6 +19,9 @@ rootProject.allprojects { repositories { google() jcenter() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1267/' + } } } @@ -33,5 +39,5 @@ android { } dependencies { - implementation 'ai.picovoice:rhino-android:2.2.2' + implementation 'ai.picovoice:rhino-android:3.0.0' } diff --git a/binding/flutter/android/src/main/java/ai/picovoice/flutter/rhino/RhinoPlugin.java b/binding/flutter/android/src/main/java/ai/picovoice/flutter/rhino/RhinoPlugin.java index 40496e1a0..7da3874db 100644 --- a/binding/flutter/android/src/main/java/ai/picovoice/flutter/rhino/RhinoPlugin.java +++ b/binding/flutter/android/src/main/java/ai/picovoice/flutter/rhino/RhinoPlugin.java @@ -31,6 +31,7 @@ public class RhinoPlugin implements FlutterPlugin, MethodCallHandler { private enum Method { CREATE, PROCESS, + RESET, DELETE } @@ -43,6 +44,8 @@ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBindin flutterContext = flutterPluginBinding.getApplicationContext(); channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "rhino"); channel.setMethodCallHandler(this); + + Rhino.setSdk("flutter"); } @Override @@ -147,6 +150,28 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { null); } break; + case RESET: + try { + String handle = call.argument("handle"); + if (!rhinoPool.containsKey(handle)) { + result.error( + RhinoInvalidStateException.class.getSimpleName(), + "Invalid rhino handle provided to native module", + null); + return; + } + + Rhino rhino = rhinoPool.get(handle); + rhino.reset(); + + result.success(null); + } catch (RhinoException e) { + result.error( + e.getClass().getSimpleName(), + e.getMessage(), + null); + } + break; case DELETE: String handle = call.argument("handle"); diff --git a/binding/flutter/ios/Classes/SwiftRhinoPlugin.swift b/binding/flutter/ios/Classes/SwiftRhinoPlugin.swift index be8510a1c..b9217ccd1 100644 --- a/binding/flutter/ios/Classes/SwiftRhinoPlugin.swift +++ b/binding/flutter/ios/Classes/SwiftRhinoPlugin.swift @@ -1,5 +1,5 @@ // -// Copyright 2021 Picovoice Inc. +// Copyright 2021-2023 Picovoice Inc. // // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE" // file accompanying this source. @@ -16,6 +16,7 @@ import Rhino enum Method: String { case CREATE case PROCESS + case RESET case DELETE } @@ -27,6 +28,8 @@ public class SwiftRhinoPlugin: NSObject, FlutterPlugin { let methodChannel = FlutterMethodChannel(name: "rhino", binaryMessenger: registrar.messenger()) registrar.addMethodCallDelegate(instance, channel: methodChannel) + + Rhino.setSdk(sdk: "flutter") } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { @@ -108,6 +111,24 @@ public class SwiftRhinoPlugin: NSObject, FlutterPlugin { } catch { result(errorToFlutterError(RhinoError(error.localizedDescription))) } + case .RESET: + do { + if let handle = args["handle"] as? String { + if let rhino = rhinoPool[handle] { + try rhino.reset() + result(nil) + } else { + result(errorToFlutterError( + RhinoInvalidStateError("Invalid handle provided to Rhino 'reset'"))) + } + } else { + result(errorToFlutterError(RhinoInvalidArgumentError("missing required arguments 'handle'"))) + } + } catch let error as RhinoError { + result(errorToFlutterError(error)) + } catch { + result(errorToFlutterError(RhinoError(error.localizedDescription))) + } case .DELETE: if let handle = args["handle"] as? String { if let rhino = rhinoPool.removeValue(forKey: handle) { diff --git a/binding/flutter/ios/rhino_flutter.podspec b/binding/flutter/ios/rhino_flutter.podspec index 283f61842..afe85d638 100644 --- a/binding/flutter/ios/rhino_flutter.podspec +++ b/binding/flutter/ios/rhino_flutter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'rhino_flutter' - s.version = '2.2.3' + s.version = '3.0.0' s.summary = 'A Flutter package plugin for Picovoice\'s Rhino Speech-to-Intent engine' s.description = <<-DESC A Flutter package plugin for Picovoice\'s Rhino Speech-to-Intent engine @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.source_files = 'Classes/**/*' s.platform = :ios, '11.0' s.dependency 'Flutter' - s.dependency 'Rhino-iOS', '~> 2.2.2' + s.dependency 'Rhino-iOS', '~> 3.0.0' s.swift_version = '5.0' end \ No newline at end of file diff --git a/binding/flutter/lib/rhino.dart b/binding/flutter/lib/rhino.dart index 9669cdaaf..053f7dda0 100644 --- a/binding/flutter/lib/rhino.dart +++ b/binding/flutter/lib/rhino.dart @@ -142,6 +142,11 @@ class Rhino { /// /// returns RhinoInference object. Future process(List? frame) async { + if (_handle == null) { + throw RhinoInvalidStateException( + "Unable to process with Rhino - resources have already been released"); + } + try { Map inference = Map.from(await _channel .invokeMethod('process', {'handle': _handle, 'frame': frame})); @@ -164,6 +169,22 @@ class Rhino { } } + /// Resets the internal state of the engine. It should be called before the + /// engine can be used to infer intent from a new stream of audio. + /// + /// Throws a `RhinoException` if reset fails + Future reset() async { + if (_handle == null) { + throw RhinoInvalidStateException( + "Unable to reset Rhino - resources have already been released"); + } + try { + await _channel.invokeMethod('reset', {'handle': _handle}); + } on PlatformException catch (error) { + throw rhinoStatusToException(error.code, error.message); + } + } + /// Frees memory that was allocated for Rhino Future delete() async { if (_handle != null) { diff --git a/binding/flutter/lib/rhino_manager.dart b/binding/flutter/lib/rhino_manager.dart index 967a59320..23202a0fc 100644 --- a/binding/flutter/lib/rhino_manager.dart +++ b/binding/flutter/lib/rhino_manager.dart @@ -145,6 +145,7 @@ class RhinoManager { if (_isListening) { return; } + if (_rhino == null || _voiceProcessor == null) { throw RhinoInvalidStateException( "Cannot start Rhino - resources have already been released"); diff --git a/binding/flutter/pubspec.yaml b/binding/flutter/pubspec.yaml index e1be17012..08c26435a 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.3 +version: 3.0.0 homepage: https://picovoice.ai/ repository: https://github.com/Picovoice/rhino/ documentation: https://picovoice.ai/docs/rhino/ diff --git a/demo/flutter/android/build.gradle b/demo/flutter/android/build.gradle index bd04753b1..38d5fd64e 100644 --- a/demo/flutter/android/build.gradle +++ b/demo/flutter/android/build.gradle @@ -2,6 +2,9 @@ buildscript { repositories { google() jcenter() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1267/' + } } dependencies { @@ -13,6 +16,9 @@ allprojects { repositories { google() jcenter() + maven { + url 'https://s01.oss.sonatype.org/content/repositories/aipicovoice-1267/' + } } } diff --git a/demo/flutter/integration_test/app_test.dart b/demo/flutter/integration_test/app_test.dart index d6daf61d7..72b8d16de 100644 --- a/demo/flutter/integration_test/app_test.dart +++ b/demo/flutter/integration_test/app_test.dart @@ -37,6 +37,47 @@ void main() { testData = json.decode(testDataJson); }); + testWidgets('Test reset', (tester) async { + String language = testData['tests']['within_context'][0]['language']; + String contextName = + testData['tests']['within_context'][0]['context_name']; + + String contextPath = + "assets/test_resources/context_files/${contextName}_$platform.rhn"; + String modelPath = + "assets/test_resources/model_files/rhino_params${language != "en" ? "_$language" : ""}.pv"; + + Rhino rhino; + try { + rhino = + await Rhino.create(accessKey, contextPath, modelPath: modelPath); + } on RhinoException catch (ex) { + expect(ex, equals(null), + reason: "Failed to initialize Rhino for $language: $ex"); + return; + } + + String audioPath = + "assets/test_resources/audio_samples/test_within_context${language != "en" ? "_$language" : ""}.wav"; + List pcm = await loadAudioFile(audioPath); + + RhinoInference? inference; + final int frameLength = rhino.frameLength; + for (int i = 0; i < (pcm.length - frameLength); i += frameLength) { + if (i == (pcm.length - frameLength) ~/ 2) { + await rhino.reset(); + } + inference = await rhino.process(pcm.sublist(i, i + frameLength)); + if (inference.isFinalized) { + break; + } + } + + rhino.delete(); + expect(inference?.isFinalized, equals(true), + reason: "Rhino should not have finalized after reset"); + }); + testWidgets('Test within_context all languages', (tester) async { for (int t = 0; t < testData['tests']['within_context'].length; t++) { String language = testData['tests']['within_context'][t]['language']; diff --git a/demo/flutter/ios/Podfile b/demo/flutter/ios/Podfile index 313ea4a15..aa1d88ee3 100644 --- a/demo/flutter/ios/Podfile +++ b/demo/flutter/ios/Podfile @@ -28,6 +28,8 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + pod 'Rhino-iOS', :podspec => 'https://raw.githubusercontent.com/Picovoice/rhino/v3.0/binding/ios/Rhino-iOS.podspec' + use_frameworks! use_modular_headers! diff --git a/demo/flutter/ios/Podfile.lock b/demo/flutter/ios/Podfile.lock index c9f895870..e15f58738 100644 --- a/demo/flutter/ios/Podfile.lock +++ b/demo/flutter/ios/Podfile.lock @@ -9,23 +9,23 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - Rhino-iOS (2.2.2): + - Rhino-iOS (3.0.0): - ios-voice-processor (~> 1.1.0) - - rhino_flutter (2.2.3): + - rhino_flutter (3.0.0): - Flutter - - Rhino-iOS (~> 2.2.2) + - Rhino-iOS (~> 3.0.0) DEPENDENCIES: - Flutter (from `Flutter`) - flutter_voice_processor (from `.symlinks/plugins/flutter_voice_processor/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - Rhino-iOS (from `https://raw.githubusercontent.com/Picovoice/rhino/v3.0/binding/ios/Rhino-iOS.podspec`) - rhino_flutter (from `.symlinks/plugins/rhino_flutter/ios`) SPEC REPOS: trunk: - ios-voice-processor - - Rhino-iOS EXTERNAL SOURCES: Flutter: @@ -36,6 +36,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/integration_test/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/ios" + Rhino-iOS: + :podspec: https://raw.githubusercontent.com/Picovoice/rhino/v3.0/binding/ios/Rhino-iOS.podspec rhino_flutter: :path: ".symlinks/plugins/rhino_flutter/ios" @@ -45,9 +47,9 @@ SPEC CHECKSUMS: integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 ios-voice-processor: 8e32d7f980a06d392d128ef1cd19cf6ddcaca3c1 path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 - Rhino-iOS: 0fad86b28d35f67ccb6bd0a2efbbcc0d88b05124 - rhino_flutter: 06eafa6dfcfae5b21696c7c3dab4007fdd46e5d9 + Rhino-iOS: b92978104bfad32da25b9eeaa4f0628b86ffcee9 + rhino_flutter: 78d56c9fa0355e4ee94fa59cb3a0d210b5d1e3f9 -PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d +PODFILE CHECKSUM: d649e1aef953c2b76883198c9a60c0dfd0334b0a COCOAPODS: 1.11.3 diff --git a/demo/flutter/pubspec.lock b/demo/flutter/pubspec.lock index 8151c8bdd..a5c07aa9f 100644 --- a/demo/flutter/pubspec.lock +++ b/demo/flutter/pubspec.lock @@ -211,10 +211,10 @@ packages: rhino_flutter: dependency: "direct main" description: - name: rhino_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "2.2.3" + path: "../../binding/flutter" + relative: true + source: path + version: "3.0.0" sky_engine: dependency: transitive description: flutter diff --git a/demo/flutter/pubspec.yaml b/demo/flutter/pubspec.yaml index f7b2385b2..c2d70f84c 100644 --- a/demo/flutter/pubspec.yaml +++ b/demo/flutter/pubspec.yaml @@ -13,7 +13,8 @@ dependencies: flutter: sdk: flutter - rhino_flutter: ^2.2.3 + rhino_flutter: + path: ../../binding/flutter dev_dependencies: integration_test: