diff --git a/.metadata b/.metadata
index b4fb38531..5c982c8d4 100644
--- a/.metadata
+++ b/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "35c388afb57ef061d06a39b537336c87e0e3d1b1"
+ revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
channel: "stable"
project_type: app
@@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
+ create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+ base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
- platform: ios
- create_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
- base_revision: 35c388afb57ef061d06a39b537336c87e0e3d1b1
+ create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+ base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
# User provided section
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index 48867bf1b..3dd7256fc 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -39,8 +39,6 @@ include(":spotify_sdk")
include(":ble_service")
include(":classic_bluetooth")
include(":agent_service")
-include(":volcano_speech")
-include(":bytedance_speech")
include(":speech")
// 设置azure_speech项目的路径
project(":azure_speech").projectDir = file("../local_plugins/azure_speech/android")
@@ -51,6 +49,4 @@ project(":spotify_sdk").projectDir = file("../local_plugins/spotify_sdk/android"
project(":ble_service").projectDir = file("../local_plugins/ble_service/android")
project(":classic_bluetooth").projectDir = file("../local_plugins/classic_bluetooth/android")
project(":agent_service").projectDir = file("../local_plugins/agent_service/android")
-project(":volcano_speech").projectDir = file("../local_plugins/volcano_speech/android")
-project(":bytedance_speech").projectDir = file("../local_plugins/bytedance_speech/android")
project(":speech").projectDir = file("../local_plugins/speech/android")
\ No newline at end of file
diff --git a/assets/config/GoogleService-Info.plist b/assets/config/GoogleService-Info.plist
new file mode 100644
index 000000000..c90ada3a3
--- /dev/null
+++ b/assets/config/GoogleService-Info.plist
@@ -0,0 +1,36 @@
+
+
+
+
+ CLIENT_ID
+ 907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com
+ REVERSED_CLIENT_ID
+ com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278
+ ANDROID_CLIENT_ID
+ 907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com
+ API_KEY
+ AIzaSyAvyBQcOXuO_q851vevqZIufsPRTMCe808
+ GCM_SENDER_ID
+ 907754492821
+ PLIST_VERSION
+ 1
+ BUNDLE_ID
+ com.yunqiinnovation.deepsound
+ PROJECT_ID
+ deepsound-e0e3d
+ STORAGE_BUCKET
+ deepsound-e0e3d.firebasestorage.app
+ IS_ADS_ENABLED
+
+ IS_ANALYTICS_ENABLED
+
+ IS_APPINVITE_ENABLED
+
+ IS_GCM_ENABLED
+
+ IS_SIGNIN_ENABLED
+
+ GOOGLE_APP_ID
+ 1:907754492821:ios:246794fc44b23174145c9c
+
+
\ No newline at end of file
diff --git a/ios/Podfile b/ios/Podfile
index c10c27d8b..4c7418ab9 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,4 +1,4 @@
-# Uncomment this line to define a global platform for your project
+# Define a global platform for your project
platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
@@ -31,10 +31,6 @@ target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-
- # 添加 Microsoft Cognitive Services Speech SDK
- pod 'MicrosoftCognitiveServicesSpeech-iOS', '~> 1.34.0'
-
target 'RunnerTests' do
inherit! :search_paths
end
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index cc790be8d..c5e3caec9 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -11,8 +11,43 @@ PODS:
- Flutter
- audio_waveforms (0.0.1):
- Flutter
+ - chat_storage (0.0.1):
+ - Flutter
+ - classic_bluetooth (0.0.1):
+ - Flutter
- device_info_plus (0.0.1):
- Flutter
+ - DKImagePickerController/Core (4.3.9):
+ - DKImagePickerController/ImageDataManager
+ - DKImagePickerController/Resource
+ - DKImagePickerController/ImageDataManager (4.3.9)
+ - DKImagePickerController/PhotoGallery (4.3.9):
+ - DKImagePickerController/Core
+ - DKPhotoGallery
+ - DKImagePickerController/Resource (4.3.9)
+ - DKPhotoGallery (0.0.19):
+ - DKPhotoGallery/Core (= 0.0.19)
+ - DKPhotoGallery/Model (= 0.0.19)
+ - DKPhotoGallery/Preview (= 0.0.19)
+ - DKPhotoGallery/Resource (= 0.0.19)
+ - SDWebImage
+ - SwiftyGif
+ - DKPhotoGallery/Core (0.0.19):
+ - DKPhotoGallery/Model
+ - DKPhotoGallery/Preview
+ - SDWebImage
+ - SwiftyGif
+ - DKPhotoGallery/Model (0.0.19):
+ - SDWebImage
+ - SwiftyGif
+ - DKPhotoGallery/Preview (0.0.19):
+ - DKPhotoGallery/Model
+ - DKPhotoGallery/Resource
+ - SDWebImage
+ - SwiftyGif
+ - DKPhotoGallery/Resource (0.0.19):
+ - SDWebImage
+ - SwiftyGif
- FBAEMKit (17.1.0):
- FBSDKCoreKit_Basics (= 17.1.0)
- FBSDKCoreKit (17.1.0):
@@ -21,36 +56,39 @@ PODS:
- FBSDKCoreKit_Basics (17.1.0)
- FBSDKLoginKit (17.1.0):
- FBSDKCoreKit (= 17.1.0)
- - Firebase/Auth (11.8.0):
+ - file_picker (0.0.1):
+ - DKImagePickerController/PhotoGallery
+ - Flutter
+ - Firebase/Auth (11.10.0):
- Firebase/CoreOnly
- - FirebaseAuth (~> 11.8.0)
- - Firebase/CoreOnly (11.8.0):
- - FirebaseCore (~> 11.8.0)
- - firebase_auth (5.5.1):
- - Firebase/Auth (= 11.8.0)
+ - FirebaseAuth (~> 11.10.0)
+ - Firebase/CoreOnly (11.10.0):
+ - FirebaseCore (~> 11.10.0)
+ - firebase_auth (5.5.2):
+ - Firebase/Auth (= 11.10.0)
- firebase_core
- Flutter
- - firebase_core (3.12.1):
- - Firebase/CoreOnly (= 11.8.0)
+ - firebase_core (3.13.0):
+ - Firebase/CoreOnly (= 11.10.0)
- Flutter
- - FirebaseAppCheckInterop (11.10.0)
- - FirebaseAuth (11.8.1):
+ - FirebaseAppCheckInterop (11.12.0)
+ - FirebaseAuth (11.10.0):
- FirebaseAppCheckInterop (~> 11.0)
- FirebaseAuthInterop (~> 11.0)
- - FirebaseCore (~> 11.8.0)
- - FirebaseCoreExtension (~> 11.8.0)
+ - FirebaseCore (~> 11.10.0)
+ - FirebaseCoreExtension (~> 11.10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
- GoogleUtilities/Environment (~> 8.0)
- GTMSessionFetcher/Core (< 5.0, >= 3.4)
- - RecaptchaInterop (~> 100.0)
- - FirebaseAuthInterop (11.10.0)
- - FirebaseCore (11.8.1):
- - FirebaseCoreInternal (~> 11.8.0)
+ - RecaptchaInterop (~> 101.0)
+ - FirebaseAuthInterop (11.12.0)
+ - FirebaseCore (11.10.0):
+ - FirebaseCoreInternal (~> 11.10.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- - FirebaseCoreExtension (11.8.0):
- - FirebaseCore (~> 11.8.0)
- - FirebaseCoreInternal (11.8.0):
+ - FirebaseCoreExtension (11.10.0):
+ - FirebaseCore (~> 11.10.0)
+ - FirebaseCoreInternal (11.10.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- Flutter (1.0.0)
- flutter_blue_plus_darwin (0.0.2):
@@ -86,25 +124,25 @@ PODS:
- AppAuth (< 2.0, >= 1.7.3)
- GTMAppAuth (< 5.0, >= 4.1.1)
- GTMSessionFetcher/Core (~> 3.3)
- - GoogleUtilities/AppDelegateSwizzler (8.0.2):
+ - GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- - GoogleUtilities/Environment (8.0.2):
+ - GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Privacy
- - GoogleUtilities/Logger (8.0.2):
+ - GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- - GoogleUtilities/Network (8.0.2):
+ - GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- - "GoogleUtilities/NSData+zlib (8.0.2)":
+ - "GoogleUtilities/NSData+zlib (8.1.0)":
- GoogleUtilities/Privacy
- - GoogleUtilities/Privacy (8.0.2)
- - GoogleUtilities/Reachability (8.0.2):
+ - GoogleUtilities/Privacy (8.1.0)
+ - GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GTMAppAuth (4.1.1):
@@ -115,8 +153,12 @@ PODS:
- GTMSessionFetcher/Core (3.5.0)
- GTMSessionFetcher/Full (3.5.0):
- GTMSessionFetcher/Core
+ - haptic_feedback (0.5.1):
+ - Flutter
- image_picker_ios (0.0.1):
- Flutter
+ - jl_ota (0.0.1):
+ - Flutter
- just_audio (0.0.1):
- Flutter
- FlutterMacOS
@@ -135,13 +177,27 @@ PODS:
- Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0)
- - MicrosoftCognitiveServicesSpeech-iOS (1.34.0)
+ - music_service (0.0.1):
+ - Flutter
+ - open_ai_service (0.0.1):
+ - Flutter
+ - package_info_plus (0.4.5):
+ - Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- - RecaptchaInterop (100.0.0)
+ - QCloudCore (6.4.7):
+ - QCloudCore/Default (= 6.4.7)
+ - QCloudCore/Default (6.4.7):
+ - QCloudTrack/Beacon (= 6.4.7)
+ - QCloudCOSXML (6.4.7):
+ - QCloudCOSXML/Default (= 6.4.7)
+ - QCloudCOSXML/Default (6.4.7):
+ - QCloudCore (= 6.4.7)
+ - QCloudTrack/Beacon (6.4.7)
+ - RecaptchaInterop (101.0.0)
- record_darwin (1.0.0):
- Flutter
- SDWebImage (5.21.0):
@@ -156,17 +212,29 @@ PODS:
- Flutter
- FlutterMacOS
- Try
+ - spotify_sdk (0.0.1):
+ - Flutter
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
+ - SwiftyGif (5.4.5)
+ - tencentcloud_cos_sdk_plugin (1.2.3):
+ - Flutter
+ - QCloudCOSXML (= 6.4.7)
- Try (2.1.1)
+ - webview_flutter_wkwebview (0.0.1):
+ - Flutter
+ - FlutterMacOS
- WechatOpenSDK-XCFramework (2.0.4)
DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`)
+ - chat_storage (from `.symlinks/plugins/chat_storage/ios`)
+ - classic_bluetooth (from `.symlinks/plugins/classic_bluetooth/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
+ - file_picker (from `.symlinks/plugins/file_picker/ios`)
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- Flutter (from `Flutter`)
@@ -178,19 +246,28 @@ DEPENDENCIES:
- flutter_tts (from `.symlinks/plugins/flutter_tts/ios`)
- fluwx (from `.symlinks/plugins/fluwx/ios`)
- google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
+ - haptic_feedback (from `.symlinks/plugins/haptic_feedback/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
+ - jl_ota (from `.symlinks/plugins/jl_ota/ios`)
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
- - MicrosoftCognitiveServicesSpeech-iOS (~> 1.34.0)
+ - music_service (from `.symlinks/plugins/music_service/ios`)
+ - open_ai_service (from `.symlinks/plugins/open_ai_service/ios`)
+ - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- speech_to_text (from `.symlinks/plugins/speech_to_text/darwin`)
+ - spotify_sdk (from `.symlinks/plugins/spotify_sdk/ios`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
+ - tencentcloud_cos_sdk_plugin (from `.symlinks/plugins/tencentcloud_cos_sdk_plugin/ios`)
+ - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
SPEC REPOS:
trunk:
- AppAuth
+ - DKImagePickerController
+ - DKPhotoGallery
- FBAEMKit
- FBSDKCoreKit
- FBSDKCoreKit_Basics
@@ -208,10 +285,13 @@ SPEC REPOS:
- GTMSessionFetcher
- libwebp
- Mantle
- - MicrosoftCognitiveServicesSpeech-iOS
+ - QCloudCore
+ - QCloudCOSXML
+ - QCloudTrack
- RecaptchaInterop
- SDWebImage
- SDWebImageWebPCoder
+ - SwiftyGif
- Try
- WechatOpenSDK-XCFramework
@@ -222,8 +302,14 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/audio_session/ios"
audio_waveforms:
:path: ".symlinks/plugins/audio_waveforms/ios"
+ chat_storage:
+ :path: ".symlinks/plugins/chat_storage/ios"
+ classic_bluetooth:
+ :path: ".symlinks/plugins/classic_bluetooth/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
+ file_picker:
+ :path: ".symlinks/plugins/file_picker/ios"
firebase_auth:
:path: ".symlinks/plugins/firebase_auth/ios"
firebase_core:
@@ -246,10 +332,20 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluwx/ios"
google_sign_in_ios:
:path: ".symlinks/plugins/google_sign_in_ios/darwin"
+ haptic_feedback:
+ :path: ".symlinks/plugins/haptic_feedback/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
+ jl_ota:
+ :path: ".symlinks/plugins/jl_ota/ios"
just_audio:
:path: ".symlinks/plugins/just_audio/darwin"
+ music_service:
+ :path: ".symlinks/plugins/music_service/ios"
+ open_ai_service:
+ :path: ".symlinks/plugins/open_ai_service/ios"
+ package_info_plus:
+ :path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
@@ -260,28 +356,39 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
speech_to_text:
:path: ".symlinks/plugins/speech_to_text/darwin"
+ spotify_sdk:
+ :path: ".symlinks/plugins/spotify_sdk/ios"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
+ tencentcloud_cos_sdk_plugin:
+ :path: ".symlinks/plugins/tencentcloud_cos_sdk_plugin/ios"
+ webview_flutter_wkwebview:
+ :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
+ chat_storage: 933a96144157a07a2fef7f6acb28c78ff18465d8
+ classic_bluetooth: c5e5c0c86acab8dcd6b8e9cef7fca654c3518034
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
+ DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
+ DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
FBAEMKit: cb719c53575a3be86ea873279f30d6a2c4e15881
FBSDKCoreKit: ecdb980a24633ccb012700299ceb16d0235e14d2
FBSDKCoreKit_Basics: 045101c4a9ef10c845347424d73a29aae02c3e43
FBSDKLoginKit: 69eb59b2f839aba635616df6e422acd0ca88030a
- Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
- firebase_auth: 9ebbd83276bf977dea1c74dfc199acf9d2a2f42f
- firebase_core: 8d552814f6c01ccde5d88939fced4ec26f2f5510
- FirebaseAppCheckInterop: 9664c858489710f682766ef54e2b6741d3b62070
- FirebaseAuth: ad59a1a7b161e75f74c39f70179d2482d40e2737
- FirebaseAuthInterop: 01a804fb074424fd58b92dd50dd0272277199356
- FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
- FirebaseCoreExtension: 3d3f2017a00d06e09ab4ebe065391b0bb642565e
- FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
+ file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
+ Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
+ firebase_auth: 34c515a8fa76a4ef678ac5ad5cfa3bb3120f5ab5
+ firebase_core: 2d4534e7b489907dcede540c835b48981d890943
+ FirebaseAppCheckInterop: 73b173e5ec45192e2d522ad43f526a82ad10b852
+ FirebaseAuth: c4146bdfdc87329f9962babd24dae89373f49a32
+ FirebaseAuthInterop: b583210c039a60ed3f1e48865e1f3da44a796595
+ FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7
+ FirebaseCoreExtension: 6f357679327f3614e995dc7cf3f2d600bdc774ac
+ FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_blue_plus_darwin: 09444a26fb6bdef523e55b68fc1c59af5a877ea6
flutter_facebook_auth: 8a9ad5fbfc25b77e7a53ea8986866950b27edcae
@@ -292,26 +399,37 @@ SPEC CHECKSUMS:
fluwx: 6bf9c5a3a99ad31b0de137dd92370a0d10a60f4b
google_sign_in_ios: 19297361f2c51d7d8ac0201b866ef1fa5d1f94a8
GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db
- GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
+ GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
+ haptic_feedback: fd1d8509833f58f164fc12122c0357fa9b2750e0
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
+ jl_ota: 5fa90bf47674de0f5bce9af2219e25fa0c1425f5
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
- MicrosoftCognitiveServicesSpeech-iOS: 723eccb3e459bbb530df3d9bfe84a291074b09eb
+ music_service: 85abecc8fa21599a06907ad50d83f48a58e0dc0d
+ open_ai_service: 82ea947b1a081a2a17bc0c8e5467dcbd7cf9b232
+ package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
- RecaptchaInterop: 7d1a4a01a6b2cb1610a47ef3f85f0c411434cb21
+ QCloudCore: 49e7571984a8e5fd8f0fd58c2f3b806255796223
+ QCloudCOSXML: 7205e76aa9cf613468222615483c9d7c074e4298
+ QCloudTrack: 3b53a7fc4fe3920e407f2aa73f2452992a61f7f3
+ RecaptchaInterop: 11e0b637842dfb48308d242afc3f448062325aba
record_darwin: fb1f375f1d9603714f55b8708a903bbb91ffdb0a
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
speech_to_text: 9dc43a5df3cbc2813f8c7cc9bd0fbf94268ed7ac
+ spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
+ SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
+ tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b
Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96
+ webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
-PODFILE CHECKSUM: 4f26889ae572e1722a139b651939749d40c71469
+PODFILE CHECKSUM: 017ca79b199d8f37c4acb8d83ec912447003b920
COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index b46ef17e1..82ce9f671 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -7,25 +7,16 @@
objects = {
/* Begin PBXBuildFile section */
- 0A5EDBFC2D89C4FD00215E26 /* ClassicBluetoothHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5EDBFB2D89C4FD00215E26 /* ClassicBluetoothHelper.swift */; };
- 0A5EDC042D8B544100215E26 /* BluetoothMediaButtonHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5EDC032D8B544100215E26 /* BluetoothMediaButtonHelper.swift */; };
- 0A7A32E42D8DBA6D0047F746 /* VoiceInteractionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7A32E22D8DBA6D0047F746 /* VoiceInteractionService.swift */; };
- 0A7A32E52D8DBA6D0047F746 /* VolcanoAIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7A32E32D8DBA6D0047F746 /* VolcanoAIService.swift */; };
- 0ADE29692D8C25660032BFCB /* silence.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 0ADE29682D8C25660032BFCB /* silence.mp3 */; };
- 0ADE296A2D8C25660032BFCB /* sample.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 0ADE29672D8C25660032BFCB /* sample.mp3 */; };
- 0ADE296D2D8C66D60032BFCB /* AzureTtsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADE296C2D8C66D60032BFCB /* AzureTtsHelper.swift */; };
- 0ADE296E2D8C66D60032BFCB /* AzureAsrHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADE296B2D8C66D60032BFCB /* AzureAsrHelper.swift */; };
- 0ADE29702D8C6C450032BFCB /* AudioSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ADE296F2D8C6C450032BFCB /* AudioSessionManager.swift */; };
+ 0AD944122DD9DB5C00557D21 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
- 3406320CAE3314A73EF78043 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BBE10AD7954527B6BB12CFB /* Pods_RunnerTests.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
- 591293932D9524D1004A65BB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 591293922D9524D1004A65BB /* GoogleService-Info.plist */; };
+ 6077538D78AAB34545D7F3DD /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E27AD0DBE6531F2655DB9986 /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
- E1D2128FAB4726BEEED42F4C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4A9190D0838960F143BDC158 /* Pods_Runner.framework */; };
+ A7986DFCFDA104638080424B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -52,32 +43,18 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
- 02CF74744224D1C933681603 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
- 0906EF7A73CBA5EAFC64E05D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
- 0A5EDBFB2D89C4FD00215E26 /* ClassicBluetoothHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassicBluetoothHelper.swift; sourceTree = ""; };
- 0A5EDC032D8B544100215E26 /* BluetoothMediaButtonHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothMediaButtonHelper.swift; sourceTree = ""; };
- 0A7A32E22D8DBA6D0047F746 /* VoiceInteractionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceInteractionService.swift; sourceTree = ""; };
- 0A7A32E32D8DBA6D0047F746 /* VolcanoAIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolcanoAIService.swift; sourceTree = ""; };
- 0ADE29672D8C25660032BFCB /* sample.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = sample.mp3; sourceTree = ""; };
- 0ADE29682D8C25660032BFCB /* silence.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = silence.mp3; sourceTree = ""; };
- 0ADE296B2D8C66D60032BFCB /* AzureAsrHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzureAsrHelper.swift; sourceTree = ""; };
- 0ADE296C2D8C66D60032BFCB /* AzureTtsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AzureTtsHelper.swift; sourceTree = ""; };
- 0ADE296F2D8C6C450032BFCB /* AudioSessionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSessionManager.swift; sourceTree = ""; };
+ 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
- 4A9190D0838960F143BDC158 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 5912938C2D9520CE004A65BB /* RunnerRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerRelease.entitlements; sourceTree = ""; };
- 591293922D9524D1004A65BB /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; };
- 5BBE10AD7954527B6BB12CFB /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6189535D5FBB81D8377BE874 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+ 742A00629970110BA5008064 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
- 77D16D761891F29239C8DF02 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 762C8874AAE15CA0CB901347 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
- 8C1130E20389847CBD7B1AAC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
- 930FE6D58BDB20688CE0A7CF /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -85,7 +62,12 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
- 9B84B894288BF76332D3F407 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ B3EC1304D6F5453D58E0BDB0 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ C15B3030DF1AED99D3829135 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; };
+ D1CF75E5BD49BB58812D5F36 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; };
+ D2958658C06A5ADF62D6ABA2 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; };
+ E27AD0DBE6531F2655DB9986 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -93,40 +75,49 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- E1D2128FAB4726BEEED42F4C /* Pods_Runner.framework in Frameworks */,
+ 6077538D78AAB34545D7F3DD /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
- B4459155A2535A0ED1D6C7D8 /* Frameworks */ = {
+ B3515CD9628E3332AFC88CD4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 3406320CAE3314A73EF78043 /* Pods_RunnerTests.framework in Frameworks */,
+ A7986DFCFDA104638080424B /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ 1ACB686EC7DC36C6E33D372F /* Pods */ = {
isa = PBXGroup;
children = (
- 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ 762C8874AAE15CA0CB901347 /* Pods-Runner.debug.xcconfig */,
+ B3EC1304D6F5453D58E0BDB0 /* Pods-Runner.release.xcconfig */,
+ 6189535D5FBB81D8377BE874 /* Pods-Runner.profile.xcconfig */,
+ C15B3030DF1AED99D3829135 /* Pods-RunnerTests.debug.xcconfig */,
+ 742A00629970110BA5008064 /* Pods-RunnerTests.release.xcconfig */,
+ D1CF75E5BD49BB58812D5F36 /* Pods-RunnerTests.profile.xcconfig */,
);
- path = RunnerTests;
+ path = Pods;
sourceTree = "";
};
- 776E5AB1CE0A62CBDBAB3996 /* Pods */ = {
+ 1E5DDD8701F6FB27AA502080 /* Frameworks */ = {
isa = PBXGroup;
children = (
- 8C1130E20389847CBD7B1AAC /* Pods-Runner.debug.xcconfig */,
- 77D16D761891F29239C8DF02 /* Pods-Runner.release.xcconfig */,
- 0906EF7A73CBA5EAFC64E05D /* Pods-Runner.profile.xcconfig */,
- 930FE6D58BDB20688CE0A7CF /* Pods-RunnerTests.debug.xcconfig */,
- 02CF74744224D1C933681603 /* Pods-RunnerTests.release.xcconfig */,
- 9B84B894288BF76332D3F407 /* Pods-RunnerTests.profile.xcconfig */,
+ E27AD0DBE6531F2655DB9986 /* Pods_Runner.framework */,
+ F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */,
);
- path = Pods;
+ name = Frameworks;
+ sourceTree = "";
+ };
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
sourceTree = "";
};
9740EEB11CF90186004384FC /* Flutter */ = {
@@ -147,8 +138,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
- 776E5AB1CE0A62CBDBAB3996 /* Pods */,
- A5A9FDE94E78257E9A48E2E8 /* Frameworks */,
+ 1ACB686EC7DC36C6E33D372F /* Pods */,
+ 1E5DDD8701F6FB27AA502080 /* Frameworks */,
);
sourceTree = "";
};
@@ -164,18 +155,8 @@
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
- 5912938C2D9520CE004A65BB /* RunnerRelease.entitlements */,
- 0A7A32E22D8DBA6D0047F746 /* VoiceInteractionService.swift */,
- 0A7A32E32D8DBA6D0047F746 /* VolcanoAIService.swift */,
- 0ADE296F2D8C6C450032BFCB /* AudioSessionManager.swift */,
- 0ADE296B2D8C66D60032BFCB /* AzureAsrHelper.swift */,
- 0ADE296C2D8C66D60032BFCB /* AzureTtsHelper.swift */,
- 0ADE29672D8C25660032BFCB /* sample.mp3 */,
- 0ADE29682D8C25660032BFCB /* silence.mp3 */,
- 0A5EDC032D8B544100215E26 /* BluetoothMediaButtonHelper.swift */,
- 0A5EDBFB2D89C4FD00215E26 /* ClassicBluetoothHelper.swift */,
+ 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
- 591293922D9524D1004A65BB /* GoogleService-Info.plist */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
@@ -183,19 +164,11 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ D2958658C06A5ADF62D6ABA2 /* Runner.entitlements */,
);
path = Runner;
sourceTree = "";
};
- A5A9FDE94E78257E9A48E2E8 /* Frameworks */ = {
- isa = PBXGroup;
- children = (
- 4A9190D0838960F143BDC158 /* Pods_Runner.framework */,
- 5BBE10AD7954527B6BB12CFB /* Pods_RunnerTests.framework */,
- );
- name = Frameworks;
- sourceTree = "";
- };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -203,10 +176,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
- 1BD7F444CF127CDFCB7F9F47 /* [CP] Check Pods Manifest.lock */,
+ D21548F12B1535261EB87672 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
- B4459155A2535A0ED1D6C7D8 /* Frameworks */,
+ B3515CD9628E3332AFC88CD4 /* Frameworks */,
);
buildRules = (
);
@@ -222,15 +195,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
- 8A6DB356BF41E96BFD7DF109 /* [CP] Check Pods Manifest.lock */,
+ 90215E32C3610039A2575557 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
- 4A5C19D622EF675DB4056F07 /* [CP] Embed Pods Frameworks */,
- D49299AF1487DF3783287D24 /* [CP] Copy Pods Resources */,
+ 510B02006C08E514BF31EFD9 /* [CP] Embed Pods Frameworks */,
+ 298589D956D542AE0EE16DAE /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -270,9 +243,6 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
- packageReferences = (
- 5912938D2D95215F004A65BB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
- );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -297,37 +267,30 @@
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 0AD944122DD9DB5C00557D21 /* GoogleService-Info.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
- 0ADE29692D8C25660032BFCB /* silence.mp3 in Resources */,
- 0ADE296A2D8C25660032BFCB /* sample.mp3 in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
- 591293932D9524D1004A65BB /* GoogleService-Info.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 1BD7F444CF127CDFCB7F9F47 /* [CP] Check Pods Manifest.lock */ = {
+ 298589D956D542AE0EE16DAE /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
+ name = "[CP] Copy Pods Resources";
outputFileListPaths = (
- );
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
@@ -346,7 +309,7 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
- 4A5C19D622EF675DB4056F07 /* [CP] Embed Pods Frameworks */ = {
+ 510B02006C08E514BF31EFD9 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -363,7 +326,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- 8A6DB356BF41E96BFD7DF109 /* [CP] Check Pods Manifest.lock */ = {
+ 90215E32C3610039A2575557 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -400,21 +363,26 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
- D49299AF1487DF3783287D24 /* [CP] Copy Pods Resources */ = {
+ D21548F12B1535261EB87672 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Copy Pods Resources";
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -432,14 +400,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 0ADE296D2D8C66D60032BFCB /* AzureTtsHelper.swift in Sources */,
- 0ADE296E2D8C66D60032BFCB /* AzureAsrHelper.swift in Sources */,
- 0A5EDBFC2D89C4FD00215E26 /* ClassicBluetoothHelper.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
- 0A5EDC042D8B544100215E26 /* BluetoothMediaButtonHelper.swift in Sources */,
- 0A7A32E42D8DBA6D0047F746 /* VoiceInteractionService.swift in Sources */,
- 0A7A32E52D8DBA6D0047F746 /* VolcanoAIService.swift in Sources */,
- 0ADE29702D8C6C450032BFCB /* AudioSessionManager.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -498,6 +459,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -532,16 +494,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 23;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -552,64 +513,50 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 930FE6D58BDB20688CE0A7CF /* Pods-RunnerTests.debug.xcconfig */;
+ baseConfigurationReference = C15B3030DF1AED99D3829135 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
- CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 42C82SZUVX;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
- SUPPORTS_MACCATALYST = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 02CF74744224D1C933681603 /* Pods-RunnerTests.release.xcconfig */;
+ baseConfigurationReference = 742A00629970110BA5008064 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 42C82SZUVX;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
- SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9B84B894288BF76332D3F407 /* Pods-RunnerTests.profile.xcconfig */;
+ baseConfigurationReference = D1CF75E5BD49BB58812D5F36 /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
- DEVELOPMENT_TEAM = 42C82SZUVX;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
+ PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
- SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
@@ -638,6 +585,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -695,6 +643,7 @@
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
@@ -731,21 +680,17 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_IDENTITY = "Apple Development";
- CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 23;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
- PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -759,17 +704,15 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
- CURRENT_PROJECT_VERSION = 23;
+ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42C82SZUVX;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
- INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = com.yunqiinnovation.deepsound;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -812,17 +755,6 @@
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
-
-/* Begin XCRemoteSwiftPackageReference section */
- 5912938D2D95215F004A65BB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = {
- isa = XCRemoteSwiftPackageReference;
- repositoryURL = "https://github.com/firebase/firebase-ios-sdk";
- requirement = {
- kind = upToNextMajorVersion;
- minimumVersion = 11.10.0;
- };
- };
-/* End XCRemoteSwiftPackageReference section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 7ee26c6b2..000000000
--- a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
- "pins" : [
- {
- "identity" : "abseil-cpp-binary",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/abseil-cpp-binary.git",
- "state" : {
- "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
- "version" : "1.2024072200.0"
- }
- },
- {
- "identity" : "app-check",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/app-check.git",
- "state" : {
- "revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
- "version" : "11.2.0"
- }
- },
- {
- "identity" : "firebase-ios-sdk",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/firebase-ios-sdk",
- "state" : {
- "revision" : "2e4a4daac9da51f79c131e76cb47e785dd7708c4",
- "version" : "11.10.0"
- }
- },
- {
- "identity" : "googleappmeasurement",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleAppMeasurement.git",
- "state" : {
- "revision" : "defe1970a9099d47765592ebd7ac5f5e9fdb57c6",
- "version" : "11.10.0"
- }
- },
- {
- "identity" : "googledatatransport",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleDataTransport.git",
- "state" : {
- "revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
- "version" : "10.1.0"
- }
- },
- {
- "identity" : "googleutilities",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleUtilities.git",
- "state" : {
- "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
- "version" : "8.0.2"
- }
- },
- {
- "identity" : "grpc-binary",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/grpc-binary.git",
- "state" : {
- "revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
- "version" : "1.69.0"
- }
- },
- {
- "identity" : "gtm-session-fetcher",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/gtm-session-fetcher.git",
- "state" : {
- "revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
- "version" : "4.4.0"
- }
- },
- {
- "identity" : "interop-ios-for-google-sdks",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/interop-ios-for-google-sdks.git",
- "state" : {
- "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
- "version" : "101.0.0"
- }
- },
- {
- "identity" : "leveldb",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/leveldb.git",
- "state" : {
- "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
- "version" : "1.22.5"
- }
- },
- {
- "identity" : "nanopb",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/nanopb.git",
- "state" : {
- "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
- "version" : "2.30910.0"
- }
- },
- {
- "identity" : "promises",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/promises.git",
- "state" : {
- "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
- "version" : "2.4.0"
- }
- },
- {
- "identity" : "swift-protobuf",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-protobuf.git",
- "state" : {
- "revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
- "version" : "1.29.0"
- }
- }
- ],
- "version" : 3
-}
diff --git a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
deleted file mode 100644
index 7ee26c6b2..000000000
--- a/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ /dev/null
@@ -1,123 +0,0 @@
-{
- "originHash" : "c63c63846d9c539229e96de38d6af51417e28c0ee9a0bc48bd0f0f19d923c329",
- "pins" : [
- {
- "identity" : "abseil-cpp-binary",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/abseil-cpp-binary.git",
- "state" : {
- "revision" : "bbe8b69694d7873315fd3a4ad41efe043e1c07c5",
- "version" : "1.2024072200.0"
- }
- },
- {
- "identity" : "app-check",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/app-check.git",
- "state" : {
- "revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
- "version" : "11.2.0"
- }
- },
- {
- "identity" : "firebase-ios-sdk",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/firebase-ios-sdk",
- "state" : {
- "revision" : "2e4a4daac9da51f79c131e76cb47e785dd7708c4",
- "version" : "11.10.0"
- }
- },
- {
- "identity" : "googleappmeasurement",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleAppMeasurement.git",
- "state" : {
- "revision" : "defe1970a9099d47765592ebd7ac5f5e9fdb57c6",
- "version" : "11.10.0"
- }
- },
- {
- "identity" : "googledatatransport",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleDataTransport.git",
- "state" : {
- "revision" : "617af071af9aa1d6a091d59a202910ac482128f9",
- "version" : "10.1.0"
- }
- },
- {
- "identity" : "googleutilities",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/GoogleUtilities.git",
- "state" : {
- "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb",
- "version" : "8.0.2"
- }
- },
- {
- "identity" : "grpc-binary",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/grpc-binary.git",
- "state" : {
- "revision" : "cc0001a0cf963aa40501d9c2b181e7fc9fd8ec71",
- "version" : "1.69.0"
- }
- },
- {
- "identity" : "gtm-session-fetcher",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/gtm-session-fetcher.git",
- "state" : {
- "revision" : "4d70340d55d7d07cc2fdf8e8125c4c126c1d5f35",
- "version" : "4.4.0"
- }
- },
- {
- "identity" : "interop-ios-for-google-sdks",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/interop-ios-for-google-sdks.git",
- "state" : {
- "revision" : "040d087ac2267d2ddd4cca36c757d1c6a05fdbfe",
- "version" : "101.0.0"
- }
- },
- {
- "identity" : "leveldb",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/leveldb.git",
- "state" : {
- "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
- "version" : "1.22.5"
- }
- },
- {
- "identity" : "nanopb",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/firebase/nanopb.git",
- "state" : {
- "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
- "version" : "2.30910.0"
- }
- },
- {
- "identity" : "promises",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/google/promises.git",
- "state" : {
- "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
- "version" : "2.4.0"
- }
- },
- {
- "identity" : "swift-protobuf",
- "kind" : "remoteSourceControl",
- "location" : "https://github.com/apple/swift-protobuf.git",
- "state" : {
- "revision" : "d72aed98f8253ec1aa9ea1141e28150f408cf17f",
- "version" : "1.29.0"
- }
- }
- ],
- "version" : 3
-}
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 3bfd2c086..626664468 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -1,743 +1,13 @@
import Flutter
import UIKit
-import AVFoundation
@main
@objc class AppDelegate: FlutterAppDelegate {
- private var classicBluetoothHelper: ClassicBluetoothHelper?
- private var mediaButtonHelper: Any?
- // 添加Azure语音服务辅助类
- private var azureAsrHelper: Any?
- private var azureTtsHelper: Any?
- // 添加火山AI服务
- private var volcanoAIService: VolcanoAIService?
- // 语音交互服务
- private var voiceInteractionService: VoiceInteractionService?
-
- private var audioSessionManager: AudioSessionManager?
-
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
-
- // 获取flutter控制器
- guard let controller = window?.rootViewController as? FlutterViewController else {
- NSLog("AppDelegate: Flutter控制器不可用")
- return false
- }
-
- // 初始化音频会话管理器
- audioSessionManager = AudioSessionManager.shared
-
-
- // 统一注册所有方法通道
- registerMethodChannel()
-
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
-
- private func registerMethodChannel() {
- let controller = window?.rootViewController as? FlutterViewController
- guard let controller = controller else {
- NSLog("AppDelegate: FlutterViewController 不可用")
- return
- }
-
- // 注册经典蓝牙方法通道
- registerClassicBluetoothChannels(controller: controller)
-
- // 仅在iOS 13.0及以上版本注册媒体按钮通道和Azure语音服务通道
- if #available(iOS 13.0, *) {
- // 注册媒体按钮方法通道
- registerMediaButtonChannels(controller: controller)
-
- // 注册Azure ASR方法通道
- registerAzureAsrChannels(controller: controller)
-
- // 注册Azure TTS方法通道
- registerAzureTtsChannels(controller: controller)
-
- // 注册火山AI服务方法通道
- registerVolcanoAIServiceChannels(controller: controller)
-
- // 注册语音交互服务方法通道
- registerVoiceInteractionChannels(controller: controller)
- }
- }
-
- // MARK: - 经典蓝牙通道
-
- private func registerClassicBluetoothChannels(controller: FlutterViewController) {
- // 初始化蓝牙辅助类
- if classicBluetoothHelper == nil {
- classicBluetoothHelper = ClassicBluetoothHelper()
- }
-
- guard let classicBluetoothHelper = self.classicBluetoothHelper else {
- NSLog("AppDelegate: 经典蓝牙辅助实例不可用")
- return
- }
-
- // 注册经典蓝牙方法通道
- let classicBluetoothChannel = FlutterMethodChannel(
- name: "com.deep_voice.classic_bluetooth",
- binaryMessenger: controller.binaryMessenger
- )
-
- classicBluetoothChannel.setMethodCallHandler { [weak self, weak classicBluetoothHelper] (call, result) in
- guard let _ = self, let classicBluetoothHelper = classicBluetoothHelper else {
- result(FlutterError(code: "UNAVAILABLE", message: "经典蓝牙辅助实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "isBluetoothEnabled":
- result(classicBluetoothHelper.isBluetoothEnabled())
-
- case "getConnectedHeadsetDevices":
- // 获取已连接的耳机设备
- classicBluetoothHelper.getConnectedHeadsetDevices { devices, error in
- if let error = error {
- result(FlutterError(code: "GET_DEVICES_FAILED", message: error, details: nil))
- } else {
- result(devices)
- }
- }
- case "initialize":
- classicBluetoothHelper.initialize()
- result(true)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册蓝牙流通道
- let classicBluetoothStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.classic_bluetooth_events",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建蓝牙流处理器(作为局部变量)
- let bluetoothStreamHandler = BluetoothStreamHandler(bluetoothHelper: classicBluetoothHelper)
- classicBluetoothStreamChannel.setStreamHandler(bluetoothStreamHandler)
- }
-
- // MARK: - 消息通道设置
-
- // MARK: - 蓝牙媒体按键通道
-
- @available(iOS 13.0, *)
- private func registerMediaButtonChannels(controller: FlutterViewController) {
- // 初始化媒体按钮辅助类
- if mediaButtonHelper == nil {
- mediaButtonHelper = BluetoothMediaButtonHelper()
- }
- // guard let mediaButtonHelper = self.mediaButtonHelper as? BluetoothMediaButtonHelper else {
- // NSLog("AppDelegate: 媒体按钮辅助实例不可用")
- // return
- // }
- // mediaButtonHelper.startButtonListening()
- // 注册媒体按钮方法通道
- let mediaButtonChannel = FlutterMethodChannel(
- name: "com.deep_voice.bluetooth_media_button",
- binaryMessenger: controller.binaryMessenger
- )
-
- mediaButtonChannel.setMethodCallHandler { [weak self] (call, result) in
- guard let _ = self, let mediaButtonHelper = self?.mediaButtonHelper as? BluetoothMediaButtonHelper else {
- result(FlutterError(code: "UNAVAILABLE", message: "媒体按钮辅助实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "startButtonListening":
- let success = mediaButtonHelper.startButtonListening()
- result(success)
-
- case "stopButtonListening":
- let success = mediaButtonHelper.stopButtonListening()
- result(success)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册媒体按钮流通道
- let mediaButtonStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.bluetooth_media_button_events",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建媒体按钮流处理器
- let mediaButtonStreamHandler = MediaButtonStreamHandler(mediaButtonHelper: mediaButtonHelper as! BluetoothMediaButtonHelper)
- mediaButtonStreamChannel.setStreamHandler(mediaButtonStreamHandler)
- }
-
-
- // 注册Azure ASR通道
- @available(iOS 13.0, *)
- private func registerAzureAsrChannels(controller: FlutterViewController) {
- // 注册Azure ASR方法通道
- let azureAsrChannel = FlutterMethodChannel(
- name: "com.deep_voice.azure_asr",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 初始化Azure ASR辅助类
- azureAsrHelper = AzureAsrHelper()
-
- // 设置ASR方法处理器
- azureAsrChannel.setMethodCallHandler { [weak self] (call, result) in
- guard let self = self,
- let asrHelper = self.azureAsrHelper as? AzureAsrHelper else {
- result(FlutterError(code: "UNAVAILABLE", message: "Azure ASR辅助实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "initialize":
- guard let args = call.arguments as? [String: Any],
- let key = args["subscriptionKey"] as? String,
- let region = args["region"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "初始化参数无效", details: nil))
- return
- }
-
- let languages = args["supportedLanguages"] as? [String]
- let success = asrHelper.initialize(speechSubscriptionKey: key, serviceRegion: region, supportedLanguages: languages)
- result(success)
-
- case "recognizeOnce":
- let success = asrHelper.recognizeOnce()
- result(success)
-
- case "startContinuousRecognition":
- let success = asrHelper.startContinuousRecognition()
- result(success)
-
- case "stopContinuousRecognition":
- let success = asrHelper.stopContinuousRecognition()
- result(success)
-
- case "isContinuousRecognitionActive":
- let isActive = asrHelper.isContinuousRecognitionActive()
- result(isActive)
-
- case "dispose":
- asrHelper.dispose()
- result(true)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册Azure ASR流通道
- let azureAsrStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.azure_asr_events",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建ASR流处理器
- let asrStreamHandler = AsrStreamHandler(azureAsrHelper: azureAsrHelper as! AzureAsrHelper)
- azureAsrStreamChannel.setStreamHandler(asrStreamHandler)
- }
-
- // 注册Azure TTS通道
- @available(iOS 13.0, *)
- private func registerAzureTtsChannels(controller: FlutterViewController) {
- // 注册Azure TTS方法通道
- let azureTtsChannel = FlutterMethodChannel(
- name: "com.deep_voice.azure_tts",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 初始化Azure TTS辅助类
- azureTtsHelper = AzureTtsHelper()
-
- // 设置TTS方法处理器
- azureTtsChannel.setMethodCallHandler { [weak self] (call, result) in
- guard let self = self,
- let ttsHelper = self.azureTtsHelper as? AzureTtsHelper else {
- result(FlutterError(code: "UNAVAILABLE", message: "Azure TTS辅助实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "initialize":
- guard let args = call.arguments as? [String: Any],
- let key = args["subscriptionKey"] as? String,
- let region = args["region"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "初始化参数无效", details: nil))
- return
- }
-
- let language = args["language"] as? String ?? "zh-CN"
- let success = ttsHelper.initialize(speechSubscriptionKey: key, serviceRegion: region, language: language)
- result(success)
-
- case "setVoice":
- guard let args = call.arguments as? [String: Any],
- let voiceName = args["voiceName"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "语音名称参数无效", details: nil))
- return
- }
-
- let success = ttsHelper.setVoice(voiceName: voiceName)
- result(success)
-
- case "setSpeechParams":
- guard let args = call.arguments as? [String: Any] else {
- result(FlutterError(code: "INVALID_ARGS", message: "语音参数无效", details: nil))
- return
- }
-
- let rate = args["rate"] as? Int ?? 0
- let pitch = args["pitch"] as? Int ?? 0
- let volume = args["volume"] as? Int ?? 100
-
- let success = ttsHelper.setSpeechParams(rate: rate, pitch: pitch, volume: volume)
- result(success)
-
- case "speakText":
- guard let args = call.arguments as? [String: Any],
- let text = args["text"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "文本参数无效", details: nil))
- return
- }
-
- let success = ttsHelper.speakText(text: text)
- result(success)
-
- case "stopSpeaking":
- let success = ttsHelper.stopSpeaking()
- result(success)
-
- case "isSpeaking":
- let isSpeaking = ttsHelper.isSpeaking()
- result(isSpeaking)
-
- case "dispose":
- ttsHelper.dispose()
- result(true)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册Azure TTS流通道
- let azureTtsStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.azure_tts_events",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建TTS流处理器
- let ttsStreamHandler = TtsStreamHandler(azureTtsHelper: azureTtsHelper as! AzureTtsHelper)
- azureTtsStreamChannel.setStreamHandler(ttsStreamHandler)
- }
-
- // 注册火山AI服务方法通道
- @available(iOS 13.0, *)
- private func registerVolcanoAIServiceChannels(controller: FlutterViewController) {
- guard let volcanoAIService = volcanoAIService else {
- return
- }
-
- // 注册火山AI方法通道
- let volcanoAIChannel = FlutterMethodChannel(
- name: "com.deep_voice.volcano_ai",
- binaryMessenger: controller.binaryMessenger
- )
-
- volcanoAIChannel.setMethodCallHandler { [weak self, weak volcanoAIService] (call, result) in
- guard let _ = self, let volcanoAIService = volcanoAIService else {
- result(FlutterError(code: "UNAVAILABLE", message: "火山AI服务实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "initialize":
- let apiKey = call.arguments as? String ?? ""
- let success = volcanoAIService.initialize(apiKey: apiKey)
- result(success)
-
- case "sendMessage":
- guard let arguments = call.arguments as? [String: Any],
- let message = arguments["message"] as? String,
- let systemPrompt = arguments["systemPrompt"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
- return
- }
-
- if message.isEmpty {
- result(FlutterError(code: "EMPTY_MESSAGE", message: "消息不能为空", details: nil))
- return
- }
-
- // 异步发送消息
- Task {
- do {
- // 创建用户消息
- let userMessage = volcanoAIService.createUserMessage(content: message)
-
- // 发送消息并等待响应
- let response = try await volcanoAIService.sendMessage(
- messages: [userMessage],
- systemPrompt: systemPrompt
- )
-
- // 返回响应
- DispatchQueue.main.async {
- result(response)
- }
- } catch {
- // 处理错误
- DispatchQueue.main.async {
- result(FlutterError(
- code: "AI_ERROR",
- message: "AI请求失败: \(error.localizedDescription)",
- details: nil
- ))
- }
- }
- }
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册火山AI流通道
- let volcanoAIStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.volcano_ai_stream",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建火山AI流处理器(作为局部变量)
- let volcanoAIStreamHandler = VolcanoAIStreamHandler(volcanoAIService: volcanoAIService)
- volcanoAIStreamChannel.setStreamHandler(volcanoAIStreamHandler)
- }
-
- // 注册语音交互服务方法通道
- @available(iOS 13.0, *)
- private func registerVoiceInteractionChannels(controller: FlutterViewController) {
- // 初始化语音交互服务
- voiceInteractionService = VoiceInteractionService.shared
-
- // 注册语音交互服务方法通道
- let voiceInteractionChannel = FlutterMethodChannel(
- name: "com.deep_voice.voice_interaction",
- binaryMessenger: controller.binaryMessenger
- )
-
- voiceInteractionChannel.setMethodCallHandler { [weak self, weak voiceInteractionService] (call, result) in
- guard let _ = self, let voiceInteractionService = voiceInteractionService else {
- result(FlutterError(code: "UNAVAILABLE", message: "语音交互服务实例不可用", details: nil))
- return
- }
-
- switch call.method {
- case "startService":
- guard let args = call.arguments as? [String: Any],
- let azureKey = args["azure_speech_key"] as? String,
- let azureRegion = args["azure_speech_region"] as? String,
- let volcanoKey = args["volcano_ai_api_key"] as? String else {
- result(FlutterError(code: "INVALID_ARGS", message: "启动服务参数无效", details: nil))
- return
- }
-
- let success = voiceInteractionService.start(azureKey: azureKey, azureRegion: azureRegion, volcanoKey: volcanoKey)
- result(success)
-
- case "stopService":
- voiceInteractionService.stop()
- result(true)
-
- case "isServiceRunning":
- let isRunning = voiceInteractionService.isServiceRunning()
- result(isRunning)
-
- case "pauseVoiceInteraction":
- voiceInteractionService.pauseVoiceInteraction()
- result(true)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // 注册语音交互流通道
- let voiceInteractionStreamChannel = FlutterEventChannel(
- name: "com.deep_voice.voice_interaction_events",
- binaryMessenger: controller.binaryMessenger
- )
-
- // 创建语音交互流处理器(作为局部变量)
- let voiceInteractionStreamHandler = VoiceInteractionStreamHandler(voiceInteractionService: voiceInteractionService!)
- voiceInteractionStreamChannel.setStreamHandler(voiceInteractionStreamHandler)
- }
-}
-
-// 蓝牙事件流处理器
-class BluetoothStreamHandler: NSObject, FlutterStreamHandler, BluetoothEventHandler {
- private let bluetoothHelper: ClassicBluetoothHelper
- private var eventSink: FlutterEventSink?
-
- init(bluetoothHelper: ClassicBluetoothHelper) {
- self.bluetoothHelper = bluetoothHelper
- super.init()
-
- // 设置事件处理器
- bluetoothHelper.setEventHandler(self)
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- // NSLog("BluetoothStreamHandler: 开始监听蓝牙事件")
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- // NSLog("BluetoothStreamHandler: 取消监听蓝牙事件")
- eventSink = nil
- return nil
- }
-
- // 实现BluetoothEventHandler协议
- func sendEvent(_ event: [String: Any]) {
- NSLog("BluetoothStreamHandler: 接收到蓝牙事件: \(event)")
- DispatchQueue.main.async { [weak self] in
- self?.eventSink?(event)
- }
- }
-}
-
-// 媒体按钮事件流处理器
-@available(iOS 13.0, *)
-class MediaButtonStreamHandler: NSObject, FlutterStreamHandler, MediaButtonEventHandler {
- private let mediaButtonHelper: BluetoothMediaButtonHelper
- private var eventSink: FlutterEventSink?
-
- init(mediaButtonHelper: BluetoothMediaButtonHelper) {
- self.mediaButtonHelper = mediaButtonHelper
- super.init()
-
- // 设置事件处理器
- mediaButtonHelper.setEventHandler(self)
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- NSLog("MediaButtonStreamHandler: 开始监听媒体按钮事件")
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- NSLog("MediaButtonStreamHandler: 取消监听媒体按钮事件")
- eventSink = nil
- return nil
- }
-
- // 实现MediaButtonEventHandler协议
- func sendEvent(_ event: [String: Any]) {
- NSLog("MediaButtonStreamHandler: 收到按钮事件 \(event)")
- DispatchQueue.main.async { [weak self] in
- self?.eventSink?(event)
- }
- }
-}
-
-// ASR事件处理器协议
-@available(iOS 13.0, *)
-protocol AsrEventHandler: AnyObject {
- func sendEvent(_ event: [String: Any])
-}
-
-// ASR事件流处理器
-@available(iOS 13.0, *)
-class AsrStreamHandler: NSObject, FlutterStreamHandler, AsrEventHandler {
- private let azureAsrHelper: AzureAsrHelper
- private var eventSink: FlutterEventSink?
-
- init(azureAsrHelper: AzureAsrHelper) {
- self.azureAsrHelper = azureAsrHelper
- super.init()
-
- // 设置事件处理器
- azureAsrHelper.setEventHandler { [weak self] event in
- self?.sendEvent(event)
- }
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- NSLog("AsrStreamHandler: 开始监听ASR事件")
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- NSLog("AsrStreamHandler: 取消监听ASR事件")
- eventSink = nil
- return nil
- }
-
- // 实现AsrEventHandler协议
- func sendEvent(_ event: [String: Any]) {
- // NSLog("AsrStreamHandler: 收到事件 \(event)")
- DispatchQueue.main.async { [weak self] in
- self?.eventSink?(event)
- }
- }
-}
-
-// TTS事件处理器协议
-@available(iOS 13.0, *)
-protocol TtsEventHandler: AnyObject {
- func sendEvent(_ event: [String: Any])
-}
-
-// TTS事件流处理器
-@available(iOS 13.0, *)
-class TtsStreamHandler: NSObject, FlutterStreamHandler, TtsEventHandler {
- private let azureTtsHelper: AzureTtsHelper
- private var eventSink: FlutterEventSink?
-
- init(azureTtsHelper: AzureTtsHelper) {
- self.azureTtsHelper = azureTtsHelper
- super.init()
-
- // 设置事件处理器
- azureTtsHelper.setEventHandler { [weak self] event in
- self?.sendEvent(event)
- }
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- NSLog("TtsStreamHandler: 开始监听TTS事件")
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- NSLog("TtsStreamHandler: 取消监听TTS事件")
- eventSink = nil
- return nil
- }
-
- // 实现TtsEventHandler协议
- func sendEvent(_ event: [String: Any]) {
- NSLog("TtsStreamHandler: 收到事件 \(event)")
- DispatchQueue.main.async { [weak self] in
- self?.eventSink?(event)
- }
- }
-}
-
-// 火山AI事件流处理器
-@available(iOS 13.0, *)
-class VolcanoAIStreamHandler: NSObject, FlutterStreamHandler {
- private let volcanoAIService: VolcanoAIService
- private var eventSink: FlutterEventSink?
-
- init(volcanoAIService: VolcanoAIService) {
- self.volcanoAIService = volcanoAIService
- super.init()
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- eventSink = nil
- return nil
- }
-
- // 处理来自Flutter的流式请求
- func handleStreamRequest(messages: [[String: Any]], systemPrompt: String) {
- volcanoAIService.sendMessageStream(messages: messages, systemPrompt: systemPrompt, streamCallback: VolcanoAIService.StreamCallback(
- onToken: { [weak self] token in
- guard let self = self, let eventSink = self.eventSink else { return }
-
- let event: [String: Any] = [
- "type": "token",
- "token": token
- ]
-
- DispatchQueue.main.async {
- eventSink(event)
- }
- },
- onComplete: { [weak self] in
- guard let self = self, let eventSink = self.eventSink else { return }
-
- let event: [String: Any] = [
- "type": "complete"
- ]
-
- DispatchQueue.main.async {
- eventSink(event)
- }
- },
- onError: { [weak self] error in
- guard let self = self, let eventSink = self.eventSink else { return }
-
- let event: [String: Any] = [
- "type": "error",
- "error": error.localizedDescription
- ]
-
- DispatchQueue.main.async {
- eventSink(event)
- }
- }
- ))
- }
-}
-
-// 语音交互事件处理器协议
-@available(iOS 13.0, *)
-protocol VoiceInteractionEventHandler: AnyObject {
- func sendEvent(_ event: [String: Any])
-}
-
-// 语音交互流处理器
-@available(iOS 13.0, *)
-class VoiceInteractionStreamHandler: NSObject, FlutterStreamHandler, VoiceInteractionEventHandler {
- private let voiceInteractionService: VoiceInteractionService
- private var eventSink: FlutterEventSink?
-
- init(voiceInteractionService: VoiceInteractionService) {
- self.voiceInteractionService = voiceInteractionService
- super.init()
-
- // 设置事件处理器
- voiceInteractionService.setEventHandler(self)
- }
-
- func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
- NSLog("VoiceInteractionStreamHandler: 开始监听语音交互事件")
- self.eventSink = events
- return nil
- }
-
- func onCancel(withArguments arguments: Any?) -> FlutterError? {
- NSLog("VoiceInteractionStreamHandler: 取消监听语音交互事件")
- eventSink = nil
- return nil
- }
-
- // 实现VoiceInteractionEventHandler协议
- func sendEvent(_ event: [String: Any]) {
- NSLog("VoiceInteractionStreamHandler: 收到事件 \(event)")
- DispatchQueue.main.async { [weak self] in
- self?.eventSink?(event)
- }
- }
}
diff --git a/ios/Runner/AudioSessionManager.swift b/ios/Runner/AudioSessionManager.swift
deleted file mode 100644
index d41d2fbf5..000000000
--- a/ios/Runner/AudioSessionManager.swift
+++ /dev/null
@@ -1,589 +0,0 @@
-import Foundation
-import AVFoundation
-import UIKit
-
-/// 统一音频会话管理类
-/// 用于统一管理所有组件的音频会话配置
-@objc class AudioSessionManager: NSObject {
- // MARK: - 单例实现
-
- @objc static let shared = AudioSessionManager()
-
- // 音频会话
- private let audioSession = AVAudioSession.sharedInstance()
-
- // 音频会话状态
- private var isConfigured = false
- private var isActive = false
-
- // 当前配置类型
- private var currentCategory: AVAudioSession.Category?
- private var currentMode: AVAudioSession.Mode?
- private var currentOptions: AVAudioSession.CategoryOptions?
-
- // 中断监听器
- private var interruptionObserver: NSObjectProtocol?
-
- // 路由变化监听器
- private var routeChangeObserver: NSObjectProtocol?
-
- // 私有初始化方法,确保只能通过shared访问
- private override init() {
- super.init()
- NSLog("[AudioSessionManager] 音频会话管理器已初始化")
- setupNotifications()
-
- }
-
- deinit {
- removeNotifications()
- }
-
-
- // MARK: - 通知设置
-
- private func setupNotifications() {
- // 监听中断事件
- interruptionObserver = NotificationCenter.default.addObserver(
- forName: AVAudioSession.interruptionNotification,
- object: nil,
- queue: .main) { [weak self] notification in
- self?.handleInterruption(notification)
- }
-
- // 监听路由变化事件
- routeChangeObserver = NotificationCenter.default.addObserver(
- forName: AVAudioSession.routeChangeNotification,
- object: nil,
- queue: .main) { [weak self] notification in
- self?.handleRouteChange(notification)
- }
-
- // 监听应用进入后台/前台
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(handleAppDidEnterBackground),
- name: UIApplication.didEnterBackgroundNotification,
- object: nil)
-
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(handleAppWillEnterForeground),
- name: UIApplication.willEnterForegroundNotification,
- object: nil)
- }
-
- private func removeNotifications() {
- if let interruptionObserver = interruptionObserver {
- NotificationCenter.default.removeObserver(interruptionObserver)
- }
-
- if let routeChangeObserver = routeChangeObserver {
- NotificationCenter.default.removeObserver(routeChangeObserver)
- }
-
- NotificationCenter.default.removeObserver(self)
- }
-
- // MARK: - 通知处理
-
- private func handleInterruption(_ notification: Notification) {
- guard let userInfo = notification.userInfo,
- let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
- let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
- return
- }
-
- switch type {
- case .began:
- // 中断开始
- NSLog("[AudioSessionManager] 音频会话被中断")
- isActive = false
-
- case .ended:
- // 中断结束,尝试恢复
- if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
- let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
- if options.contains(.shouldResume) {
- NSLog("[AudioSessionManager] 尝试恢复被中断的音频会话")
- try? activateSession()
- }
- }
-
- @unknown default:
- break
- }
- }
-
- private func handleRouteChange(_ notification: Notification) {
- guard let userInfo = notification.userInfo,
- let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
- let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
- return
- }
-
- switch reason {
- case .newDeviceAvailable:
- // 新设备连接
- let currentRoute = audioSession.currentRoute
- for output in currentRoute.outputs where output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP {
- NSLog("[AudioSessionManager] 检测到蓝牙音频设备连接: \(output.portName)")
- // 蓝牙设备连接,可能需要更新配置
- if isActive && currentOptions?.contains(.allowBluetooth) == true {
- try? forceReconfigure()
- }
- break
- }
-
- case .oldDeviceUnavailable:
- // 设备断开连接
- if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
- for output in previousRoute.outputs where output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP {
- NSLog("[AudioSessionManager] 蓝牙音频设备断开连接: \(output.portName)")
- // 蓝牙设备断开,重新配置到内置扬声器
- if isActive {
- try? forceReconfigure()
- }
- break
- }
- }
-
- default:
- break
- }
- }
-
- @objc private func handleAppDidEnterBackground() {
- NSLog("[AudioSessionManager] 应用进入后台")
- // 应用进入后台时可能需要调整音频会话
-
- }
-
- @objc private func handleAppWillEnterForeground() {
- NSLog("[AudioSessionManager] 应用即将进入前台")
- // 应用返回前台,检查并恢复音频会话
- if isConfigured && !isActive {
- try? activateSession()
- }
- }
-
- // MARK: - 音频会话配置
-
- /// 统一配置方法 - 所有音频配置都通过此方法进行
- @objc func configureSession(category: AVAudioSession.Category,
- mode: AVAudioSession.Mode,
- options: AVAudioSession.CategoryOptions,
- force: Bool = false) throws {
-
- NSLog("[AudioSessionManager] 配置音频会话: 类别=\(category), 模式=\(mode), 选项=\(options), 强制=\(force)")
-
- // 如果已配置且不强制重新配置,则直接返回
- if isConfigured && !force &&
- currentCategory == category &&
- currentMode == mode &&
- currentOptions == options {
- NSLog("[AudioSessionManager] 音频会话已配置,跳过配置")
- return
- }
-
- do {
- // NSLog("[AudioSessionManager] 配置音频会话: 类别=\(category), 模式=\(mode)")
-
- // 暂时停用当前会话(如果正在活动)
- if isActive {
- try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
- }
-
- // 设置新的分类、模式和选项
- try audioSession.setCategory(category, mode: mode, options: options)
-
- // 设置采样率和缓冲区大小(对于语音识别优化)
- // if mode == .spokenAudio {
- // try audioSession.setPreferredSampleRate(16000)
- // try audioSession.setPreferredIOBufferDuration(0.01) // 10ms
- // }
-
- // 重新激活会话
- try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
- isActive = true
- // 切换到扬声器
- // 记录当前配置
- currentCategory = category
- currentMode = mode
- currentOptions = options
- isConfigured = true
-
- // 检查并设置首选的输入设备(如果需要)
- if category == .playAndRecord || category == .record {
- try configurePreferredInput()
- }
- return
- } catch {
- NSLog("[AudioSessionManager] 配置音频会话失败: \(error.localizedDescription)")
- throw error
- }
- }
-
- /// 配置首选输入设备
- private func configurePreferredInput() throws {
- // 获取可用输入设备
- guard let availableInputs = audioSession.availableInputs else {
- NSLog("[AudioSessionManager] 无法获取可用输入设备")
- return
- }
-
- // 首选顺序:有线耳机 > 蓝牙耳机 > 内置麦克风
- var preferredInput: AVAudioSessionPortDescription? = nil
-
- // 先尝试找到有线耳机
- if let wiredHeadset = availableInputs.first(where: { $0.portType == .headsetMic }) {
- preferredInput = wiredHeadset
- NSLog("[AudioSessionManager] 设置首选输入设备: 有线耳机")
- }
- // 如果没有有线耳机,尝试使用蓝牙
- else if let bluetooth = availableInputs.first(where: { $0.portType == .bluetoothHFP }) {
- preferredInput = bluetooth
- NSLog("[AudioSessionManager] 设置首选输入设备: 蓝牙耳机")
- }
- // 如果没有外部设备,使用内置麦克风
- else if let builtIn = availableInputs.first(where: { $0.portType == .builtInMic }) {
- preferredInput = builtIn
- NSLog("[AudioSessionManager] 设置首选输入设备: 内置麦克风")
- }
-
- // 设置首选输入设备
- if let input = preferredInput {
- try audioSession.setPreferredInput(input)
- }
- }
-
-
-
- /// 激活音频会话(如果已配置)
- @objc func activateSession() throws {
- if !isConfigured {
- NSLog("[AudioSessionManager] 警告: 尝试激活未配置的音频会话,应用默认配置")
- // 未配置,应用默认配置
- // try configureSession(
- // category: .playAndRecord,
- // mode: .spokenAudio,
- // options: [.allowBluetooth, .allowBluetoothA2DP, .duckOthers, .defaultToSpeaker],
- // force: true
- // )
-
- try configureSession(
- category: .playAndRecord, // 允许同时录音和播放
- mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
- options: [
- .allowBluetooth, // 允许蓝牙设备
- .defaultToSpeaker, // 默认使用扬声器
- .mixWithOthers // 允许与其他应用混音
- ],
- force: false // 是否强制重新配置
- )
-
-
- } else if !isActive {
- NSLog("[AudioSessionManager] 激活已配置的音频会话")
- try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
- isActive = true
- }
- }
-
- /// 停用音频会话(保留配置)
- @objc func deactivateSession() throws {
- if isActive {
- NSLog("[AudioSessionManager] 停用音频会话")
- try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
- isActive = false
- }
- }
-
- /// 重新配置当前会话
- @objc func forceReconfigure() throws {
- NSLog("[AudioSessionManager] 强制重新配置音频会话")
- // try configureSession(
- // category: currentCategory ?? .playAndRecord,
- // mode: currentMode ?? .default,
- // options: currentOptions ?? [.allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker],
- // force: true
- // )
-
- try configureSession(
- category: .playAndRecord, // 允许同时录音和播放
- mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
- options: [
- .allowBluetooth, // 允许蓝牙设备
- .defaultToSpeaker, // 默认使用扬声器
- .mixWithOthers // 允许与其他应用混音
- ],
- force: false // 是否强制重新配置
- )
- try audioSession.overrideOutputAudioPort(.speaker)
-
-
- }
-
- // MARK: - 状态检查
-
- /// 检查麦克风权限
- @objc func checkMicrophonePermission(completion: @escaping (Bool) -> Void) {
- switch AVAudioSession.sharedInstance().recordPermission {
- case .granted:
- completion(true)
- case .denied:
- NSLog("[AudioSessionManager] 麦克风权限被拒绝")
- completion(false)
- case .undetermined:
- NSLog("[AudioSessionManager] 请求麦克风权限")
- AVAudioSession.sharedInstance().requestRecordPermission { granted in
- DispatchQueue.main.async {
- completion(granted)
- }
- }
- @unknown default:
- completion(false)
- }
- }
-
- /// 获取当前音频会话的配置信息
- @objc func getCurrentConfiguration() -> [String: Any] {
- return [
- "isConfigured": isConfigured,
- "isActive": isActive,
- "category": String(describing: currentCategory),
- "mode": String(describing: currentMode),
- "bluetoothEnabled": currentOptions?.contains(.allowBluetooth) == true,
- "currentInputs": audioSession.currentRoute.inputs.map { [$0.portType.rawValue: $0.portName] },
- "currentOutputs": audioSession.currentRoute.outputs.map { [$0.portType.rawValue: $0.portName] }
- ]
- }
-
- /// 检查当前是否有蓝牙耳机连接
- @objc func hasBluetoothHeadsetConnected() -> Bool {
- let outputs = audioSession.currentRoute.outputs
- return outputs.contains { $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE }
- }
-
- /// 检查蓝牙是否启用
- @objc func isBluetoothEnabled() -> Bool {
- // 在iOS中无法直接检查蓝牙是否启用,我们可以通过检查音频会话配置来推断
-
- // 检查音频会话是否配置了允许蓝牙
- if let options = currentOptions {
- return options.contains(.allowBluetooth) || options.contains(.allowBluetoothA2DP)
- }
-
- // 如果尚未配置音频会话,尝试检查当前路由中是否有蓝牙设备
- let outputs = audioSession.currentRoute.outputs
- let hasBluetooth = outputs.contains {
- $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP || $0.portType == .bluetoothLE
- }
-
- return hasBluetooth
- }
-
- /// 获取当前音频路由
- @objc func getCurrentAudioRoute() -> String {
- let outputs = audioSession.currentRoute.outputs
- if let output = outputs.first {
- return "\(output.portType.rawValue) (\(output.portName))"
- }
- return "unknown"
- }
-
- /// 获取当前输入设备
- @objc func getCurrentInputDevice() -> String {
- let inputs = audioSession.currentRoute.inputs
- if let input = inputs.first {
- return "\(input.portType.rawValue) (\(input.portName))"
- }
- return "unknown"
- }
-
- // MARK: - 路由管理和监听
-
- /// 获取当前路由信息
- @objc func getCurrentRoute() -> AVAudioSessionRouteDescription {
- return audioSession.currentRoute
- }
-
- /// 添加路由变更监听器
- /// - Parameters:
- /// - observer: 监听对象
- /// - selector: 监听方法选择器
- @objc func addRouteChangeListener(_ observer: Any, selector: Selector) {
- NotificationCenter.default.addObserver(
- observer,
- selector: selector,
- name: AVAudioSession.routeChangeNotification,
- object: nil
- )
- }
-
- /// 移除路由变更监听器
- /// - Parameter observer: 要移除的监听对象
- @objc func removeRouteChangeListener(_ observer: Any) {
- NotificationCenter.default.removeObserver(
- observer,
- name: AVAudioSession.routeChangeNotification,
- object: nil
- )
- }
- /// 获取设备类型描述
- private func getDeviceType(portType: AVAudioSession.Port) -> String {
- switch portType {
- case .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
- return "bluetooth"
- case .headphones, .headsetMic:
- return "wired"
- case .airPlay:
- return "airplay"
- default:
- return "unknown"
- }
- }
-
-
-
-
- // /// 为Azure语音识别专门配置的音频会话
- // /// 此函数优化Azure语音识别的音频处理配置,提供更强的回音消除能力
- // /// - Parameters:
- // /// - force: 是否强制重新配置
- // /// - configureAdditionalSettings: 配置完成后进行额外的音频设置
- // /// - Returns: 配置是否成功
- // @objc func configureForAzureSpeechRecognition(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool {
- // NSLog("[AudioSessionManager] 配置Azure ASR语音识别的音频会话")
- // do {
- // // 配置音频会话以优化语音识别
- // try configureSession(
- // category: .playAndRecord, // 允许同时录音和播放
- // mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
- // options: [
- // .allowBluetooth, // 允许蓝牙设备
- // .defaultToSpeaker, // 默认使用扬声器
- // .mixWithOthers // 允许与其他应用混音
- // ],
- // force: force // 是否强制重新配置
- // )
-
- // // 额外的优化配置
- // if configureAdditionalSettings {
- // // 获取当前是否使用耳机
- // let currentRoute = audioSession.currentRoute
- // let hasHeadphones = currentRoute.outputs.contains {
- // $0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
- // }
-
- // // 设置采样率为16kHz(Azure Speech API推荐)
- // try audioSession.setPreferredSampleRate(16000.0)
-
- // // 设置较小的缓冲区大小以减少延迟
- // try audioSession.setPreferredIOBufferDuration(0.01)
-
- // // 根据是否有耳机连接调整输入增益
- // if !hasHeadphones {
- // // 无耳机时降低输入增益以减少回音
- // try audioSession.setInputGain(0.8)
- // NSLog("[AudioSessionManager] 启用扬声器回音消除优化")
- // } else {
- // // 使用耳机时可以使用较高增益
- // try audioSession.setInputGain(1.0)
- // NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式")
- // }
- // }
-
- // NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话")
- // return true
- // } catch {
- // NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)")
- // return false
- // }
- // }
-
-
-@objc func configureForVoiceInteraction(force: Bool = false, configureAdditionalSettings: Bool = true) -> Bool {
- NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话")
- do {
- // 配置音频会话以优化语音识别
- try configureSession(
- category: .playAndRecord, // 允许同时录音和播放
- mode: .voiceChat, // 使用voiceChat模式获得最佳回音消除效果
- options: [
- .allowBluetooth, // 允许蓝牙设备
- .defaultToSpeaker, // 默认使用扬声器
- .mixWithOthers // 允许与其他应用混音
- ],
- force: force // 是否强制重新配置
- )
-
- try audioSession.overrideOutputAudioPort(.speaker)
-
- // 额外的优化配置
- if configureAdditionalSettings {
- // 获取当前是否使用耳机
- let currentRoute = audioSession.currentRoute
- let hasHeadphones = currentRoute.outputs.contains {
- $0.portType == .headphones || $0.portType == .bluetoothA2DP || $0.portType == .bluetoothHFP
- }
-
- // 设置采样率为16kHz(Azure Speech API推荐)
- try audioSession.setPreferredSampleRate(16000.0)
-
- // 设置较小的缓冲区大小以减少延迟
- try audioSession.setPreferredIOBufferDuration(0.01)
-
- // 根据是否有耳机连接调整输入增益
- if !hasHeadphones {
- // 无耳机时降低输入增益以减少回音
- try audioSession.setInputGain(0.8)
- NSLog("[AudioSessionManager] 启用扬声器回音消除优化")
- } else {
- // 使用耳机时可以使用较高增益
- try audioSession.setInputGain(1.0)
- NSLog("[AudioSessionManager] 检测到耳机连接,应用耳机模式")
- }
- }
-
- NSLog("[AudioSessionManager] 已成功配置Azure语音识别的音频会话")
- return true
- } catch {
- NSLog("[AudioSessionManager] 配置Azure语音识别的音频会话失败: \(error.localizedDescription)")
- return false
- }
- }
-
-
- /// 配置用于语音交互(同时支持ASR和TTS)的音频会话
- /// 此函数综合优化语音识别和语音合成,适用于需要双向交互的场景
- /// - Returns: 配置是否成功
- // @objc func configureForVoiceInteraction(force: Bool = false) -> Bool {
- // do {
- // // 配置音频会话以支持语音交互
- // // 使用playAndRecord类别允许同时录音和播放
- // // 使用spokenAudio模式优化语音交互
- // try configureSession(
- // category: .playAndRecord,
- // mode: .voiceChat,
- // options: [
- // // .allowBluetoothA2DP, // 允许通过蓝牙A2DP连接进行高质量音频输出
- // .allowBluetooth, // 允许通过蓝牙SCO连接进行输入和输出
- // .defaultToSpeaker, // 默认使用扬声器输出
- // .mixWithOthers
- // ],
- // force: force // 强制重新配置,确保设置生效
- // )
-
- // // 设置首选输入设备
- // try configurePreferredInput()
-
- // NSLog("[AudioSessionManager] 已配置语音交互(ASR+TTS)的音频会话")
- // return true
- // } catch {
- // NSLog("[AudioSessionManager] 配置语音交互(ASR+TTS)的音频会话失败: \(error.localizedDescription)")
- // return false
- // }
- // }
-
-}
\ No newline at end of file
diff --git a/ios/Runner/AzureAsrHelper.swift b/ios/Runner/AzureAsrHelper.swift
deleted file mode 100644
index 5a2b73189..000000000
--- a/ios/Runner/AzureAsrHelper.swift
+++ /dev/null
@@ -1,813 +0,0 @@
-import Foundation
-import MicrosoftCognitiveServicesSpeech
-import AVFoundation
-import AudioToolbox
-
-/// Azure ASR工具类,负责实现语音识别服务接口
-@available(iOS 13.0, *)
-class AzureAsrHelper: NSObject {
- // MARK: - 属性
-
- /// 是否使用自定义音频处理器 - 设置为false时将使用系统默认麦克风输入
- private let useCustomAudioProcessor = true
-
- /// 事件处理回调
- private var eventHandler: (([String: Any]) -> Void)?
-
- /// 语音配置信息
- private var speechSubscriptionKey: String = ""
- private var serviceRegion: String = ""
-
- /// 语音识别相关
- private var speechConfig: SPXSpeechConfiguration?
- private var recognizer: SPXSpeechRecognizer?
- private var audioConfig: SPXAudioConfiguration?
-
- /// 添加自定义音频处理相关
- private var pushStream: SPXPushAudioInputStream?
- private var audioProcessor: CustomAudioProcessor?
- private var isProcessingAudio = false
- private var audioProcessingTimer: Timer?
-
- /// 状态标志
- private var isInitialized = false
- private var _isContinuousRecognitionActive = false
-
- /// 当前语言和支持的语言
- private var currentLanguage = "zh-CN"
- private var supportedLanguages: [String] = ["zh-CN", "en-US"]
- private var isAutoDetectLanguage = false
-
- /// 音频会话管理器
- private let audioSessionManager = AudioSessionManager.shared
-
- // MARK: - 初始化
-
- override init() {
- super.init()
- }
-
- deinit {
- dispose()
- }
-
-
- // MARK: - ASR Service 接口实现
-
- /// 初始化语音识别服务
- /// - Parameters:
- /// - speechSubscriptionKey: Azure 语音服务订阅密钥
- /// - serviceRegion: Azure 服务区域 (如 eastasia)
- /// - supportedLanguages: 支持的语言代码数组 (可选)
- /// - Returns: 初始化是否成功
- func initialize(speechSubscriptionKey: String, serviceRegion: String, supportedLanguages: [String]? = nil) -> Bool {
- print("[AzureAsrHelper] 初始化 Azure 语音服务")
-
- // 检查配置是否为空
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- // 检查语言参数
- if let languages = supportedLanguages, languages.isEmpty {
- print("[AzureAsrHelper] 警告: 传入的支持语言列表为空,将使用默认语言")
- }
-
- // 释放之前的资源
- dispose()
-
- // 记录配置信息
- self.speechSubscriptionKey = speechSubscriptionKey
- self.serviceRegion = serviceRegion
-
- // 设置语言
- if let languages = supportedLanguages, !languages.isEmpty {
- self.supportedLanguages = languages
- }
-
- // 根据支持的语言数量决定是否启用自动语言检测
- isAutoDetectLanguage = self.supportedLanguages.count >= 2
-
- // 如果只有一种语言,设置为当前语言
- if !isAutoDetectLanguage && !self.supportedLanguages.isEmpty {
- currentLanguage = self.supportedLanguages[0]
- }
-
- // 创建识别器和设置回调
- if !createRecognizerAndSetupCallbacks() {
- return false
- }
-
- print("[AzureAsrHelper] Azure 语音服务初始化成功")
- isInitialized = true
- return true
- }
-
- /// 创建识别器并设置回调
- private func createRecognizerAndSetupCallbacks() -> Bool {
- // 释放之前的 recognizer
- recognizer = nil
- audioConfig = nil
-
- do {
- // 创建语音配置
- speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion)
-
- // 设置音频输入参数
- try setupAudioSession()
-
- // 根据设置决定是否使用自定义音频处理器
- if useCustomAudioProcessor {
- // 创建自定义音频流和处理器,替代默认的麦克风输入
- pushStream = try SPXPushAudioInputStream()
- audioConfig = try SPXAudioConfiguration(streamInput: pushStream!)
-
- // 初始化自定义音频处理器
- audioProcessor = CustomAudioProcessor()
- } else {
- // 使用默认麦克风输入
- audioConfig = try SPXAudioConfiguration()
- }
-
- // 设置语言配置
- if isAutoDetectLanguage {
- // 设置自动语言检测
- speechConfig?.setPropertyTo("Continuous", by: SPXPropertyId.speechServiceConnectionLanguageIdMode)
-
- // 创建自动语言检测配置
- let autoDetectSourceLanguageConfig = try SPXAutoDetectSourceLanguageConfiguration(supportedLanguages)
-
- // 创建识别器
- recognizer = try SPXSpeechRecognizer(
- speechConfiguration: speechConfig!,
- autoDetectSourceLanguageConfiguration: autoDetectSourceLanguageConfig,
- audioConfiguration: audioConfig!
- )
- } else {
- // 设置指定的识别语言
- speechConfig?.speechRecognitionLanguage = currentLanguage
-
- // 创建识别器
- recognizer = try SPXSpeechRecognizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!)
- }
-
- // 设置所有回调
- setupAllCallbacks()
-
- return true
- } catch {
- print("[AzureAsrHelper] 错误: 创建识别器失败: \(error.localizedDescription)")
- eventHandler?(["type": "error", "message": "创建识别器失败: \(error.localizedDescription)"])
- return false
- }
- }
-
- /// 设置音频会话
- private func setupAudioSession() throws {
- print("[AzureAsrHelper] 开始配置音频会话...")
-
- // 使用AudioSessionManager配置音频会话,使用专门为Azure ASR优化的配置
- let success = audioSessionManager.configureForVoiceInteraction()
- if !success {
- print("[AzureAsrHelper] 警告: 通过AudioSessionManager配置音频会话失败")
- }
- }
-
-
- /// 设置所有回调
- private func setupAllCallbacks() {
- guard let recognizer = recognizer else { return }
-
- // 最终识别结果
- recognizer.addRecognizedEventHandler { [weak self] _, event in
- guard let self = self else { return }
-
- if event.result.reason == SPXResultReason.recognizedSpeech {
- let detectedLanguage = self.getDetectedLanguage(from: event.result)
- print("[AzureAsrHelper] 识别结果: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
- self.eventHandler?(["type": "result",
- "text": event.result.text ?? "",
- "detectedLanguage": detectedLanguage
- ])
- }
- }
-
- // 识别中事件
- recognizer.addRecognizingEventHandler { [weak self] _, event in
- guard let self = self else { return }
-
- if event.result.reason == SPXResultReason.recognizingSpeech {
- let detectedLanguage = self.getDetectedLanguage(from: event.result)
- // print("[AzureAsrHelper] 识别中: \(event.result.text ?? ""), 语言: \(detectedLanguage)")
- self.eventHandler?(["type": "recognizing",
- "text": event.result.text ?? "",
- "detectedLanguage": detectedLanguage
- ])
- }
- }
-
- // 会话事件
- recognizer.addSessionStartedEventHandler { [weak self] _, _ in
- guard let self = self else { return }
-
- print("[AzureAsrHelper] 识别会话已开始")
- self._isContinuousRecognitionActive = true
- self.eventHandler?(["type": "sessionStarted"])
- }
-
- recognizer.addSessionStoppedEventHandler { [weak self] _, _ in
- guard let self = self else { return }
-
- print("[AzureAsrHelper] 识别会话已结束")
- self._isContinuousRecognitionActive = false
- self.eventHandler?(["type": "sessionStopped"])
- }
-
- // 取消事件
- recognizer.addCanceledEventHandler { [weak self] _, event in
- guard let self = self else { return }
-
- let reason = event.reason.rawValue
- let errorDetails = event.errorDetails ?? "未知错误"
-
- print("[AzureAsrHelper] 识别取消: \(errorDetails)")
-
- self.eventHandler?(["type": "canceled", "reason": reason, "errorDetails": errorDetails])
-
- self._isContinuousRecognitionActive = false
- }
- }
-
- /// 执行一次性语音识别
- /// - Returns: 是否成功启动识别
- func recognizeOnce() -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureAsrHelper] 错误: 语音服务未初始化")
- eventHandler?(["type": "error", "message": "语音服务未初始化"])
- return false
- }
-
- // 检查参数有效性
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- // 如果正在连续识别,先停止
- if _isContinuousRecognitionActive {
- stopContinuousRecognition()
- }
-
- // 确保识别器已创建
- if recognizer == nil && !createRecognizerAndSetupCallbacks() {
- return false
- }
-
- do {
- // 启动音频处理
- startAudioProcessing()
-
- // 通知会话开始
- eventHandler?(["type": "sessionStarted"])
-
- // 执行识别
- try recognizer?.recognizeOnceAsync { [weak self] result in
- guard let self = self else { return }
-
- // 停止音频处理
- self.stopAudioProcessing()
-
- if result.reason == SPXResultReason.recognizedSpeech {
- let detectedLanguage = self.getDetectedLanguage(from: result)
- self.eventHandler?(["type": "result",
- "text": result.text ?? "",
- "detectedLanguage": detectedLanguage
- ])
- } else if result.reason == SPXResultReason.noMatch {
- print("[AzureAsrHelper] 无匹配结果")
- self.eventHandler?(["type": "noMatch"])
- } else if result.reason == SPXResultReason.canceled {
- do {
- let details = try SPXCancellationDetails(fromCanceledRecognitionResult: result)
- let errorDetails = details.errorDetails ?? "未知错误"
- self.eventHandler?(["type": "error", "message": "识别取消: \(errorDetails)"])
- } catch {
- print("[AzureAsrHelper] 错误: 获取取消详情失败: \(error.localizedDescription)")
- self.eventHandler?(["type": "error", "message": "识别取消,无法获取详细原因"])
- }
- }
- }
-
- return true
- } catch {
- print("[AzureAsrHelper] 错误: 识别异常: \(error.localizedDescription)")
- eventHandler?(["type": "error", "message": "识别异常: \(error.localizedDescription)"])
- stopAudioProcessing()
- return false
- }
- }
-
- /// 开始连续语音识别
- /// - Returns: 是否成功启动识别
- func startContinuousRecognition() -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureAsrHelper] 错误: 语音服务未初始化")
- eventHandler?(["type": "error", "message": "语音服务未初始化"])
- return false
- }
-
- // 检查参数有效性
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureAsrHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- // 检查是否已经在识别
- if _isContinuousRecognitionActive {
- print("[AzureAsrHelper] 已经在进行连续识别中,忽略请求")
- return true
- }
-
- // 确保识别器已创建
- if recognizer == nil {
- print("[AzureAsrHelper] 尝试重新创建识别器...")
- if !createRecognizerAndSetupCallbacks() {
- eventHandler?(["type": "error", "message": "无法创建语音识别器"])
- return false
- }
- }
-
- // 启动音频处理
- startAudioProcessing()
-
- // 尝试启动连续识别
- do {
- // 启动连续识别
- try recognizer?.startContinuousRecognition()
- _isContinuousRecognitionActive = true
-
- print("[AzureAsrHelper] 连续识别已启动")
- return true
- } catch {
- _isContinuousRecognitionActive = false
- print("[AzureAsrHelper] 错误: 开始连续识别失败: \(error.localizedDescription)")
-
- // 停止音频处理
- stopAudioProcessing()
-
- // 发送错误通知
- eventHandler?(["type": "error", "message": "开始连续识别失败: \(error.localizedDescription)"])
-
- return false
- }
- }
-
-
- /// 停止连续语音识别
- /// - Returns: 是否成功停止识别
- func stopContinuousRecognition() -> Bool {
- // 停止音频处理
- stopAudioProcessing()
-
- // 检查是否初始化
- if !isInitialized {
- print("[AzureAsrHelper] 错误: 语音服务未初始化")
- eventHandler?(["type": "error", "message": "语音服务未初始化"])
- return false
- }
-
- if !_isContinuousRecognitionActive {
- print("[AzureAsrHelper] 未进行连续识别,忽略停止请求")
- return true
- }
-
- print("[AzureAsrHelper] 停止连续语音识别")
-
- // 防止空指针异常
- guard let recognizer = recognizer else {
- print("[AzureAsrHelper] 警告: 识别器为空,但状态显示活跃")
- _isContinuousRecognitionActive = false
- eventHandler?(["type": "sessionStopped"])
- return true
- }
-
- // 先标记为非活跃状态,防止重复调用
- _isContinuousRecognitionActive = false
-
- // 异步执行停止操作,避免阻塞主线程
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self else { return }
-
- do {
- // 在后台线程执行停止操作
- try recognizer.stopContinuousRecognition()
-
- // 主线程回调通知结果
- DispatchQueue.main.async {
- print("[AzureAsrHelper] 连续识别已停止")
- self.eventHandler?(["type": "sessionStopped"])
- }
- } catch {
- // 主线程回调通知错误
- DispatchQueue.main.async {
- print("[AzureAsrHelper] 错误: 停止连续识别失败: \(error.localizedDescription)")
- // 停止失败,恢复状态
- self._isContinuousRecognitionActive = true
- self.eventHandler?(["type": "error", "message": "停止识别失败: \(error.localizedDescription)"])
- }
- }
- }
-
- // 立即返回,不阻塞调用方
- return true
- }
-
- /// 检查连续识别是否活跃
- /// - Returns: 连续识别是否处于活跃状态
- func isContinuousRecognitionActive() -> Bool {
- return _isContinuousRecognitionActive
- }
-
- /// 释放资源
- func dispose() {
- // 停止音频处理
- stopAudioProcessing()
-
- // 尝试停止所有识别操作
- if _isContinuousRecognitionActive {
- do {
- if let recognizer = recognizer {
- try recognizer.stopContinuousRecognition()
- print("[AzureAsrHelper] 连续识别已停止")
- }
- } catch {
- print("[AzureAsrHelper] 警告: 停止连续识别失败: \(error.localizedDescription)")
- }
- _isContinuousRecognitionActive = false
- }
-
- // 释放识别器
- recognizer = nil
- audioConfig = nil
- speechConfig = nil
- pushStream = nil
- audioProcessor = nil
-
- // 重置状态
- isInitialized = false
-
- print("[AzureAsrHelper] 资源已释放")
- }
-
- /// 从结果中获取检测到的语言
- private func getDetectedLanguage(from result: SPXSpeechRecognitionResult) -> String {
- if isAutoDetectLanguage {
- do {
- let langResult = try SPXAutoDetectSourceLanguageResult(result)
- return langResult.language ?? currentLanguage
- } catch {
- print("[AzureAsrHelper] 错误: 获取检测到的语言失败: \(error.localizedDescription)")
- return currentLanguage
- }
- } else {
- return currentLanguage
- }
- }
-
- /// 设置事件处理器
- @objc func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) {
- self.eventHandler = handler
- }
-
- // MARK: - 音频处理
-
- /// 开始音频处理
- private func startAudioProcessing() {
- // 如果未使用自定义音频处理器,跳过
- if !useCustomAudioProcessor {
- return
- }
-
- guard !isProcessingAudio, let audioProcessor = audioProcessor else { return }
-
- isProcessingAudio = true
-
- // 启动音频处理器
- if !audioProcessor.startRecord() {
- print("[AzureAsrHelper] 错误: 启动音频处理器失败")
- eventHandler?(["type": "error", "message": "启动音频处理器失败"])
- return
- }
-
- // 启动音频处理定时器
- audioProcessingTimer = Timer.scheduledTimer(withTimeInterval: 0.08, repeats: true) { [weak self] _ in
- guard let self = self, self.isProcessingAudio, let processor = self.audioProcessor, let stream = self.pushStream else {
- return
- }
-
- // 读取处理后的音频数据
- var bytes = [UInt8](repeating: 0, count: 2560)
- let bytesRead = processor.read(bytes: &bytes)
-
- if bytesRead > 0 {
- // 推送数据到Azure语音服务
- let data = Data(bytes: bytes, count: bytesRead)
- stream.write(data)
-
- // 通知音频数据可用(可选)
- // self.eventHandler?(["type": "audioData", "data": bytes])
- }
- }
-
- print("[AzureAsrHelper] 音频处理已启动")
- }
-
- /// 停止音频处理
- private func stopAudioProcessing() {
- // 如果未使用自定义音频处理器,跳过
- if !useCustomAudioProcessor {
- return
- }
-
- // 停止定时器
- audioProcessingTimer?.invalidate()
- audioProcessingTimer = nil
-
- // 停止音频处理器
- audioProcessor?.stopRecord()
-
- isProcessingAudio = false
- print("[AzureAsrHelper] 音频处理已停止")
- }
-}
-
-// MARK: - 自定义音频处理器
-
-@available(iOS 13.0, *)
-class CustomAudioProcessor: NSObject {
- // 音频单元
- private var ioUnit: AudioUnit?
-
- // 音频格式
- private var audioFormat: AudioStreamBasicDescription
-
- // 音频缓冲
- private var audioBufferList: AudioBufferList
- private var audioList: [Float] = []
- private let audioListQueue = DispatchQueue(label: "audioListQueue")
-
- // 回音消除状态
- private var isEchoCancellationEnabled = true
-
- override init() {
- // 设置音频格式 - 16kHz, 16位, 单声道
- audioFormat = AudioStreamBasicDescription(
- mSampleRate: 16000.0,
- mFormatID: kAudioFormatLinearPCM,
- mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked,
- mBytesPerPacket: 2,
- mFramesPerPacket: 1,
- mBytesPerFrame: 2,
- mChannelsPerFrame: 1,
- mBitsPerChannel: 16,
- mReserved: 0
- )
-
- // 初始化音频缓冲
- audioBufferList = AudioBufferList(
- mNumberBuffers: 1,
- mBuffers: AudioBuffer(
- mNumberChannels: 1,
- mDataByteSize: 4096,
- mData: malloc(4096)
- )
- )
-
- super.init()
- }
-
- deinit {
- stopRecord()
- free(audioBufferList.mBuffers.mData)
- }
-
- /// 启动音频处理
- /// - Returns: 是否成功启动
- func startRecord() -> Bool {
- print("[CustomAudioProcessor] 配置音频单元")
-
- // 创建音频组件描述 - 使用VoiceProcessingIO类型获取回音消除
- var ioUnitDescription = AudioComponentDescription(
- componentType: kAudioUnitType_Output,
- componentSubType: kAudioUnitSubType_VoiceProcessingIO,
- componentManufacturer: kAudioUnitManufacturer_Apple,
- componentFlags: 0,
- componentFlagsMask: 0
- )
-
- // 查找音频组件
- guard let ioUnitRef = AudioComponentFindNext(nil, &ioUnitDescription) else {
- print("[CustomAudioProcessor] 错误: 未找到音频组件")
- return false
- }
-
- // 创建音频单元实例
- if checkError(AudioComponentInstanceNew(ioUnitRef, &ioUnit), "创建音频单元") {
- ioUnit = nil
- return false
- }
-
- // 启用输入端口
- var enableInput: UInt32 = 1
- let kInputBus: AudioUnitElement = 1
- let kOutputBus: AudioUnitElement = 0
- if checkError(AudioUnitSetProperty(ioUnit!, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Input, kInputBus, &enableInput,
- UInt32(MemoryLayout.size)), "启用输入端口") {
- return false
- }
-
- // 禁用输出端口 (我们只需要输入)
- var enableOutput: UInt32 = 0
- if checkError(AudioUnitSetProperty(ioUnit!, kAudioOutputUnitProperty_EnableIO,
- kAudioUnitScope_Output, kOutputBus,
- &enableOutput, UInt32(MemoryLayout.size)), "禁用输出端口") {
- return false
- }
-
- // 设置缓冲区分配标志
- var flag: UInt32 = 0
- if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_ShouldAllocateBuffer,
- kAudioUnitScope_Output, kInputBus, &flag, UInt32(MemoryLayout.size)), "设置缓冲区分配标志") {
- return false
- }
-
- // 设置音频格式
- let size = UInt32(MemoryLayout.size)
- if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Output, kInputBus, &audioFormat, size), "设置输入总线输出范围的流格式") {
- return false
- }
-
- if checkError(AudioUnitSetProperty(ioUnit!, kAudioUnitProperty_StreamFormat,
- kAudioUnitScope_Input, kOutputBus, &audioFormat, size), "设置输出总线输入范围的流格式") {
- return false
- }
-
- // 启用回音消除 - 注意: kAUVoiceIOProperty_BypassVoiceProcessing值为1时表示绕过处理,值为0表示启用处理
- if isEchoCancellationEnabled {
- var echoCancellation: UInt32 = 0 // 0表示不绕过,即启用回音消除
- AudioUnitSetProperty(ioUnit!, kAUVoiceIOProperty_BypassVoiceProcessing,
- kAudioUnitScope_Global, 0, &echoCancellation, UInt32(MemoryLayout.size))
- }
-
- // 设置输入回调 - 当有新音频数据时调用
- var inputCallback = AURenderCallbackStruct(
- inputProc: CustomAudioProcessor.onAudioDataAvailable,
- inputProcRefCon: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
- )
-
- if checkError(AudioUnitSetProperty(ioUnit!,
- kAudioOutputUnitProperty_SetInputCallback,
- kAudioUnitScope_Global, kInputBus,
- &inputCallback, UInt32(MemoryLayout.size)), "设置输入回调") {
- return false
- }
-
- // 初始化音频单元
- var hasError = checkError(AudioUnitInitialize(ioUnit!), "初始化音频单元")
- while hasError {
- Thread.sleep(forTimeInterval: 0.1)
- hasError = checkError(AudioUnitInitialize(ioUnit!), "初始化音频单元")
- }
-
- // 启动音频单元
- hasError = checkError(AudioOutputUnitStart(ioUnit!), "启动音频单元")
-
- print("[CustomAudioProcessor] 音频处理器已启动,回音消除\(isEchoCancellationEnabled ? "已启用" : "已禁用")")
- return !hasError
- }
-
- /// 停止音频处理
- func stopRecord() {
- print("[CustomAudioProcessor] 停止音频处理器")
-
- if let ioUnit = ioUnit {
- // 停止音频单元
- _ = checkError(AudioOutputUnitStop(ioUnit), "停止音频单元")
-
- // 关闭音频单元
- _ = checkError(AudioUnitUninitialize(ioUnit), "反初始化音频单元")
- _ = checkError(AudioComponentInstanceDispose(ioUnit), "释放音频单元")
-
- self.ioUnit = nil
- }
-
- // 清空音频数据缓冲
- audioListQueue.sync {
- audioList.removeAll()
- }
- }
-
- /// 音频数据回调 - 当有新的音频数据可用时调用
- private static let onAudioDataAvailable: AURenderCallback = { inRefCon, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData in
- // 获取实例
- let processor = Unmanaged.fromOpaque(inRefCon).takeUnretainedValue()
-
- // 计算预期数据大小
- let expectedDataByteSize = inNumberFrames * processor.audioFormat.mBytesPerFrame
-
- // 确保缓冲区足够大
- if processor.audioBufferList.mBuffers.mDataByteSize < expectedDataByteSize {
- processor.audioBufferList.mBuffers.mData = realloc(processor.audioBufferList.mBuffers.mData, Int(expectedDataByteSize))
- processor.audioBufferList.mBuffers.mDataByteSize = expectedDataByteSize
- }
-
- // 渲染音频数据
- let status = processor.checkOSStatus(AudioUnitRender(processor.ioUnit!, ioActionFlags, inTimeStamp,
- inBusNumber, inNumberFrames, &processor.audioBufferList),
- "渲染音频数据")
-
- // 将Int16数据转换为浮点数据进行处理
- var audioDataFloat = [Float](repeating: 0.0, count: Int(inNumberFrames))
- let buffer = processor.audioBufferList.mBuffers
- let bufferData = buffer.mData!.assumingMemoryBound(to: Int16.self)
-
- for j in 0...size)) {
- // 归一化到[-1.0, 1.0]范围
- audioDataFloat[j] = Float(bufferData[j]) / 32768.0
- }
-
- // 保存处理后的数据
- if status == noErr {
- processor.audioListQueue.async {
- processor.audioList.append(contentsOf: audioDataFloat)
- }
- }
-
- return status
- }
-
- /// 读取处理后的音频数据
- /// - Parameter bytes: 输出字节数组
- /// - Returns: 读取的字节数
- func read(bytes: inout [UInt8]) -> Int {
- return audioListQueue.sync {
- // 如果没有数据,返回0
- if audioList.isEmpty {
- return 0
- }
-
- // 确保有足够的数据 (至少1280个样本)
- if audioList.count < 1280 {
- return 0
- }
-
- // 读取一帧数据 (1280个样本)
- let frameLength = 1280
- let buffer = Array(audioList.prefix(frameLength))
- audioList.removeFirst(frameLength)
-
- // 将浮点数据转回Int16格式
- var int16Data = buffer.map { Int16($0 * 32767) }
-
- // 转换为字节数组
- let data = Data(buffer: UnsafeBufferPointer(start: &int16Data, count: int16Data.count))
- bytes = [UInt8](data)
-
- // 每个样本2字节 (16位PCM)
- return frameLength * 2
- }
- }
-
- /// 检查错误并打印日志
- /// - Parameters:
- /// - status: 操作状态
- /// - operation: 操作描述
- /// - Returns: 是否发生错误
- private func checkError(_ status: OSStatus, _ operation: String) -> Bool {
- if status != noErr {
- print("[CustomAudioProcessor] 错误: \(operation)失败: \(status)")
- return true
- }
- return false
- }
-
- /// 检查OSStatus并返回状态
- /// - Parameters:
- /// - status: 操作状态
- /// - operation: 操作描述
- /// - Returns: 原始状态
- private func checkOSStatus(_ status: OSStatus, _ operation: String) -> OSStatus {
- if status != noErr {
- print("[CustomAudioProcessor] 错误: \(operation)失败: \(status)")
- }
- return status
- }
-}
\ No newline at end of file
diff --git a/ios/Runner/AzureTtsHelper.swift b/ios/Runner/AzureTtsHelper.swift
deleted file mode 100644
index f62c3f579..000000000
--- a/ios/Runner/AzureTtsHelper.swift
+++ /dev/null
@@ -1,454 +0,0 @@
-import Foundation
-import MicrosoftCognitiveServicesSpeech
-import AVFoundation
-
-/// Azure TTS工具类,负责实现TTS服务接口
-@available(iOS 13.0, *)
-class AzureTtsHelper: NSObject {
- // MARK: - 属性
-
- /// 事件处理回调
- private var eventHandler: (([String: Any]) -> Void)?
-
- /// 语音配置信息
- private var speechSubscriptionKey: String = ""
- private var serviceRegion: String = ""
-
- /// 语音合成配置
- private var speechConfig: SPXSpeechConfiguration?
-
- /// 语音合成器
- private var synthesizer: SPXSpeechSynthesizer?
-
- /// 是否初始化成功
- private var isInitialized = false
-
- /// 当前是否正在播放
- private var _isSpeaking = false
-
- /// 音频会话配置
- private var isAudioSessionConfigured = false
-
- /// 音频会话管理器
- private let audioSessionManager = AudioSessionManager.shared
-
- // MARK: - 语音设置
-
- /// 当前语音
- private var currentVoice = "zh-CN-XiaoxiaoNeural"
-
- /// 支持的语音映射
- private var voiceMap: [String: String] = [
- "zh-CN": "zh-CN-XiaoxiaoNeural",
- "en-US": "en-US-JennyNeural",
- "ja-JP": "ja-JP-NanamiNeural",
- "ko-KR": "ko-KR-SunHiNeural",
- "zh-TW": "zh-TW-HsiaoChenNeural",
- "zh-HK": "zh-HK-HiuMaanNeural"
- ]
-
- /// 当前语音合成参数
- private var currentSpeechRate = "0%"
- private var currentPitch = "0%"
- private var currentVolume = "100%"
-
- // MARK: - 初始化
-
- override init() {
- super.init()
- }
-
- deinit {
- dispose()
- }
-
- /// 设置事件处理器
- @objc func setEventHandler(_ handler: @escaping ([String: Any]) -> Void) {
- self.eventHandler = handler
- }
-
- // MARK: - TTS 接口实现
-
- /// 初始化语音合成服务
- /// - Parameters:
- /// - speechSubscriptionKey: Azure 语音服务订阅密钥
- /// - serviceRegion: Azure 服务区域 (如 eastasia)
- /// - language: 语言代码 (默认 zh-CN)
- /// - Returns: 初始化是否成功
- func initialize(speechSubscriptionKey: String, serviceRegion: String, language: String = "zh-CN") -> Bool {
- print("[AzureTtsHelper] 初始化语音合成服务")
-
- // 检查配置是否为空
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureTtsHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- // 释放之前的资源
- dispose()
-
- // 记录配置信息
- self.speechSubscriptionKey = speechSubscriptionKey
- self.serviceRegion = serviceRegion
-
- // 配置音频会话
- if !configureAudioSession() {
- print("[AzureTtsHelper] 警告: 音频会话配置失败,将尝试继续初始化")
- }
-
- do {
- // 创建语音配置
- speechConfig = try SPXSpeechConfiguration(subscription: speechSubscriptionKey, region: serviceRegion)
-
- // 设置默认语音
- let defaultVoice = getDefaultVoiceForLanguage(language)
- currentVoice = defaultVoice
- speechConfig?.speechSynthesisVoiceName = defaultVoice
-
- // 创建语音合成器
- synthesizer = try SPXSpeechSynthesizer(speechConfig!)
-
- // 设置事件处理器
- setupSynthesizerEvents()
-
- isInitialized = true
- print("[AzureTtsHelper] TTS 引擎初始化成功")
-
- return true
- } catch {
- print("[AzureTtsHelper] 错误: 初始化语音合成服务失败: \(error.localizedDescription)")
- eventHandler?(["type": "error", "message": "初始化语音合成服务失败: \(error.localizedDescription)"])
- return false
- }
- }
-
- /// 配置音频会话
- private func configureAudioSession() -> Bool {
- do {
- // 使用音频会话管理器配置媒体播放模式
- try audioSessionManager.configureForVoiceInteraction()
-
- isAudioSessionConfigured = true
- print("[AzureTtsHelper] 音频会话配置成功")
- return true
- } catch {
- print("[AzureTtsHelper] 警告: 音频会话配置失败: \(error.localizedDescription)")
- isAudioSessionConfigured = false
- return false
- }
- }
-
- /// 设置语音
- /// - Parameter voiceName: 语音名称 (如 "zh-CN-XiaoxiaoNeural")
- /// - Returns: 设置是否成功
- func setVoice(voiceName: String) -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
- eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"])
- return false
- }
-
- // 检查参数有效性
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureTtsHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- if voiceName.isEmpty {
- print("[AzureTtsHelper] 错误: 声音名称为空")
- eventHandler?(["type": "error", "message": "声音名称不能为空"])
- return false
- }
-
- if voiceName == currentVoice {
- print("[AzureTtsHelper] 已设置语音: \(voiceName)")
- return true
- }
-
- print("[AzureTtsHelper] 设置声音: \(voiceName)")
- currentVoice = voiceName
-
- // 更新语音配置
- if let speechConfig = speechConfig {
- speechConfig.speechSynthesisVoiceName = voiceName
- return true
- }
-
- return false
- }
-
- /// 设置语音合成参数
- /// - Parameters:
- /// - rate: 语速,范围 -100 到 100,默认为 0
- /// - pitch: 音调,范围 -100 到 100,默认为 0
- /// - volume: 音量,范围 0 到 100,默认为 100
- /// - Returns: 是否设置成功
- func setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100) -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
- eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"])
- return false
- }
-
- // 检查参数有效性
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureTtsHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- // 转换参数格式
- currentSpeechRate = formatRateParam(rate)
- currentPitch = formatPitchParam(pitch)
- currentVolume = formatVolumeParam(volume)
-
- print("[AzureTtsHelper] 已设置语音参数: 语速=\(currentSpeechRate), 音调=\(currentPitch), 音量=\(currentVolume)")
- return true
- }
-
- /// 合成文本为语音并播放
- /// - Parameter text: 要合成的文本
- /// - Returns: 操作是否成功启动
- func speakText(text: String) -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
- eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"])
- return false
- }
-
- // 检查参数有效性
- if speechSubscriptionKey.isEmpty || serviceRegion.isEmpty {
- print("[AzureTtsHelper] 错误: Azure 配置信息不完整")
- eventHandler?(["type": "error", "message": "Azure 配置信息不完整"])
- return false
- }
-
- if text.isEmpty {
- print("[AzureTtsHelper] 警告: 要播放的文本为空")
- return true
- }
-
- // 确保音频会话已配置
- if !isAudioSessionConfigured {
- _ = configureAudioSession()
- }
-
- print("[AzureTtsHelper] 开始语音合成: \(text.prefix(50))...")
-
- // 生成SSML
- let ssml = generateSsml(text: text)
-
- // 直接进行SSML合成
- return speakSsmlInternal(text: ssml)
- }
-
- /// 内部SSML合成和播放
- private func speakSsmlInternal(text: String) -> Bool {
- guard let synthesizer = synthesizer else {
- print("[AzureTtsHelper] 错误: 合成器未初始化")
- eventHandler?(["type": "error", "message": "合成器未初始化"])
- return false
- }
-
- _isSpeaking = true
- eventHandler?(["type": "started"])
-
- Task {
- do {
- // 使用异步方法进行合成并直接播放
- _ = try synthesizer.startSpeakingSsml(text)
-
- } catch {
- print("[AzureTtsHelper] 错误: 语音合成失败: \(error.localizedDescription)")
- DispatchQueue.main.async {
- self._isSpeaking = false
- self.eventHandler?(["type": "error", "message": "语音合成失败: \(error.localizedDescription)"])
- }
- }
- }
-
- return true
- }
-
- /// 停止当前语音合成
- /// - Returns: 操作是否成功
- func stopSpeaking() -> Bool {
- // 检查是否初始化
- if !isInitialized {
- print("[AzureTtsHelper] 错误: TTS 引擎尚未初始化")
- eventHandler?(["type": "error", "message": "TTS 引擎尚未初始化"])
- return false
- }
-
- if !_isSpeaking {
- print("[AzureTtsHelper] 未进行语音播放,忽略停止请求")
- return true
- }
-
- // 停止合成
- do {
- try synthesizer?.stopSpeaking()
- _isSpeaking = false
- eventHandler?(["type": "canceled"])
- print("[AzureTtsHelper] 已停止语音合成")
- return true
- } catch {
- print("[AzureTtsHelper] 错误: 停止语音合成失败: \(error.localizedDescription)")
- eventHandler?(["type": "error", "message": "停止语音合成失败: \(error.localizedDescription)"])
- return false
- }
- }
-
- /// 检查是否正在播放
- /// - Returns: 当前是否正在播放语音
- func isSpeaking() -> Bool {
- return _isSpeaking
- }
-
- /// 释放资源
- func dispose() {
- // 尝试停止所有语音合成
- if _isSpeaking {
- do {
- if let synthesizer = synthesizer {
- try synthesizer.stopSpeaking()
- print("[AzureTtsHelper] 语音合成已停止")
- }
- } catch {
- print("[AzureTtsHelper] 警告: 停止语音合成失败: \(error.localizedDescription)")
- }
- _isSpeaking = false
- }
-
- // 释放合成器
- synthesizer = nil
- speechConfig = nil
-
- // 重置状态
- isInitialized = false
- isAudioSessionConfigured = false
-
- print("[AzureTtsHelper] 资源已释放")
- }
-
- // MARK: - 私有辅助方法
-
- /// 设置合成器事件处理
- private func setupSynthesizerEvents() {
- guard let synthesizer = synthesizer else { return }
-
- // 添加书签到达事件处理
- synthesizer.addBookmarkReachedEventHandler { _, e in
- print("[AzureTtsHelper] 书签事件: 音频偏移: \((e.audioOffset + 5000) / 10000)ms, 文本: \"\(e.text)\"")
- }
-
- // 合成完成事件
- synthesizer.addSynthesisCompletedEventHandler { [weak self] _, e in
- guard let self = self else { return }
- print("[AzureTtsHelper] 语音合成完成: 音频持续时间: \(e.result.audioDuration)")
- DispatchQueue.main.async {
- self._isSpeaking = false
- self.eventHandler?(["type": "completed"])
- }
- }
-
- // 合成取消事件
- synthesizer.addSynthesisCanceledEventHandler { [weak self] _, e in
- guard let self = self else { return }
-
- let result = e.result
- do {
- let cancellationDetails = try SPXSpeechSynthesisCancellationDetails(fromCanceledSynthesisResult: result)
- print("[AzureTtsHelper] 语音合成取消: 原因: \(cancellationDetails.reason)")
-
- if cancellationDetails.reason == SPXCancellationReason.error {
- print("[AzureTtsHelper] 错误代码: \(cancellationDetails.errorCode)")
- print("[AzureTtsHelper] 错误详情: \(cancellationDetails.errorDetails ?? "未知")")
- }
-
- DispatchQueue.main.async {
- self._isSpeaking = false
- self.eventHandler?(["type": "error", "message": "语音合成取消: \(cancellationDetails.errorDetails ?? "未知错误")"])
- }
- } catch {
- print("[AzureTtsHelper] 获取取消详情时出错: \(error)")
-
- DispatchQueue.main.async {
- self._isSpeaking = false
- self.eventHandler?(["type": "error", "message": "语音合成被取消"])
- }
- }
- }
-
- // 合成开始事件
- synthesizer.addSynthesisStartedEventHandler { _, _ in
- // print("[AzureTtsHelper] 语音合成开始")
- }
-
- // 合成中事件
- synthesizer.addSynthesizingEventHandler { _, _ in
- // print("[AzureTtsHelper] 语音合成中")
- }
- }
-
- /// 生成 SSML 文本
- private func generateSsml(text: String) -> String {
- return """
-
-
-
- \(text)
-
-
-
- """
- }
-
- /// 格式化语速参数
- private func formatRateParam(_ rate: Int) -> String {
- let clampedRate = rate.clamp(min: -100, max: 100)
- if clampedRate == 0 {
- return "0%"
- } else if clampedRate < 0 {
- return "\(Int(Double(clampedRate) * 0.9))%"
- } else {
- return "+\(clampedRate)%"
- }
- }
-
- /// 格式化音调参数
- private func formatPitchParam(_ pitch: Int) -> String {
- let clampedPitch = pitch.clamp(min: -100, max: 100)
- if clampedPitch == 0 {
- return "0%"
- } else {
- return "\(Int(Double(clampedPitch) * 0.5))%"
- }
- }
-
- /// 格式化音量参数
- private func formatVolumeParam(_ volume: Int) -> String {
- let clampedVolume = volume.clamp(min: 0, max: 100)
- return "\(clampedVolume)%"
- }
-
- /// 获取指定语言的默认语音
- private func getDefaultVoiceForLanguage(_ language: String) -> String {
- return voiceMap[language] ?? "zh-CN-XiaoxiaoNeural"
- }
-}
-
-// MARK: - 扩展
-
-extension Int {
- func clamp(min: Int, max: Int) -> Int {
- if self < min { return min }
- if self > max { return max }
- return self
- }
-}
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
index 19d3697a4..f3c28516f 100644
--- a/ios/Runner/Base.lproj/Main.storyboard
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -1,10 +1,8 @@
-
-
-
+
+
-
-
+
@@ -16,14 +14,13 @@
-
+
-
+
-
diff --git a/ios/Runner/BluetoothMediaButtonHelper.swift b/ios/Runner/BluetoothMediaButtonHelper.swift
deleted file mode 100644
index 1a8369172..000000000
--- a/ios/Runner/BluetoothMediaButtonHelper.swift
+++ /dev/null
@@ -1,464 +0,0 @@
-import Foundation
-import MediaPlayer
-import AVFoundation
-import UIKit
-
-/// 媒体按钮事件处理协议
-@available(iOS 13.0, *)
-@objc protocol MediaButtonEventHandler: AnyObject {
- func sendEvent(_ event: [String: Any])
-}
-
-/// 蓝牙媒体按键事件处理类
-/// 该类仅在iOS 13.0及以上版本可用
-@available(iOS 13.0, *)
-class BluetoothMediaButtonHelper: NSObject, AVAudioPlayerDelegate {
- // MARK: - 属性
-
- @objc static let shared = BluetoothMediaButtonHelper()
-
- /// 日志标签
- private let TAG = "BluetoothMediaButtonHelper"
-
- /// 远程控制中心
- private var remoteCommandCenter: MPRemoteCommandCenter
-
- /// 事件处理器 - 用于发送事件到Flutter
- private weak var eventHandler: MediaButtonEventHandler?
-
- /// 是否已启用监听
- private var isListening = false
-
- /// 静音音频播放器 - 用于保持音频会话活跃
- private var silentAudioPlayer: AVAudioPlayer?
-
- /// 播放计时器 - 用于定期更新播放信息
- private var playbackTimer: Timer?
-
- /// 当前播放时间 - 模拟播放进度
- private var currentPlaybackTime: TimeInterval = 0
-
- /// 总时长 - 模拟
- private let totalDuration: TimeInterval = 300.0
-
- /// 音频会话管理器
- private let audioSessionManager = AudioSessionManager.shared
-
- // MARK: - 初始化
-
- override init() {
- // 获取远程控制中心单例
- self.remoteCommandCenter = MPRemoteCommandCenter.shared()
-
- super.init()
-
- NSLog("\(TAG): 初始化媒体按钮处理器")
-
- // 初始化静音音频播放器
- initSilentAudioPlayer()
- }
-
- deinit {
- stopButtonListening()
- NSLog("\(TAG): 媒体按钮处理器已释放")
- }
-
- // MARK: - 公共方法
-
- /// 设置事件处理器
- @objc func setEventHandler(_ handler: MediaButtonEventHandler) {
- eventHandler = handler
- NSLog("\(TAG): 已设置媒体按钮事件处理器")
- }
-
- /// 开始监听蓝牙媒体按钮事件
- @objc func startButtonListening() -> Bool {
- if isListening {
- NSLog("\(TAG): 已经在监听蓝牙媒体按钮事件")
- return true
- }
-
- NSLog("\(TAG): 开始监听蓝牙媒体按钮事件")
-
- // 立即注册远程命令 - 这不需要音频会话
- registerRemoteCommands()
-
- // 标记为已在监听
- isListening = true
-
- // 确保NowPlaying信息可用 - 这也不依赖于活跃的音频会话
- setupNowPlaying(isPlaying: true)
-
- // 尝试启用静音音频播放(但即使失败,远程命令仍然可用)
- do {
- // try audioSessionManager.configureForBluetoothMediaButtons()
- startSilentAudio()
-
- // 延时1秒
- DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
- self.setupNowPlaying(isPlaying: false)
- self.stopSilentAudio()
- }
-
- // startUpdatingPlaybackInfo()
- } catch {
- NSLog("\(TAG): 音频设置或播放失败: \(error.localizedDescription)")
- // 继续执行,因为远程命令在某些设备上不需要播放音频也能工作
- }
-
- NSLog("\(TAG): 蓝牙媒体按钮监听已启动")
- return true
- }
-
- /// 停止监听蓝牙媒体按钮事件
- @objc func stopButtonListening() -> Bool {
- if !isListening {
- return true
- }
-
- NSLog("\(TAG): 停止监听蓝牙媒体按钮事件")
-
- // 停止播放静音音频
- stopSilentAudio()
-
- // 停止更新播放信息
- stopUpdatingPlaybackInfo()
-
- // 取消注册远程命令
- unregisterRemoteCommands()
-
- // 清除NowPlaying信息
- DispatchQueue.main.async {
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
- }
-
- isListening = false
- NSLog("\(TAG): 蓝牙媒体按钮监听已停止")
- return true
- }
-
- // MARK: - 私有方法
-
- /// 初始化静音音频播放器
- private func initSilentAudioPlayer() {
- do {
- // 尝试加载sample.mp3文件
- if let audioPath = Bundle.main.path(forResource: "sample", ofType: "mp3") {
- let audioUrl = URL(fileURLWithPath: audioPath)
-
- silentAudioPlayer = try AVAudioPlayer(contentsOf: audioUrl)
- silentAudioPlayer?.numberOfLoops = -1 // 无限循环播放
- silentAudioPlayer?.volume = 0.0 // 设置为全音量
- silentAudioPlayer?.prepareToPlay() // 预加载
- silentAudioPlayer?.delegate = self
-
- NSLog("\(TAG): 音频播放器使用sample.mp3初始化成功")
- return
- }
-
- NSLog("\(TAG): 未找到sample.mp3文件,使用生成的静音数据")
-
- } catch {
- NSLog("\(TAG): 初始化静音音频播放器失败: \(error.localizedDescription)")
- }
- }
-
- /// 开始播放静音音频
- private func startSilentAudio() {
- guard let player = silentAudioPlayer else {
- NSLog("\(TAG): 无法开始播放,播放器未初始化")
- return
- }
-
- if !player.isPlaying {
- let success = player.play()
- if success {
- NSLog("\(TAG): 开始播放静音音频")
- } else {
- NSLog("\(TAG): 播放启动失败")
- }
- } else {
- NSLog("\(TAG): 静音音频已在播放中")
- }
- }
-
- /// 停止播放静音音频
- private func stopSilentAudio() {
- silentAudioPlayer?.stop()
- NSLog("\(TAG): 停止播放静音音频")
- }
-
- /// 开始更新播放信息
- private func startUpdatingPlaybackInfo() {
- // 初始设置
- setupNowPlaying(isPlaying: true)
-
- // 定期更新播放信息 - 恢复定时器,确保持续更新播放状态
- playbackTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
- guard let self = self else { return }
-
- // 更新播放时间(模拟)
- self.currentPlaybackTime += 1.0
- if self.currentPlaybackTime >= self.totalDuration {
- self.currentPlaybackTime = 0.0
- }
-
- // 更新NowPlaying信息
- self.setupNowPlaying(isPlaying: true)
- }
-
- NSLog("\(TAG): 开始更新播放信息")
- }
-
- /// 停止更新播放信息
- private func stopUpdatingPlaybackInfo() {
- playbackTimer?.invalidate()
- playbackTimer = nil
-
- // 清空NowPlaying信息
- DispatchQueue.main.async {
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
- }
-
- NSLog("\(TAG): 停止更新播放信息")
- }
-
- /// 设置NowPlaying信息
- private func setupNowPlaying(isPlaying: Bool) {
- NSLog("\(TAG): 设置NowPlaying信息, isPlaying=\(isPlaying)")
-
- // 创建音频专辑图片
- let albumArt = UIImage(named: "AppIcon") // 使用应用图标作为专辑图片
- var nowPlayingInfo: [String: Any] = [
- MPMediaItemPropertyTitle: "DeepSound 语音助手",
- MPMediaItemPropertyArtist: "DeepSound",
- MPNowPlayingInfoPropertyPlaybackRate: isPlaying ? 1.0 : 0.0, // 1.0表示正在播放
- MPNowPlayingInfoPropertyElapsedPlaybackTime: currentPlaybackTime,
- MPMediaItemPropertyPlaybackDuration: totalDuration
- ]
-
- // 如果有专辑图片,添加到NowPlaying信息中
- if let albumArt = albumArt {
- let artwork = MPMediaItemArtwork(boundsSize: albumArt.size) { size in
- return albumArt
- }
- nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
- }
-
- // 设置NowPlaying信息 (在主线程执行)
- DispatchQueue.main.async {
- MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
- NSLog("\(self.TAG): NowPlaying信息设置完成")
- }
- }
-
- /// 注册远程命令
- private func registerRemoteCommands() {
- let commandCenter = MPRemoteCommandCenter.shared()
-
- // 播放/暂停命令
- commandCenter.togglePlayPauseCommand.isEnabled = true
- commandCenter.togglePlayPauseCommand.addTarget(self, action: #selector(handlePlayPauseCommand(_:)))
- commandCenter.playCommand.isEnabled = true
- commandCenter.playCommand.addTarget(self, action: #selector(handlePlayPauseCommand(_:)))
- commandCenter.pauseCommand.isEnabled = true
- commandCenter.pauseCommand.addTarget(self, action: #selector(handlePlayPauseCommand(_:)))
-
- // 下一曲/上一曲命令
- commandCenter.nextTrackCommand.isEnabled = true
- commandCenter.nextTrackCommand.addTarget(self, action: #selector(handleNextTrackCommand(_:)))
- commandCenter.previousTrackCommand.isEnabled = true
- commandCenter.previousTrackCommand.addTarget(self, action: #selector(handlePreviousTrackCommand(_:)))
-
- // 快进/快退命令
- commandCenter.seekForwardCommand.isEnabled = true
- commandCenter.seekForwardCommand.addTarget(self, action: #selector(handleSeekForwardCommand(_:)))
- commandCenter.seekBackwardCommand.isEnabled = true
- commandCenter.seekBackwardCommand.addTarget(self, action: #selector(handleSeekBackwardCommand(_:)))
-
- // 停止命令
- commandCenter.stopCommand.isEnabled = true
- commandCenter.stopCommand.addTarget(self, action: #selector(handleStopCommand(_:)))
-
-
-
- NSLog("\(TAG): 远程命令已注册")
- }
-
- /// 取消注册远程命令
- private func unregisterRemoteCommands() {
- NSLog("\(TAG): 取消注册远程命令")
-
- // 基本控制命令
- remoteCommandCenter.playCommand.isEnabled = false
- remoteCommandCenter.playCommand.removeTarget(nil)
-
- remoteCommandCenter.pauseCommand.isEnabled = false
- remoteCommandCenter.pauseCommand.removeTarget(nil)
-
- remoteCommandCenter.togglePlayPauseCommand.isEnabled = false
- remoteCommandCenter.togglePlayPauseCommand.removeTarget(nil)
-
- remoteCommandCenter.stopCommand.isEnabled = false
- remoteCommandCenter.stopCommand.removeTarget(nil)
-
-
- }
-
- /// 发送按钮事件到Flutter
- private func sendButtonEvent(type: String) {
- // 创建事件数据
- let eventData: [String: Any] = [
- "type": "mediaButtonEvent",
- "buttonType": type,
- "timestamp": Int(Date().timeIntervalSince1970 * 1000)
- ]
-
- // 发送事件到处理器
- eventHandler?.sendEvent(eventData)
- }
-
-
- // MARK: - 命令处理方法
-
- /// 处理播放/暂停命令
- @objc private func handlePlayPauseCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到播放/暂停命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "play_pause")
-
- // 调用VoiceInteractionService处理媒体按钮事件
- if VoiceInteractionService.shared.isServiceRunning() {
- NSLog("\(TAG): 转发到VoiceInteractionService处理")
- VoiceInteractionService.shared.handleMediaButtonAction()
- }
-
- return .success
- }
-
- /// 处理下一曲命令
- @objc private func handleNextTrackCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到下一曲命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "next_track")
-
- // 也可以转发到VoiceInteractionService
- if VoiceInteractionService.shared.isServiceRunning() {
- VoiceInteractionService.shared.handleMediaButtonAction()
- }
-
- return .success
- }
-
- /// 处理上一曲命令
- @objc private func handlePreviousTrackCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到上一曲命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "previous_track")
-
- // 也可以转发到VoiceInteractionService
- if VoiceInteractionService.shared.isServiceRunning() {
- VoiceInteractionService.shared.handleMediaButtonAction()
- }
-
- return .success
- }
-
- /// 处理快进命令
- @objc private func handleSeekForwardCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到快进命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "seek_forward")
-
- return .success
- }
-
- /// 处理快退命令
- @objc private func handleSeekBackwardCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到快退命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "seek_backward")
-
- return .success
- }
-
- /// 处理停止命令
- @objc private func handleStopCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
- NSLog("\(TAG): 收到停止命令")
-
- // 发送事件到Flutter
- sendButtonEvent(type: "stop")
-
- // 调用VoiceInteractionService处理媒体按钮事件
- if VoiceInteractionService.shared.isServiceRunning() {
- VoiceInteractionService.shared.handleMediaButtonAction()
- }
-
- return .success
- }
-
- // MARK: - AVAudioPlayerDelegate方法
-
- /// 音频播放结束回调
- func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
- NSLog("\(TAG): 音频播放结束, 成功: \(flag)")
-
- // 如果仍在监听状态,重新开始播放
- if isListening && player == silentAudioPlayer {
- player.play()
- }
- }
-
- /// 音频播放错误回调
- func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
- if let error = error {
- NSLog("\(TAG): 音频解码错误: \(error.localizedDescription)")
- } else {
- NSLog("\(TAG): 音频解码发生未知错误")
- }
-
- // 重新初始化播放器
- if player == silentAudioPlayer && isListening {
- initSilentAudioPlayer()
- startSilentAudio()
- }
- }
-
- /// 清理和释放资源
- func dispose() {
- NSLog("\(TAG): 释放BluetoothMediaButtonHelper资源")
- // 停止监听
- stopButtonListening()
-
- // 释放播放器和计时器资源
- silentAudioPlayer = nil
- if let timer = playbackTimer {
- timer.invalidate()
- playbackTimer = nil
- }
-
- // 清除引用
- eventHandler = nil
-
- NSLog("\(TAG): 资源已释放")
- }
-}
-
-extension UInt32 {
- var data: Data {
- var int = self
- return Data(bytes: &int, count: MemoryLayout.size)
- }
-}
-
-extension UInt16 {
- var data: Data {
- var int = self
- return Data(bytes: &int, count: MemoryLayout.size)
- }
-}
\ No newline at end of file
diff --git a/ios/Runner/ClassicBluetoothHelper.swift b/ios/Runner/ClassicBluetoothHelper.swift
deleted file mode 100644
index 2694075e0..000000000
--- a/ios/Runner/ClassicBluetoothHelper.swift
+++ /dev/null
@@ -1,243 +0,0 @@
-import Foundation
-import CoreBluetooth
-import AVFoundation
-import UIKit
-
-/// 蓝牙事件处理协议
-@objc protocol BluetoothEventHandler: AnyObject {
- func sendEvent(_ event: [String: Any])
-}
-
-@objc class ClassicBluetoothHelper: NSObject, CBCentralManagerDelegate {
- // MARK: - 属性
-
- // 音频会话管理器
- private let audioSessionManager = AudioSessionManager.shared
-
- // 蓝牙管理器
- private var centralManager: CBCentralManager?
-
- // 事件处理器
- private weak var eventHandler: BluetoothEventHandler?
-
- // MARK: - 初始化与设置
-
- override init() {
- super.init()
- }
-
- // 设置事件处理器
- @objc func setEventHandler(_ handler: BluetoothEventHandler) {
- eventHandler = handler
- }
-
-
- // MARK: - CBCentralManagerDelegate
-
- func centralManagerDidUpdateState(_ central: CBCentralManager) {
- let stateString = getBluetoothStateString(state: central.state)
- sendBluetoothStateEvent(state: stateString)
- }
-
- // MARK: - 设备监控
-
- // 获取蓝牙状态字符串
- private func getBluetoothStateString(state: CBManagerState) -> String {
- switch state {
- case .poweredOn:
- return "on"
- case .poweredOff:
- return "off"
- default:
- return "unknown"
- }
- }
-
- // 发送蓝牙状态事件
- private func sendBluetoothStateEvent(state: String) {
- let event: [String: Any] = [
- "type": "bluetoothStateChanged",
- "state": state,
- "timestamp": Int(Date().timeIntervalSince1970 * 1000)
- ]
- eventHandler?.sendEvent(event)
- }
-
- // 启动蓝牙设备监控
- @objc func initialize() {
- if centralManager == nil {
- centralManager = CBCentralManager(delegate: self, queue: nil)
- }
-
-
-
- // 发送当前蓝牙状态
- if let manager = centralManager {
- let stateString = getBluetoothStateString(state: manager.state)
- sendBluetoothStateEvent(state: stateString)
- }
- // 移除之前的监听器
- NotificationCenter.default.removeObserver(self)
- // 添加新的监听器
- audioSessionManager.addRouteChangeListener(self, selector: #selector(handleRouteChange(_:)))
- // BluetoothMediaButtonHelper.shared.startButtonListening()
- }
-
- // 处理音频路由变化
- @objc private func handleRouteChange(_ notification: Notification) {
- guard let userInfo = notification.userInfo,
- let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
- let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
- return
- }
-
- // 获取当前设备
- let currentDevices = getConnectedAudioDevices()
-
- // 检查原因
- switch reason {
- case .newDeviceAvailable:
- // 新设备已连接
- for device in currentDevices {
- // 发送设备连接事件
- NSLog("发送设备连接事件 - 设备: \(device["name"] ?? "未知设备") [\(device["address"] ?? "未知地址")]")
- sendDeviceConnectedEvent(device: device)
- }
-
- case .oldDeviceUnavailable:
- // 获取断开之前的路由
- if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription {
- // 过滤蓝牙相关设备
- for output in previousRoute.outputs where isBluetoothOutput(output.portType) {
- let device: [String: String] = [
- "name": output.portName,
- "address": cleanDeviceAddress(output.uid)
- ]
- // 发送设备断开事件
- sendDeviceDisconnectedEvent(device: device)
- }
- }
-
- default:
- break
- }
- }
-
- // 检查是否为蓝牙输出
- private func isBluetoothOutput(_ portType: AVAudioSession.Port) -> Bool {
- return portType == .bluetoothA2DP || portType == .bluetoothHFP || portType == .bluetoothLE
- }
-
- // MARK: - 设备事件
-
- // 发送设备连接事件
- private func sendDeviceConnectedEvent(device: [String: String]) {
- guard let name = device["name"], let address = device["address"] else { return }
-
- let deviceMap: [String: Any] = [
- "name": name,
- "address": address,
- "type": "headset"
- ]
-
- let event: [String: Any] = [
- "type": "deviceConnected",
- "device": deviceMap,
- "timestamp": Int(Date().timeIntervalSince1970 * 1000)
- ]
-
- eventHandler?.sendEvent(event)
- }
-
- // 发送设备断开事件
- private func sendDeviceDisconnectedEvent(device: [String: String]) {
- guard let name = device["name"], let address = device["address"] else { return }
-
- let deviceMap: [String: Any] = [
- "name": name,
- "address": address,
- "type": "headset"
- ]
-
- let event: [String: Any] = [
- "type": "deviceDisconnected",
- "device": deviceMap,
- "timestamp": Int(Date().timeIntervalSince1970 * 1000)
- ]
-
- eventHandler?.sendEvent(event)
- }
-
- // MARK: - 公共API
-
- // 获取当前连接的耳机设备
- @objc func getConnectedHeadsetDevices(_ completion: @escaping ([[String: String]]?, String?) -> Void) {
- // 检查蓝牙状态
- if centralManager?.state != .poweredOn {
- completion(nil, "蓝牙未启用")
- return
- }
-
- do {
- // 获取设备
- let devices = getConnectedAudioDevices()
- completion(devices, nil)
- } catch {
- completion(nil, "获取设备失败: \(error.localizedDescription)")
- }
- }
-
- // 获取当前连接的音频设备
- private func getConnectedAudioDevices() -> [[String: String]] {
- var result: [[String: String]] = []
-
- // 获取当前音频路由
- let outputs = audioSessionManager.getCurrentRoute().outputs
-
- // 过滤蓝牙相关设备
- for output in outputs where isBluetoothOutput(output.portType) {
- let device: [String: String] = [
- "name": output.portName,
- "address": cleanDeviceAddress(output.uid) // 处理设备地址,移除后缀
- ]
- result.append(device)
- }
-
- return result
- }
-
- // 释放资源
- @objc func dispose() {
- audioSessionManager.removeRouteChangeListener(self)
- eventHandler = nil
- }
-
-
-
- // 检查蓝牙是否启用
- @objc func isBluetoothEnabled() -> Bool {
- return centralManager?.state == .poweredOn
- }
-
- // 清理设备地址,移除后缀
- private func cleanDeviceAddress(_ address: String) -> String {
- // 检查是否包含常见分隔符
- if let range = address.range(of: "-") ?? address.range(of: ":") ?? address.range(of: "_") {
- // 只保留分隔符之前的部分
- return String(address[..
-CFBundleURLTypes
-
-
- CFBundleURLName
- com.yunqiinnovation.deepsound
- CFBundleURLSchemes
-
- fb3829022037361855
- com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278
-
-
-
-FacebookAppID
-3829022037361855
-FacebookClientToken
-193d0a2b1757c41cfa557ea8e8f21c33
-FacebookDisplayName
-Deepsound
-
-LSApplicationQueriesSchemes
-
- fbapi
- fb-messenger-api
- fbauth2
- fbshareextension
- googlechrome
- googlechromes
-
- ITSAppUsesNonExemptEncryption
-
CADisableMinimumFrameDurationOnPhone
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- DeepSound
+ Deep Voice
CFBundleExecutable
$(EXECUTABLE_NAME)
- NSUserTrackingUsageDescription
- This application needs to access your tracking data in order to offer better personalized services and advertisements
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
@@ -54,23 +22,57 @@
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ fb3829022037361855
+
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ wx5bbb7be9324f3e2d
+
+
+
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
+ LSApplicationQueriesSchemes
+
+ weixin
+ weixinULAPI
+ weixinURLParamsAPI
+ fbapi
+ fbapi20130214
+ fbapi20130410
+ fbapi20130702
+ fbapi20131010
+ fbapi20131219
+ fbapi20140410
+ fbapi20140116
+ fbapi20150313
+ fbapi20150629
+ fbapi20160328
+ fbauth
+ fb-messenger-share-api
+ fbauth2
+ fbshareextension
+
LSRequiresIPhoneOS
- NSBluetoothAlwaysUsageDescription
- Bluetooth permission is required to connect and manage audio devices
- NSBluetoothPeripheralUsageDescription
- Bluetooth permission is required to connect and manage audio devices
- NSMicrophoneUsageDescription
- Microphone permission is required for voice recognition
- NSSpeechRecognitionUsageDescription
- Speech recognition permission is required to provide voice interaction services
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+ NSAllowsArbitraryLoadsInWebContent
+
+
UIApplicationSupportsIndirectInputEvents
- UIBackgroundModes
-
-
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -88,5 +90,23 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSBluetoothAlwaysUsageDescription
+ 需要蓝牙权限用于连接和管理音频设备
+ NSBluetoothPeripheralUsageDescription
+ 需要蓝牙权限用于连接和管理音频设备
+ NSLocationWhenInUseUsageDescription
+ 需要位置权限用于搜索附近的蓝牙设备
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ 需要位置权限用于搜索附近的蓝牙设备
+ NSMicrophoneUsageDescription
+ 需要麦克风权限用于语音识别和录音功能
+ FacebookClientToken
+ 193d0a2b1757c41cfa557ea8e8f21c33
+ FacebookAppID
+ 3829022037361855
+ FacebookAdvertiserIDCollectionEnabled
+
+ FacebookAutoLogAppEventsEnabled
+
diff --git a/ios/Runner/Resources/.gitkeep b/ios/Runner/Resources/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/ios/Runner/Resources/sample.mp3 b/ios/Runner/Resources/sample.mp3
deleted file mode 100644
index 31ebc28ce..000000000
Binary files a/ios/Runner/Resources/sample.mp3 and /dev/null differ
diff --git a/ios/Runner/Resources/silence.mp3 b/ios/Runner/Resources/silence.mp3
deleted file mode 100644
index f5bd92f88..000000000
Binary files a/ios/Runner/Resources/silence.mp3 and /dev/null differ
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
index 72042ccf3..308a2a560 100644
--- a/ios/Runner/Runner-Bridging-Header.h
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -1,7 +1 @@
-#ifndef Runner_Bridging_Header_h
-#define Runner_Bridging_Header_h
-
#import "GeneratedPluginRegistrant.h"
-#import
-
-#endif /* Runner_Bridging_Header_h */
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
new file mode 100644
index 000000000..0c67376eb
--- /dev/null
+++ b/ios/Runner/Runner.entitlements
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/ios/Runner/VoiceInteractionService.swift b/ios/Runner/VoiceInteractionService.swift
deleted file mode 100644
index f19baf2c9..000000000
--- a/ios/Runner/VoiceInteractionService.swift
+++ /dev/null
@@ -1,565 +0,0 @@
-import Foundation
-import UIKit
-import AVFoundation
-
-/**
- * 语音交互服务,iOS原生实现
- *
- * 负责:
- * 1) 监听蓝牙耳机按键
- * 2) 处理语音识别
- * 3) 与火山AI服务交互
- * 4) 文本转语音播放
- */
-@available(iOS 13.0, *)
-class VoiceInteractionService: NSObject {
- // 常量
- private let TAG = "VoiceInteractionService"
- private let RECOGNITION_TIMEOUT: TimeInterval = 8.0
-
- // 单例
- static let shared = VoiceInteractionService()
-
- // 服务状态
- private var isRunning = false
- private var isActive = false
- private var isRecognitionActive = false
- private var isTimeoutPaused = false
- private var hasSpeechDetected = false
- private var isTtsSpeaking = false
-
- // 事件处理器
- private weak var eventHandler: VoiceInteractionEventHandler?
-
- // 按键处理
- private var lastKeyEventTime: TimeInterval = 0
- private var keyEventCount = 0
-
- // 活动时间
- private var lastActivityTime: TimeInterval = 0
-
- // 当前用户输入
- private var currentUserInput = ""
- private var lastUserMessage = ""
- private var lastAssistantMessage = ""
-
- // 服务组件
- private var azureAsrHelper: AzureAsrHelper?
- private var azureTtsHelper: AzureTtsHelper?
- private var volcanoAIService: VolcanoAIService?
-
-
- private var silenceTimer: Timer?
-
- // 系统提示词
- private let systemPrompt = """
- 你是一个智能语音助手,能够简洁明了地回答用户的问题。
- 请保持回答简短、准确,避免过长的解释。
- 如果用户的问题不清楚,请礼貌地请求澄清。
- 不要使用复杂的术语,除非用户明确要求。
- 用户用语音和你交互。
- """
-
- // 初始化方法
- private override init() {
- super.init()
- NSLog("%@: VoiceInteractionService 初始化中", TAG)
-
-
- }
-
- /**
- * 启动服务
- *
- * @param apiKey Azure语音服务API密钥
- * @param region Azure语音服务区域
- * @param volcanoKey 火山AI服务API密钥
- * @return 启动是否成功
- */
- func start(azureKey: String, azureRegion: String, volcanoKey: String) -> Bool {
- guard !isRunning else {
- NSLog("%@: 服务已经在运行", TAG)
- return true
- }
-
- NSLog("%@: 启动服务中", TAG)
-
- // 重置状态
- resetState()
-
- // 初始化各个服务组件
- initServices(azureKey: azureKey, azureRegion: azureRegion, volcanoKey: volcanoKey)
-
- // 启动monitorService定时器
- silenceTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(monitorService), userInfo: nil, repeats: true)
- NSLog("%@: monitorService定时器已启动", TAG)
-
- // 标记服务为运行状态
- isRunning = true
-
- NSLog("%@: 服务启动完成,等待蓝牙按键事件", TAG)
- return true
- }
-
- /**
- * 停止服务
- */
- func stop() {
- guard isRunning else {
- NSLog("%@: 服务未运行", TAG)
- return
- }
-
- NSLog("%@: 停止服务中", TAG)
-
- // 停止monitorService定时器
- silenceTimer?.invalidate()
- silenceTimer = nil
-
- // 停止语音识别
- if isRecognitionActive {
- stopVoiceRecognition()
- }
-
- // 停止TTS
- stopCurrentTTS()
-
- // 释放服务实例
- azureAsrHelper?.dispose()
- azureTtsHelper?.dispose()
-
- // 更新状态
- resetState()
- isRunning = false
-
- NSLog("%@: 服务已停止", TAG)
- }
-
- /**
- * 获取服务运行状态
- */
- func isServiceRunning() -> Bool {
- return isRunning
- }
-
- /**
- * 重置状态
- */
- private func resetState() {
- isActive = false
- isRecognitionActive = false
- isTimeoutPaused = false
- hasSpeechDetected = false
- isTtsSpeaking = false
- lastUserMessage = ""
- lastAssistantMessage = ""
- }
-
- /**
- * 初始化服务
- */
- private func initServices(azureKey: String, azureRegion: String, volcanoKey: String) {
- NSLog("%@: 初始化服务组件", TAG)
-
- // 初始化Azure ASR
- azureAsrHelper = AzureAsrHelper()
- if let helper = azureAsrHelper {
- helper.setEventHandler({ [weak self] eventData in
- // 处理ASR事件
- self?.handleAsrEvent(eventData: eventData)
- })
- }
-
- // 初始化Azure TTS
- azureTtsHelper = AzureTtsHelper()
- if let helper = azureTtsHelper {
- helper.setEventHandler({ [weak self] eventData in
- // 处理TTS事件
- self?.handleTtsEvent(eventData: eventData)
- })
- }
-
- // 初始化火山AI服务
- volcanoAIService = VolcanoAIService()
-
- // 配置Azure ASR
- if !azureKey.isEmpty && !azureRegion.isEmpty {
- azureAsrHelper?.initialize(speechSubscriptionKey: azureKey, serviceRegion: azureRegion, supportedLanguages: ["zh-CN"])
- NSLog("%@: Azure ASR初始化完成", TAG)
- } else {
- NSLog("%@: Azure配置信息不完整,无法初始化Azure ASR", TAG)
- }
-
- // 配置Azure TTS
- if !azureKey.isEmpty && !azureRegion.isEmpty {
- azureTtsHelper?.initialize(speechSubscriptionKey: azureKey, serviceRegion: azureRegion, language: "zh-CN")
- NSLog("%@: Azure TTS初始化完成", TAG)
- } else {
- NSLog("%@: Azure配置信息不完整,无法初始化Azure TTS", TAG)
- }
-
- // 配置火山AI
- if !volcanoKey.isEmpty {
- volcanoAIService?.initialize(apiKey: volcanoKey)
- NSLog("%@: 火山AI服务初始化完成", TAG)
- } else {
- NSLog("%@: 火山AI配置信息不完整,无法初始化火山AI服务", TAG)
- }
- }
-
- /**
- * 监控服务状态
- */
- @objc private func monitorService() {
- // 确保服务保持活跃状态
- if !isActive {
- isActive = true
- }
-
- // 检查语音识别状态
- if isRecognitionActive {
- let currentTime = Date().timeIntervalSince1970
- let elapsedTime = currentTime - lastActivityTime
-
- // NSLog("%@: 已过去时间: %@, 是否检测到语音: %@, 是否TTS播放: %@", TAG, String(elapsedTime), String(hasSpeechDetected), String(isTtsSpeaking), String(elapsedTime))
-
- // 如果超过超时时间没有检测到语音,且不在TTS播放中,暂停语音识别
- if !hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT {
- // NSLog("%@: 超过%@秒未检测到语音,停止识别", TAG, String(RECOGNITION_TIMEOUT))
- isTimeoutPaused = true
- playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。")
-
- // 停止语音识别并确保资源完全释放
- stopVoiceRecognition()
-
- // 清理识别状态
- isRecognitionActive = false
- hasSpeechDetected = false
- }
- }
-
- }
-
- /**
- * 处理ASR事件
- */
- private func handleAsrEvent(eventData: [String: Any]) {
- if let type = eventData["type"] as? String {
- // NSLog("%@: 处理ASR事件: %@", TAG, type)
-
- switch type {
- case "recognizing":
- if let text = eventData["text"] as? String, !text.isEmpty {
- hasSpeechDetected = true
- stopCurrentTTS()
- updateLastActivityTime()
- } else if let data = eventData["data"] as? [String: Any],
- let text = data["text"] as? String,
- !text.isEmpty {
- hasSpeechDetected = true
- stopCurrentTTS()
- updateLastActivityTime()
- }
-
- case "result":
- var text: String?
-
- // 直接读取text或从data中读取
- if let directText = eventData["text"] as? String, !directText.isEmpty {
- text = directText
- } else if let data = eventData["data"] as? [String: Any],
- let dataText = data["text"] as? String,
- !dataText.isEmpty {
- text = dataText
- }
-
- if let finalText = text {
- updateLastActivityTime()
-
- // 保存用户消息
- lastUserMessage = finalText
-
- processWithVolcanoAI(finalText)
- }
-
- // 重置状态,继续识别
- hasSpeechDetected = false
-
- case "sessionStarted":
- updateLastActivityTime()
-
- case "sessionStopped":
- isRecognitionActive = false
-
- case "canceled":
- isRecognitionActive = false
-
- case "error":
- isRecognitionActive = false
- if let message = eventData["message"] as? String {
- NSLog("%@: ASR错误: %@", TAG, message)
- }
- playNotification("语音识别出错")
-
- default:
- break
- }
- }
- }
-
- /**
- * 处理TTS事件
- */
- private func handleTtsEvent(eventData: [String: Any]) {
- if let type = eventData["type"] as? String {
- // NSLog("%@: 处理TTS事件: %@", TAG, type)
-
- switch type {
- case "started":
- isTtsSpeaking = true
-
- case "completed":
- isTtsSpeaking = false
- updateLastActivityTime()
-
- case "canceled":
- isTtsSpeaking = false
-
- case "error":
- isTtsSpeaking = false
- if let message = eventData["message"] as? String {
- NSLog("%@: TTS错误: %@", TAG, message)
- }
-
- default:
- break
- }
- }
- }
-
- /**
- * 处理双击事件
- */
- func handleMediaButtonAction() {
- NSLog("%@: 处理媒体按钮事件", TAG)
-
- // 屏蔽短时间重复响应的问题
- let currentTime = Date().timeIntervalSince1970
- let timeDiff = currentTime - lastKeyEventTime
- if timeDiff < 0.5 {
- NSLog("%@: 短时间重复响应,忽略", TAG)
- return
- }
- lastKeyEventTime = currentTime
-
- // 停止当前TTS播放
- stopCurrentTTS()
-
- // 检查应用是否在后台
- if UIApplication.shared.applicationState == .background {
- NSLog("%@: 应用在后台,无法开始语音识别", TAG)
- playNotification("请打开应用后重试!")
- return
- }
- // 播放提示音
- playPrompt("我在!")
-
- // 重置超时暂停标志
- isTimeoutPaused = false
-
- // 启动或重置语音识别
- if !isRecognitionActive {
- NSLog("%@: 语音识别未激活,开始启动", TAG)
-
- startVoiceRecognition()
- } else {
- NSLog("%@: 语音识别已激活,更新活动时间", TAG)
- updateLastActivityTime()
- hasSpeechDetected = false
- }
- }
-
- /**
- * 开始语音识别
- */
- private func startVoiceRecognition() {
- if isRecognitionActive { return }
-
-
-
- // 通知Flutter语音识别已启动
- notifyVoiceRecognitionStarted()
-
- isActive = true
- isRecognitionActive = true
- hasSpeechDetected = false
- updateLastActivityTime()
-
- if let asrHelper = azureAsrHelper {
- let success = asrHelper.startContinuousRecognition()
- if !success {
- isRecognitionActive = false
- NSLog("%@: 启动语音识别失败", TAG)
- playNotification("启动语音识别失败")
- }
- } else {
- isRecognitionActive = false
- NSLog("%@: Azure ASR服务未初始化", TAG)
- playNotification("语音识别服务未初始化")
- }
- }
-
- /**
- * 停止语音识别
- */
- private func stopVoiceRecognition() {
- if !isRecognitionActive { return }
-
- NSLog("%@: 停止语音识别", TAG)
-
- if let asrHelper = azureAsrHelper {
- let success = asrHelper.stopContinuousRecognition()
- if !success {
- NSLog("%@: 停止语音识别失败", TAG)
- }
- }
-
- isRecognitionActive = false
- hasSpeechDetected = false
- }
-
- /**
- * 使用火山AI处理语音识别结果
- */
- private func processWithVolcanoAI(_ text: String) {
- // 保存当前用户输入
- currentUserInput = text
-
- // 在后台线程处理
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
- guard let self = self, let volcanoAIService = self.volcanoAIService else {
- return
- }
-
- do {
- // 创建消息
- let message = volcanoAIService.createUserMessage(content: text)
- let messages = [message]
-
- // 发送请求
- let response = try volcanoAIService.sendMessage(messages: messages, systemPrompt: self.systemPrompt)
-
- // 保存助手回复
- self.lastAssistantMessage = response
-
- // 播放AI回复
- self.speakAIResponse(response)
-
- // 同步聊天记录到Flutter端
- self.notifyChatHistoryUpdated(userMessage: text, assistantMessage: response)
- } catch {
- NSLog("%@: AI处理出错: %@", self.TAG, error.localizedDescription)
- self.playNotification("AI处理出错")
- }
- }
- }
-
- /**
- * 播放AI回复
- */
- private func speakAIResponse(_ text: String) {
- isTtsSpeaking = true
- azureTtsHelper?.speakText(text: text)
- }
-
- /**
- * 播放提示音
- */
- private func playPrompt(_ message: String) {
- isTtsSpeaking = true
- azureTtsHelper?.speakText(text: message)
- }
-
- /**
- * 播放通知提示音
- */
- private func playNotification(_ message: String) {
- isTtsSpeaking = true
- azureTtsHelper?.speakText(text: message)
- }
-
- /**
- * 停止当前TTS播放
- */
- private func stopCurrentTTS() {
- if isTtsSpeaking {
- azureTtsHelper?.stopSpeaking()
- isTtsSpeaking = false
- }
- }
-
- /**
- * 更新最后活动时间
- */
- private func updateLastActivityTime() {
- lastActivityTime = Date().timeIntervalSince1970
- }
-
- /**
- * 暂停语音交互
- */
- func pauseVoiceInteraction() {
- NSLog("%@: 暂停语音交互", TAG)
-
- // 停止当前语音播放
- stopCurrentTTS()
-
- // 停止语音识别
- if isRecognitionActive {
- stopVoiceRecognition()
- }
-
- // 设置状态为暂停,但服务保持运行
- isTimeoutPaused = true
- }
-
- /**
- * 通知语音识别已开始
- */
- private func notifyVoiceRecognitionStarted() {
- // 发送识别开始事件
- sendEvent(type: "recognition_started", data: [:])
- }
-
- /**
- * 通知聊天历史更新
- */
- private func notifyChatHistoryUpdated(userMessage: String, assistantMessage: String) {
- // 发送聊天历史更新事件
- sendEvent(type: "chat_history_updated", data: [
- "agentId": "personal_assistant",
- "userMessage": userMessage,
- "assistantMessage": assistantMessage
- ])
- }
-
- /**
- * 设置事件处理器
- */
- func setEventHandler(_ handler: VoiceInteractionEventHandler) {
- self.eventHandler = handler
- }
-
- /**
- * 发送事件到Flutter端
- */
- private func sendEvent(type: String, data: [String: Any] = [:]) {
- var eventData = data
- eventData["type"] = type
- eventData["timestamp"] = Int(Date().timeIntervalSince1970 * 1000)
-
- // 使用事件处理器发送事件
- eventHandler?.sendEvent(eventData)
- }
-
-}
\ No newline at end of file
diff --git a/ios/Runner/VolcanoAIService.swift b/ios/Runner/VolcanoAIService.swift
deleted file mode 100644
index 4289b3b1b..000000000
--- a/ios/Runner/VolcanoAIService.swift
+++ /dev/null
@@ -1,391 +0,0 @@
-import Foundation
-
-/**
- * 火山AI服务的iOS原生实现
- *
- * 参考Android端的VolcanoAIService实现,提供同步和异步的API调用方式
- */
-class VolcanoAIService {
- private let TAG = "VolcanoAIService"
- private let baseUrl = "https://ark.cn-beijing.volces.com/api/v3"
- private let chatEndpoint = "/chat/completions"
- private let session: URLSession
-
- private var apiKey: String = ""
- private var isInitialized = false
-
- init() {
- let config = URLSessionConfiguration.default
- config.timeoutIntervalForRequest = 30.0
- config.timeoutIntervalForResource = 30.0
- self.session = URLSession(configuration: config)
- }
-
- /**
- * 初始化火山AI服务
- *
- * @param apiKey 火山AI API密钥
- * @return 初始化是否成功
- */
- func initialize(apiKey: String) -> Bool {
- self.apiKey = apiKey
- isInitialized = !apiKey.isEmpty
-
- if !isInitialized {
- NSLog("%@: 初始化失败:API key 不能为空", TAG)
- } else {
- NSLog("%@: 火山AI服务初始化成功", TAG)
- }
-
- return isInitialized
- }
-
- /**
- * 生成个性化问候语
- *
- * @param agentName 代理名称
- * @param systemPrompt 系统提示词
- * @param callback 回调函数,返回生成的问候语
- */
- func generateGreeting(agentName: String, systemPrompt: String, callback: @escaping (String?, Error?) -> Void) {
- let messages: [[String: Any]] = [
- ["role": "system", "content": systemPrompt],
- ["role": "user", "content": "请用一句简短的话向我打个招呼,要符合你的身份和性格特点,不要超过18个字。"]
- ]
-
- let messagesData = try? JSONSerialization.data(withJSONObject: messages, options: [])
- let messagesArray = try? JSONSerialization.jsonObject(with: messagesData!, options: []) as? [[String: Any]]
-
- var result = ""
-
- sendMessageStream(messages: messagesArray!, systemPrompt: systemPrompt, streamCallback: StreamCallback(
- onToken: { token in
- result.append(token)
- },
- onComplete: {
- callback(result, nil)
- },
- onError: { error in
- callback(nil, error)
- }
- ))
- }
-
- /**
- * 发送消息(非流式输出)
- *
- * @param messages 消息列表
- * @param systemPrompt 系统提示词
- * @return 返回AI的回复
- * @throws VolcanoAIError 如果API调用失败
- */
- func sendMessage(messages: [[String: Any]], systemPrompt: String) throws -> String {
- // 检查是否已初始化
- if !isInitialized || apiKey.isEmpty {
- throw VolcanoAIError.serviceNotInitialized
- }
-
- var fullMessages: [[String: Any]] = [
- ["role": "system", "content": systemPrompt]
- ]
-
- fullMessages.append(contentsOf: messages)
-
- let requestBody: [String: Any] = [
- "model": "doubao-1-5-lite-32k-250115",
- "messages": fullMessages,
- "temperature": 0.7,
- "max_tokens": 2000,
- "stream": false
- ]
-
- guard let url = URL(string: "\(baseUrl)\(chatEndpoint)") else {
- throw VolcanoAIError.invalidURL
- }
-
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
- request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
-
- do {
- request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
- } catch {
- throw VolcanoAIError.invalidRequestBody
- }
-
- let semaphore = DispatchSemaphore(value: 0)
- var responseData: Data?
- var responseError: Error?
-
- let task = session.dataTask(with: request) { data, response, error in
- if let error = error {
- responseError = VolcanoAIError.networkError(error.localizedDescription)
- semaphore.signal()
- return
- }
-
- guard let httpResponse = response as? HTTPURLResponse else {
- responseError = VolcanoAIError.invalidResponse
- semaphore.signal()
- return
- }
-
- if !(200...299).contains(httpResponse.statusCode) {
- var errorMessage = "Unknown error occurred"
- if let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
- let error = json["error"] as? [String: Any],
- let message = error["message"] as? String {
- errorMessage = message
- }
- responseError = VolcanoAIError.apiError(errorMessage)
- semaphore.signal()
- return
- }
-
- responseData = data
- semaphore.signal()
- }
-
- task.resume()
- _ = semaphore.wait(timeout: .distantFuture)
-
- if let error = responseError {
- throw error
- }
-
- guard let data = responseData else {
- throw VolcanoAIError.emptyResponse
- }
-
- do {
- guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
- let choices = json["choices"] as? [[String: Any]],
- let firstChoice = choices.first,
- let message = firstChoice["message"] as? [String: Any],
- let content = message["content"] as? String else {
- throw VolcanoAIError.invalidResponseFormat
- }
-
- return content
- } catch {
- throw VolcanoAIError.invalidResponseFormat
- }
- }
-
- /**
- * 发送消息(流式输出)
- *
- * @param messages 消息列表
- * @param systemPrompt 系统提示词
- * @param streamCallback 回调函数,用于接收流式输出的结果
- */
- func sendMessageStream(messages: [[String: Any]], systemPrompt: String, streamCallback: StreamCallback) {
- // 检查是否已初始化
- if !isInitialized || apiKey.isEmpty {
- streamCallback.onError(VolcanoAIError.serviceNotInitialized)
- return
- }
-
- var fullMessages: [[String: Any]] = [
- ["role": "system", "content": systemPrompt]
- ]
-
- fullMessages.append(contentsOf: messages)
-
- let requestBody: [String: Any] = [
- "model": "doubao-1-5-lite-32k-250115",
- "messages": fullMessages,
- "temperature": 0.7,
- "max_tokens": 2000,
- "stream": true
- ]
-
- guard let url = URL(string: "\(baseUrl)\(chatEndpoint)") else {
- streamCallback.onError(VolcanoAIError.invalidURL)
- return
- }
-
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
- request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
- request.addValue("text/event-stream", forHTTPHeaderField: "Accept")
-
- do {
- request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
- } catch {
- streamCallback.onError(VolcanoAIError.invalidRequestBody)
- return
- }
-
- let task = session.dataTask(with: request) { data, response, error in
- if let error = error {
- streamCallback.onError(VolcanoAIError.networkError(error.localizedDescription))
- return
- }
-
- guard let httpResponse = response as? HTTPURLResponse else {
- streamCallback.onError(VolcanoAIError.invalidResponse)
- return
- }
-
- if !(200...299).contains(httpResponse.statusCode) {
- var errorMessage = "Unknown error occurred"
- if let data = data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
- let error = json["error"] as? [String: Any],
- let message = error["message"] as? String {
- errorMessage = message
- }
- streamCallback.onError(VolcanoAIError.apiError(errorMessage))
- return
- }
-
- guard let data = data else {
- streamCallback.onError(VolcanoAIError.emptyResponse)
- return
- }
-
- // 处理SSE流数据
- let responseString = String(data: data, encoding: .utf8) ?? ""
- let lines = responseString.components(separatedBy: "\n")
-
- for line in lines {
- if line.isEmpty { continue }
-
- if line.hasPrefix("data: ") {
- let data = String(line.dropFirst(6))
- if data == "[DONE]" {
- streamCallback.onComplete()
- break
- }
-
- do {
- if let jsonData = data.data(using: .utf8),
- let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
- let choices = json["choices"] as? [[String: Any]],
- let firstChoice = choices.first,
- let delta = firstChoice["delta"] as? [String: Any],
- let content = delta["content"] as? String {
- streamCallback.onToken(content)
- }
- } catch {
- // 忽略无效的JSON数据
- continue
- }
- }
- }
- }
-
- task.resume()
- }
-
- /**
- * 同步方式发送消息(流式输出)
- *
- * 注意:此方法会阻塞当前线程,请在后台线程中调用
- *
- * @param messages 消息列表
- * @param systemPrompt 系统提示词
- * @return 返回完整的AI回复
- * @throws VolcanoAIError 如果API调用失败
- */
- func sendMessageStreamSync(messages: [[String: Any]], systemPrompt: String) throws -> String {
- var result = ""
- let semaphore = DispatchSemaphore(value: 0)
- var responseError: Error?
-
- sendMessageStream(messages: messages, systemPrompt: systemPrompt, streamCallback: StreamCallback(
- onToken: { token in
- result.append(token)
- },
- onComplete: {
- semaphore.signal()
- },
- onError: { error in
- responseError = error
- semaphore.signal()
- }
- ))
-
- // 等待流式输出完成或出错
- _ = semaphore.wait(timeout: .now() + 60)
-
- if let error = responseError {
- throw error
- }
-
- return result
- }
-
- /**
- * 创建用户消息
- */
- func createUserMessage(content: String) -> [String: Any] {
- return ["role": "user", "content": content]
- }
-
- /**
- * 创建系统消息
- */
- func createSystemMessage(content: String) -> [String: Any] {
- return ["role": "system", "content": content]
- }
-
- /**
- * 创建助手消息
- */
- func createAssistantMessage(content: String) -> [String: Any] {
- return ["role": "assistant", "content": content]
- }
-
- /**
- * 流式输出回调类
- */
- class StreamCallback {
- let onToken: (String) -> Void
- let onComplete: () -> Void
- let onError: (Error) -> Void
-
- init(onToken: @escaping (String) -> Void, onComplete: @escaping () -> Void, onError: @escaping (Error) -> Void) {
- self.onToken = onToken
- self.onComplete = onComplete
- self.onError = onError
- }
- }
-}
-
-/**
- * 火山AI错误枚举
- */
-enum VolcanoAIError: Error {
- case serviceNotInitialized
- case invalidURL
- case invalidRequestBody
- case networkError(String)
- case invalidResponse
- case emptyResponse
- case invalidResponseFormat
- case apiError(String)
-
- var localizedDescription: String {
- switch self {
- case .serviceNotInitialized:
- return "火山AI服务未初始化或API key为空,请先调用initialize方法"
- case .invalidURL:
- return "无效的URL"
- case .invalidRequestBody:
- return "无效的请求体"
- case .networkError(let message):
- return "网络错误: \(message)"
- case .invalidResponse:
- return "无效的响应"
- case .emptyResponse:
- return "空响应"
- case .invalidResponseFormat:
- return "无效的响应格式"
- case .apiError(let message):
- return "API错误: \(message)"
- }
- }
-}
\ No newline at end of file
diff --git a/ios/Runner/sample.mp3 b/ios/Runner/sample.mp3
deleted file mode 100644
index 31ebc28ce..000000000
Binary files a/ios/Runner/sample.mp3 and /dev/null differ
diff --git a/ios/Runner/silence.mp3 b/ios/Runner/silence.mp3
deleted file mode 100644
index f5bd92f88..000000000
Binary files a/ios/Runner/silence.mp3 and /dev/null differ
diff --git a/lib/data/services/login_service.dart b/lib/data/services/login_service.dart
index d09ce9672..2844ef0f0 100644
--- a/lib/data/services/login_service.dart
+++ b/lib/data/services/login_service.dart
@@ -8,6 +8,7 @@ class LoginService {
FlWX().register(
appSecret: '4cfde33bc22b6cc6b1f8b225dd97d255',
appId: 'wx5bbb7be9324f3e2d',
+ universalLink: 'https://yunqiinnovation.com/app/',
params: FlWXCallbackParams(onHttp: (url) async {
return '';
}));
@@ -18,13 +19,12 @@ class LoginService {
Future _initializeFirebase() async {
try {
// 尝试获取已存在的实例
- Firebase.app('DeepSound');
+ Firebase.app();
print('已存在的 Firebase 实例被重用');
} catch (e) {
try {
// 如果不存在则初始化新实例
await Firebase.initializeApp(
- name: 'DeepSound',
options: DefaultFirebaseOptions.currentPlatform,
);
print('Firebase 初始化成功');
diff --git a/lib/data/services/speech_impl/volcano_tts_service.dart b/lib/data/services/speech_impl/volcano_tts_service.dart
index 47d7aa2eb..402aac5b8 100644
--- a/lib/data/services/speech_impl/volcano_tts_service.dart
+++ b/lib/data/services/speech_impl/volcano_tts_service.dart
@@ -87,11 +87,11 @@ class VolcanoTtsService extends GetxService implements TtsService {
case 'error':
_isSpeaking = false;
- final errorCode = eventMap['errorCode'] as int? ?? 0;
+ final errorCode = eventMap['errorCode'] as String? ?? "-1";
final errorMessage = eventMap['errorMessage'] as String? ?? '未知错误';
_eventStreamController.add(TtsEvent(
type: TtsEventType.error,
- error: errorMessage,
+ error: '$errorMessage (错误码: $errorCode)',
));
break;
}
diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart
index 1f8b9593e..2fb1540d1 100644
--- a/lib/firebase_options.dart
+++ b/lib/firebase_options.dart
@@ -66,7 +66,7 @@ class DefaultFirebaseOptions {
storageBucket: 'deepsound-e0e3d.firebasestorage.app',
androidClientId: '907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com',
iosClientId: '907754492821-ej6j6pfto66jfhpj47b9bjs9amvo9dde.apps.googleusercontent.com',
- iosBundleId: 'com.example.client',
+ iosBundleId: 'com.yunqiinnovation.deepsound',
);
static const FirebaseOptions macos = FirebaseOptions(
@@ -77,7 +77,7 @@ class DefaultFirebaseOptions {
storageBucket: 'deepsound-e0e3d.firebasestorage.app',
androidClientId: '907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com',
iosClientId: '907754492821-ej6j6pfto66jfhpj47b9bjs9amvo9dde.apps.googleusercontent.com',
- iosBundleId: 'com.example.client',
+ iosBundleId: 'com.yunqiinnovation.deepsound',
);
static const FirebaseOptions windows = FirebaseOptions(
diff --git a/lib/main.dart b/lib/main.dart
index 72b596679..9086063c7 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -24,8 +24,10 @@ void main() async {
// 初始化环境变量
await dotenv.load();
+
+ // 初始化登录服务(包含Firebase初始化)
LoginService();
- //LoginService.FirebaseInit();
+
// 设置窗口大小(仅在 macOS 下生效)
if (Platform.isMacOS) {
const double width = 375.0;
diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart
index 9ee8e9899..64668f38a 100644
--- a/lib/modules/chat/controllers/chat_controller.dart
+++ b/lib/modules/chat/controllers/chat_controller.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
-import 'package:amap_service/amap_service.dart';
import '../../../data/services/spotify_service.dart';
import 'package:get/get.dart';
import 'package:flutter/widgets.dart';
@@ -8,7 +7,6 @@ import 'package:get_storage/get_storage.dart';
import 'package:haptic_feedback/haptic_feedback.dart';
import 'package:just_audio/just_audio.dart';
import 'package:chat_storage/chat_storage.dart';
-import 'package:path/path.dart';
import '../../../data/services/ai_service.dart';
import '../../../data/services/deapsound_ai_service.dart';
import '../../../data/services/kouzi_ai_service.dart';
@@ -32,7 +30,6 @@ class ChatController extends GetxController {
// 添加ScrollController用于控制列表滚动
final ScrollController scrollController = ScrollController();
- final _amapFlutter = AmapService();
StreamSubscription? _locationStream;
String _latLng = '';
String _city = '深圳市';
@@ -187,7 +184,6 @@ class ChatController extends GetxController {
// 自动停止计时器会在startVoiceRecognition中启动
}
- _getLocation();
player.playerStateStream.listen((state) async {
if (state.processingState == ProcessingState.completed) {
@@ -203,15 +199,6 @@ class ChatController extends GetxController {
});
}
- void _getLocation() async {
- await _amapFlutter.initLocation();
- _locationStream = _amapFlutter.locationStream().listen((data) async {
- _latLng = '${data['longitude']},${data['latitude']}}';
- _city = data['city'];
- _amapFlutter.stopLocation();
- });
- _amapFlutter.startLocation();
- }
@override
void onClose() {
@@ -228,7 +215,6 @@ class ChatController extends GetxController {
_talkDebounceTimer?.cancel();
_talkStartDebounceTimer?.cancel();
- _amapFlutter.dispose();
_locationStream?.cancel();
// 停止TTS
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index d15f0da8e..653c849a2 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -9,6 +9,7 @@ import '../../../data/services/bluetooth_manager.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
import '../../../data/services/ble_manager.dart';
import 'package:ble_service/ble_service.dart';
+import 'package:flutter/material.dart';
/// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程
class PairingController extends GetxController {
@@ -83,6 +84,11 @@ class PairingController extends GetxController {
isLoading.value = true;
statusDescription.value = 'checkingPermissions'.tr;
+ // 在iOS上,延迟一下再请求权限,避免多权限冲突
+ if (Platform.isIOS) {
+ await Future.delayed(Duration(milliseconds: 800));
+ }
+
// 检查并请求权限
if (!await _checkAndRequestPermissions()) {
statusDescription.value = 'permissionDenied'.tr;
@@ -192,21 +198,66 @@ class PairingController extends GetxController {
permissionsToRequest.add(Permission.bluetoothConnect);
}
} else if (Platform.isIOS) {
- permissionsToRequest.add(Permission.bluetooth);
+ // iOS上单独处理蓝牙权限请求,避免冲突
+ PermissionStatus status = await Permission.bluetooth.status;
+
+ if (status.isPermanentlyDenied) {
+ Logger.warning('iOS蓝牙权限状态为permanentlyDenied,这可能是iOS系统误报');
+
+ // 在iOS上打开蓝牙设置对话框
+ Get.dialog(
+ AlertDialog(
+ title: Text('需要蓝牙权限'),
+ content: Text('请在系统设置中手动开启蓝牙权限,以便连接设备'),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text('稍后'),
+ ),
+ TextButton(
+ onPressed: () {
+ Get.back();
+ openAppSettings();
+ },
+ child: Text('前往设置'),
+ ),
+ ],
+ ),
+ barrierDismissible: false,
+ );
+ return false;
+ } else if (!status.isGranted) {
+ // 单独请求蓝牙权限
+ PermissionStatus result = await Permission.bluetooth.request();
+ if (!result.isGranted) {
+ Logger.warning('iOS蓝牙权限申请失败: $result');
+ return false;
+ }
+ }
+
+ // iOS不再添加到批量请求列表
+ // permissionsToRequest.add(Permission.bluetooth);
}
-
- statuses = await permissionsToRequest.request();
-
- bool allGranted = true;
- statuses.forEach((permission, status) {
- if (!status.isGranted) {
- allGranted = false;
- Logger.warning('权限 ${permission.toString()} 未被授予: $status');
+
+ // 如果还有其他权限需要请求
+ if (permissionsToRequest.isNotEmpty) {
+ statuses = await permissionsToRequest.request();
+
+ bool allGranted = true;
+ statuses.forEach((permission, status) {
+ if (!status.isGranted) {
+ allGranted = false;
+ Logger.warning('权限 ${permission.toString()} 未被授予: $status');
+ }
+ });
+
+ if (!allGranted) {
+ return false;
}
- });
+ }
- _hasPermissions.value = allGranted;
- return allGranted;
+ _hasPermissions.value = true;
+ return true;
}
// 辅助方法获取Android SDK版本
@@ -430,7 +481,15 @@ class PairingController extends GetxController {
/// 打开应用设置
Future openAppSettings() async {
- await AppSettings.openAppSettings();
+ try {
+ await AppSettings.openAppSettings();
+ // 返回后重新检查权限状态
+ Future.delayed(Duration(milliseconds: 500), () {
+ reinitializeBluetooth();
+ });
+ } catch (e) {
+ Logger.error('打开应用设置失败: ${e.toString()}');
+ }
}
/// 重新开始关联
@@ -447,4 +506,33 @@ class PairingController extends GetxController {
_isNavigatingToHome = true;
Get.offAllNamed(Routes.home);
}
+
+ /// 重新初始化蓝牙
+ Future reinitializeBluetooth() async {
+ try {
+ isLoading.value = true;
+ statusDescription.value = '正在重新初始化蓝牙...';
+
+ // iOS上尝试重置权限状态
+ if (Platform.isIOS) {
+ await Future.delayed(Duration(milliseconds: 500));
+ if (await Permission.bluetooth.isPermanentlyDenied) {
+ Logger.info("iOS蓝牙权限仍处于永久拒绝状态,尝试直接使用蓝牙功能");
+ // 在永久拒绝状态下,尝试使用蓝牙功能,iOS可能会再次弹出系统权限请求
+ _bluetoothService.isBluetoothEnabled();
+ }
+ }
+
+ // 重新检查权限
+ if (await _checkAndRequestPermissions()) {
+ // 重新检查蓝牙状态
+ await _checkBluetoothAndRefresh();
+ }
+ } catch (e) {
+ Logger.error('重新初始化蓝牙失败: ${e.toString()}');
+ } finally {
+ isLoading.value = false;
+ _updateStatusDescription();
+ }
+ }
}
\ No newline at end of file
diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart
index cd7864e3c..e53f146dc 100644
--- a/lib/modules/splash/controllers/splash_controller.dart
+++ b/lib/modules/splash/controllers/splash_controller.dart
@@ -8,7 +8,6 @@ import '../../../data/models/user_Info.dart';
import '../../../routes/app_routes.dart';
import '../../../core/themes/theme_service.dart';
import '../../../data/services/network/api.dart';
-import 'package:amap_flutter/amap_flutter.dart';
class SplashController extends GetxController {
final GetStorage _storage = GetStorage();
diff --git a/local_plugins/agent_service/android/build.gradle.kts b/local_plugins/agent_service/android/build.gradle.kts
index b37d622a5..2a67d0ba0 100644
--- a/local_plugins/agent_service/android/build.gradle.kts
+++ b/local_plugins/agent_service/android/build.gradle.kts
@@ -48,7 +48,5 @@ dependencies {
add("compileOnly", project(":ble_service"))
add("compileOnly", project(":music_service"))
add("compileOnly", project(":amap_service"))
- add("compileOnly", project(":volcano_speech"))
- add("compileOnly", project(":bytedance_speech"))
add("compileOnly", project(":speech"))
}
\ No newline at end of file
diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
index 5c40eaaeb..bda9b148b 100644
--- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
+++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
@@ -21,8 +21,7 @@ import android.os.Looper
import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
-import com.deep_voice.speech.tts.ITtsService
-import com.deep_voice.bytedance_speech.BytedanceTTS
+import com.yunqiinnovation.azure_speech.AzureTtsHelper
/**
* 代理服务事件监听接口
*/
@@ -55,8 +54,8 @@ object AgentService : CoroutineScope {
// Azure服务
private var azureAsrHelper: AzureAsrHelper? = null
- // 使用接口类型,支持不同实现
- private var ttsService: ITtsService? = null
+ // 使用AzureTtsHelper作为唯一的TTS实现
+ private var ttsService: AzureTtsHelper? = null
// OpenAI服务
private lateinit var openAIService: OpenAIService
@@ -247,65 +246,37 @@ object AgentService : CoroutineScope {
}
/**
- * 初始化TTS引擎
+ * 初始化TTS引擎,只使用火山语音引擎
*/
private fun initializeTtsEngine(config: Map) {
// 停止当前TTS服务(如果有)
if (ttsService != null) {
ttsService?.stop()
+ ttsService = null
}
try {
- val ttsEngine = config["ttsEngine"]?.toString() ?: "volcano" // 默认使用火山引擎
+ // 从配置中获取TTS参数
+ val ttsAppId = config["volcanoAppId"]?.toString() ?: ""
+ val ttsAppToken = config["volcanoToken"]?.toString() ?: ""
val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN"
-
- // 根据配置选择合适的TTS引擎
- if (ttsEngine == "azure") {
- // 使用Azure TTS
- val azureSpeechKey = config["azureSpeechKey"]?.toString() ?: ""
- val azureSpeechRegion = config["azureSpeechRegion"]?.toString() ?: ""
-
- // 创建并初始化Azure TTS Helper
- val azureTtsHelper = com.yunqiinnovation.azure_speech.AzureTtsHelper(context)
- ttsService = azureTtsHelper
-
- // 直接初始化,无需协程包装
- val success = azureTtsHelper.initialize(
- ttsAppId = "", // Azure TTS不需要appId
- ttsAppToken = azureSpeechKey,
- ttsResource = azureSpeechRegion,
- language = ttsLanguage
- )
-
- if (success) {
- Log.d(TAG, "Azure TTS服务初始化成功")
- } else {
- Log.e(TAG, "Azure TTS服务初始化失败")
- }
- } else {
- // 默认使用火山引擎TTS
- // 创建并初始化BytedanceTTS
-
- // 从配置中获取TTS参数
- val ttsAppId = config["volcanoAppId"]?.toString() ?: ""
- val ttsAppToken = config["volcanoToken"]?.toString() ?: ""
- val bytedanceTts = BytedanceTTS(context)
- ttsService = bytedanceTts
-
- // 初始化火山TTS
- val success = bytedanceTts.initialize(
- ttsAppId = ttsAppId,
- ttsAppToken = ttsAppToken,
- ttsResource = "", // 火山TTS不需要resource
- language = ttsLanguage
- )
-
- if (success) {
- Log.d(TAG, "火山TTS服务初始化请求已发送")
- } else {
- Log.e(TAG, "火山TTS服务初始化失败")
- }
+ // 创建并初始化AzureTtsHelper
+ val azureTtsHelper = AzureTtsHelper(context)
+ ttsService = azureTtsHelper
+
+ // 初始化Azure TTS
+ val success = azureTtsHelper.initialize(
+ ttsAppId = "", // Azure TTS不需要appId
+ ttsAppToken = config["azureSpeechKey"]?.toString() ?: "", // Azure需要subscription key
+ ttsResource = config["azureSpeechRegion"]?.toString() ?: "", // Azure需要region信息
+ language = ttsLanguage
+ )
+
+ if (success) {
+ Log.d(TAG, "Azure TTS服务初始化成功")
+ } else {
+ Log.e(TAG, "Azure TTS服务初始化失败")
}
// 添加TTS事件监听
@@ -742,7 +713,6 @@ object AgentService : CoroutineScope {
override fun onToken(token: String) {
responseBuilder.append(token)
if (speakResponse) {
- // 直接调用TTS,无需协程包装
ttsService?.speakStream(token)
}
// 发送流式回复token
@@ -753,7 +723,6 @@ object AgentService : CoroutineScope {
Log.d(TAG, "AI完整回复: $responseBuilder")
// 视情况决定是否朗读回复
if (speakResponse) {
- // 直接调用TTS,无需协程包装
ttsService?.flushStream()
}
val response = responseBuilder.toString()
@@ -1130,7 +1099,7 @@ object AgentService : CoroutineScope {
val metaStr = functionCallResult.optString("meta")
if (metaStr.isNotEmpty()) {
val meta = JSONObject(metaStr)
- Log.d(TAG, "mcp调用结果: $metaStr")
+ // Log.d(TAG, "mcp调用结果: $metaStr")
if (meta.has("card_music")) { //音乐卡片
val cardMusic = meta.getJSONObject("card_music")
Log.d(TAG, "检查到音乐卡片: $cardMusic")
diff --git a/local_plugins/azure_speech/.gitignore b/local_plugins/azure_speech/.gitignore
new file mode 100644
index 000000000..79c113f9b
--- /dev/null
+++ b/local_plugins/azure_speech/.gitignore
@@ -0,0 +1,45 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.build/
+.buildlog/
+.history
+.svn/
+.swiftpm/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/local_plugins/azure_speech/.metadata b/local_plugins/azure_speech/.metadata
new file mode 100644
index 000000000..5c982c8d4
--- /dev/null
+++ b/local_plugins/azure_speech/.metadata
@@ -0,0 +1,30 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "ea121f8859e4b13e47a8f845e4586164519588bc"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+ base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+ - platform: ios
+ create_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+ base_revision: ea121f8859e4b13e47a8f845e4586164519588bc
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/local_plugins/azure_speech/analysis_options.yaml b/local_plugins/azure_speech/analysis_options.yaml
new file mode 100644
index 000000000..f9b303465
--- /dev/null
+++ b/local_plugins/azure_speech/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
index 5a27836c7..70227503f 100644
--- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
+++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt
@@ -375,7 +375,7 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
val currentText = streamBuffer.toString()
// 定义标点符号列表
- val punctuationMarks = listOf('.', '。', '!', '!', '?', '?', ';', ';', ',', ',', ':', ':')
+ val punctuationMarks = listOf('.', '。', '!', '!', '?', '?', ';', ';', ',', ',', ':', ':', '\n')
// 查找最后一个标点符号的位置
var lastPunctuationIndex = -1
diff --git a/local_plugins/azure_speech/ios/.gitignore b/local_plugins/azure_speech/ios/.gitignore
new file mode 100644
index 000000000..7a7f9873a
--- /dev/null
+++ b/local_plugins/azure_speech/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/local_plugins/azure_speech/ios/Flutter/AppFrameworkInfo.plist b/local_plugins/azure_speech/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 000000000..7c5696400
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
+
+
diff --git a/local_plugins/azure_speech/ios/Flutter/Debug.xcconfig b/local_plugins/azure_speech/ios/Flutter/Debug.xcconfig
new file mode 100644
index 000000000..592ceee85
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/local_plugins/azure_speech/ios/Flutter/Release.xcconfig b/local_plugins/azure_speech/ios/Flutter/Release.xcconfig
new file mode 100644
index 000000000..592ceee85
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/local_plugins/azure_speech/ios/Runner.xcodeproj/project.pbxproj b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..8b82c852b
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,619 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 54;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+ remoteInfo = Runner;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; };
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 331C8082294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXGroup;
+ children = (
+ 331C807B294A618700263BE5 /* RunnerTests.swift */,
+ );
+ path = RunnerTests;
+ sourceTree = "";
+ };
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ 331C8082294A63A400263BE5 /* RunnerTests */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ 331C8081294A63A400263BE5 /* RunnerTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 331C8080294A63A400263BE5 /* RunnerTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+ buildPhases = (
+ 331C807D294A63A400263BE5 /* Sources */,
+ 331C807F294A63A400263BE5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */,
+ );
+ name = RunnerTests;
+ productName = RunnerTests;
+ productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = YES;
+ LastUpgradeCheck = 1510;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 331C8080294A63A400263BE5 = {
+ CreatedOnToolsVersion = 14.0;
+ TestTargetID = 97C146ED1CF9000F007C117D;
+ };
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ 331C8080294A63A400263BE5 /* RunnerTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 331C807F294A63A400263BE5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 331C807D294A63A400263BE5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 97C146ED1CF9000F007C117D /* Runner */;
+ targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = PNNU77KTCP;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 331C8088294A63A400263BE5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Debug;
+ };
+ 331C8089294A63A400263BE5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Release;
+ };
+ 331C808A294A63A400263BE5 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ GENERATE_INFOPLIST_FILE = YES;
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_USER_SCRIPT_SANDBOXING = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = PNNU77KTCP;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = PNNU77KTCP;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 331C8088294A63A400263BE5 /* Debug */,
+ 331C8089294A63A400263BE5 /* Release */,
+ 331C808A294A63A400263BE5 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/ios/Runner/RunnerRelease.entitlements b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 60%
rename from ios/Runner/RunnerRelease.entitlements
rename to local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
index 62a4a38c3..18d981003 100644
--- a/ios/Runner/RunnerRelease.entitlements
+++ b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -2,9 +2,7 @@
- keychain-access-groups
-
- $(AppIdentifierPrefix)com.yunqiinnovation.deepsound
-
+ IDEDidComputeMac32BitWarning
+
diff --git a/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/local_plugins/azure_speech/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 000000000..15cada483
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner.xcworkspace/contents.xcworkspacedata b/local_plugins/azure_speech/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..1d526a16e
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner/AppDelegate.swift b/local_plugins/azure_speech/ios/Runner/AppDelegate.swift
new file mode 100644
index 000000000..626664468
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import Flutter
+import UIKit
+
+@main
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..d36b1fab2
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 000000000..dc9ada472
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 000000000..7353c41ec
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 000000000..797d452e4
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 000000000..6ed2d933e
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 000000000..4cd7b0099
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 000000000..fe730945a
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 000000000..321773cd8
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 000000000..797d452e4
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 000000000..502f463a9
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 000000000..0ec303439
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 000000000..0ec303439
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 000000000..e9f5fea27
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 000000000..84ac32ae7
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 000000000..8953cba09
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 000000000..0467bf12a
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 000000000..0bedcf2fd
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 000000000..89c2725b7
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/local_plugins/azure_speech/ios/Runner/Base.lproj/LaunchScreen.storyboard b/local_plugins/azure_speech/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000..f2e259c7c
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner/Base.lproj/Main.storyboard b/local_plugins/azure_speech/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..f3c28516f
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner/Info.plist b/local_plugins/azure_speech/ios/Runner/Info.plist
new file mode 100644
index 000000000..1d54d7cd7
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Azure Speech
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ azure_speech
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/local_plugins/azure_speech/ios/Runner/Runner-Bridging-Header.h b/local_plugins/azure_speech/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 000000000..308a2a560
--- /dev/null
+++ b/local_plugins/azure_speech/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/local_plugins/azure_speech/ios/RunnerTests/RunnerTests.swift b/local_plugins/azure_speech/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 000000000..86a7c3b1b
--- /dev/null
+++ b/local_plugins/azure_speech/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/local_plugins/azure_speech/ios/azure_speech.podspec b/local_plugins/azure_speech/ios/azure_speech.podspec
index bc179f0c1..b6eed7b01 100644
--- a/local_plugins/azure_speech/ios/azure_speech.podspec
+++ b/local_plugins/azure_speech/ios/azure_speech.podspec
@@ -16,7 +16,7 @@ A Flutter plugin for Azure Speech services.
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'MicrosoftCognitiveServicesSpeech-iOS'
- s.platform = :ios, '12.0'
+ s.platform = :ios, '13.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
diff --git a/local_plugins/azure_speech/lib/main.dart b/local_plugins/azure_speech/lib/main.dart
new file mode 100644
index 000000000..a7256585a
--- /dev/null
+++ b/local_plugins/azure_speech/lib/main.dart
@@ -0,0 +1,20 @@
+import 'package:flutter/material.dart';
+
+void main() {
+ runApp(const MainApp());
+}
+
+class MainApp extends StatelessWidget {
+ const MainApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return const MaterialApp(
+ home: Scaffold(
+ body: Center(
+ child: Text('Hello World!'),
+ ),
+ ),
+ );
+ }
+}
diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
index 8d53a0950..999d56c3a 100644
--- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
+++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
@@ -101,7 +101,7 @@ object BleService {
private val mainHandler = Handler(Looper.getMainLooper())
// ota解码器实例
- var otaManager: OTAManager? = null
+ // var otaManager: OTAManager? = null
// 初始化状态
private var isInitialized = false
@@ -117,19 +117,19 @@ object BleService {
try {
this.context = appContext.applicationContext
- otaManager = OTAManager(this.context).apply {
- // 设置数据回调
- setDataCallback(object : OTAManager.DataCallback {
- override fun onDataReceived(device: BluetoothDevice?, data: ByteArray?) {
- data?.let {
- Log.i("BleService", "收到数据:${it.size} 字节")
- Log.i("BleService", "认证交互数据${it.contentToString()}")
- // 移除次数限制,除非明确需要
- otaManager?.onReceiveDeviceData(device, it)
- }
- }
- })
- }
+ // otaManager = OTAManager(this.context).apply {
+ // // 设置数据回调
+ // setDataCallback(object : OTAManager.DataCallback {
+ // override fun onDataReceived(device: BluetoothDevice?, data: ByteArray?) {
+ // data?.let {
+ // Log.i("BleService", "收到数据:${it.size} 字节")
+ // Log.i("BleService", "认证交互数据${it.contentToString()}")
+ // // 移除次数限制,除非明确需要
+ // otaManager?.onReceiveDeviceData(device, it)
+ // }
+ // }
+ // })
+ // }
// 初始化蓝牙管理器和适配器
bluetoothManager =
context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
@@ -428,44 +428,44 @@ object BleService {
}
}
- fun startOTA() {
- try {
- otaManager?.startOTA(CustomUpdateCallback(context, null))
- Log.i(TAG, "OTA 升级已启动")
- } catch (e: IllegalStateException) {
- Log.e(TAG, "OTA 启动失败: ${e.message}")
- disconnectGatt()
- }
- }
-
- // 配置OTA
- fun configureOTA() {
- try {
- val firmwarePath =
- context.getExternalFilesDir("downloads/ota_upgrades")?.path + "/1.ufw"
- val file = File(firmwarePath)
- if (!file.exists()) {
- // throw IOException("Firmware file not found")
- Log.i(TAG, "Firmware file not found")
- }
-
- val bluetoothOption = BluetoothOTAConfigure().apply {
- priority = BluetoothOTAConfigure.PREFER_BLE
- isUseReconnect = true
- isUseAuthDevice = true
- mtu = 512
- isNeedChangeMtu = false // 确保MTU请求生效
- isUseJLServer = false
- firmwareFilePath = firmwarePath
- }
- otaManager?.configure(bluetoothOption) ?: run {
- Log.e(TAG, "OTA Manager未初始化")
- }
- } catch (e: Exception) {
- Log.e(TAG, "OTA配置失败: ${e.message}")
- // 处理错误
- }
- }
+ // fun startOTA() {
+ // try {
+ // otaManager?.startOTA(CustomUpdateCallback(context, null))
+ // Log.i(TAG, "OTA 升级已启动")
+ // } catch (e: IllegalStateException) {
+ // Log.e(TAG, "OTA 启动失败: ${e.message}")
+ // disconnectGatt()
+ // }
+ // }
+
+ // // 配置OTA
+ // fun configureOTA() {
+ // try {
+ // val firmwarePath =
+ // context.getExternalFilesDir("downloads/ota_upgrades")?.path + "/1.ufw"
+ // val file = File(firmwarePath)
+ // if (!file.exists()) {
+ // // throw IOException("Firmware file not found")
+ // Log.i(TAG, "Firmware file not found")
+ // }
+
+ // val bluetoothOption = BluetoothOTAConfigure().apply {
+ // priority = BluetoothOTAConfigure.PREFER_BLE
+ // isUseReconnect = true
+ // isUseAuthDevice = true
+ // mtu = 512
+ // isNeedChangeMtu = false // 确保MTU请求生效
+ // isUseJLServer = false
+ // firmwareFilePath = firmwarePath
+ // }
+ // otaManager?.configure(bluetoothOption) ?: run {
+ // Log.e(TAG, "OTA Manager未初始化")
+ // }
+ // } catch (e: Exception) {
+ // Log.e(TAG, "OTA配置失败: ${e.message}")
+ // // 处理错误
+ // }
+ // }
/**
* 更新连接状态并通知回调
@@ -481,35 +481,35 @@ object BleService {
private val gattCallback = object : BluetoothGattCallback() {
- override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
- if (status == BluetoothGatt.GATT_SUCCESS) {
- Log.i(TAG, "MTU 更新成功: $mtu")
- configureOTA()
+ // override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
+ // if (status == BluetoothGatt.GATT_SUCCESS) {
+ // Log.i(TAG, "MTU 更新成功: $mtu")
+ // configureOTA()
- startOTA()
- } else {
- Log.e(TAG, "MTU 更新失败: status=$status")
- }
- }
+ // startOTA()
+ // } else {
+ // Log.e(TAG, "MTU 更新失败: status=$status")
+ // }
+ // }
override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
when {
status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED -> {
updateConnectionState(BleConst.STATE_CONNECTED)
g.discoverServices()
- Log.i(TAG, "连接成功,开始 MTU 协商")
+ // Log.i(TAG, "连接成功,开始 MTU 协商")
- otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_OK)
- g.requestMtu(512) // 触发 MTU 修改流程
+ // otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_OK)
+ // g.requestMtu(512) // 触发 MTU 修改流程
}
newState == BluetoothProfile.STATE_DISCONNECTED -> {
updateConnectionState(BleConst.STATE_DISCONNECTED)
disconnectGatt()
- otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_CONNECTING)
- g.close()
- otaManager?.release();
+ // otaManager?.onBtDeviceConnection(g.device, StateCode.CONNECTION_CONNECTING)
+ // g.close()
+ // otaManager?.release();
}
else -> {
diff --git a/local_plugins/chat_storage/ios/Classes/.gitkeep b/local_plugins/chat_storage/ios/Classes/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
diff --git a/local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift b/local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift
index d0702e65c..e008b2e78 100644
--- a/local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift
+++ b/local_plugins/chat_storage/ios/Classes/ChatStorageHelper.swift
@@ -1,410 +1,237 @@
import Foundation
import SQLite3
-/// ChatStorageHelper - iOS版本的聊天存储助手
-/// 负责SQLite数据库操作和业务逻辑
+/**
+ * ChatStorageHelper - 聊天数据存储助手
+ * 负责处理SQLite数据库操作,提供消息存储和检索功能
+ */
class ChatStorageHelper {
- // MARK: - Constants
- private let TAG = "ChatStorageHelper"
-
- // 数据库常量
- private let DATABASE_NAME = "chat_messages.db"
- private let DATABASE_VERSION = 1
-
- // 表和列名
- private let TABLE_MESSAGES = "messages"
- private let COLUMN_ID = "id"
- private let COLUMN_SESSION_ID = "session_id"
- private let COLUMN_MESSAGE = "message"
- private let COLUMN_SENDER = "sender"
- private let COLUMN_TIMESTAMP = "timestamp"
- private let COLUMN_METADATA = "metadata"
-
- // MARK: - Properties
private var db: OpaquePointer?
private let dbPath: String
- // MARK: - Initialization
- init(_ context: Any) {
- // 获取数据库文件路径
- let fileManager = FileManager.default
- if let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first {
- let databaseDirectory = documentsDirectory.appendingPathComponent("Databases")
-
- // 确保目录存在
- try? fileManager.createDirectory(at: databaseDirectory, withIntermediateDirectories: true)
-
- dbPath = databaseDirectory.appendingPathComponent(DATABASE_NAME).path
-
- // 打开数据库连接
- openDatabase()
- } else {
- dbPath = ""
- print("\(TAG) 无法获取文档目录路径")
- }
- }
-
- deinit {
- closeDatabase()
- }
-
- // MARK: - Database Operations
-
- /// 打开数据库连接
- private func openDatabase() {
- if sqlite3_open(dbPath, &db) == SQLITE_OK {
- createTables()
- print("\(TAG) 数据库已成功打开")
- } else {
- print("\(TAG) 无法打开数据库: \(String(describing: errorMessage))")
- }
- }
-
- /// 关闭数据库连接
- private func closeDatabase() {
- if db != nil {
- sqlite3_close(db)
- db = nil
+ init() {
+ // 获取文档目录路径
+ let fileURL = try! FileManager.default
+ .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
+ .appendingPathComponent("chat_storage.db")
+
+ dbPath = fileURL.path
+
+ // 打开数据库
+ if sqlite3_open(dbPath, &db) != SQLITE_OK {
+ print("无法打开数据库")
+ return
}
- }
-
- /// 创建必要的数据库表
- private func createTables() {
+
// 创建消息表
- let createTableSQL = """
- CREATE TABLE IF NOT EXISTS \(TABLE_MESSAGES) (
- \(COLUMN_ID) INTEGER PRIMARY KEY AUTOINCREMENT,
- \(COLUMN_SESSION_ID) TEXT NOT NULL,
- \(COLUMN_MESSAGE) TEXT NOT NULL,
- \(COLUMN_SENDER) TEXT NOT NULL,
- \(COLUMN_TIMESTAMP) TEXT NOT NULL,
- \(COLUMN_METADATA) TEXT
- );
+ let createTableString = """
+ CREATE TABLE IF NOT EXISTS messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ session_id TEXT NOT NULL,
+ message TEXT NOT NULL,
+ sender TEXT NOT NULL,
+ metadata TEXT,
+ created_at INTEGER NOT NULL
+ );
+ CREATE INDEX IF NOT EXISTS idx_session_id ON messages (session_id);
+ CREATE INDEX IF NOT EXISTS idx_created_at ON messages (created_at);
"""
- if executeUpdate(sql: createTableSQL) {
- // 创建索引以加快查询速度
- executeUpdate(sql: "CREATE INDEX IF NOT EXISTS idx_session_id ON \(TABLE_MESSAGES) (\(COLUMN_SESSION_ID))")
- executeUpdate(sql: "CREATE INDEX IF NOT EXISTS idx_timestamp ON \(TABLE_MESSAGES) (\(COLUMN_TIMESTAMP))")
- }
- }
-
- /// 获取SQLite错误消息
- private var errorMessage: String {
- if let errorPointer = sqlite3_errmsg(db) {
- let errorMessage = String(cString: errorPointer)
- return errorMessage
- } else {
- return "未知错误"
+ if sqlite3_exec(db, createTableString, nil, nil, nil) != SQLITE_OK {
+ let errmsg = String(cString: sqlite3_errmsg(db)!)
+ print("创建表失败: \(errmsg)")
}
}
- /// 执行SQL更新操作(INSERT, UPDATE, DELETE, CREATE等)
- private func executeUpdate(sql: String, parameters: [Any]? = nil) -> Bool {
- var statement: OpaquePointer?
-
- if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
- // 绑定参数
- if let params = parameters {
- for (index, param) in params.enumerated() {
- bindParameter(statement: statement, index: index + 1, value: param)
- }
- }
-
- // 执行语句
- let result = sqlite3_step(statement)
- sqlite3_finalize(statement)
-
- return result == SQLITE_DONE
- } else {
- print("\(TAG) 准备SQL语句失败: \(errorMessage)")
- return false
- }
- }
-
- /// 绑定SQL参数
- private func bindParameter(statement: OpaquePointer?, index: Int, value: Any) {
- if let value = value as? Int {
- sqlite3_bind_int(statement, Int32(index), Int32(value))
- } else if let value = value as? Double {
- sqlite3_bind_double(statement, Int32(index), value)
- } else if let value = value as? String {
- sqlite3_bind_text(statement, Int32(index), (value as NSString).utf8String, -1, nil)
- } else if value is NSNull {
- sqlite3_bind_null(statement, Int32(index))
- }
- }
-
- /// 查询数据并处理结果
- private func executeQuery(sql: String, parameters: [Any]? = nil, handler: (OpaquePointer) -> Void) {
- var statement: OpaquePointer?
-
- if sqlite3_prepare_v2(db, sql, -1, &statement, nil) == SQLITE_OK {
- // 绑定参数
- if let params = parameters {
- for (index, param) in params.enumerated() {
- bindParameter(statement: statement, index: index + 1, value: param)
- }
- }
-
- // 处理结果
- while sqlite3_step(statement) == SQLITE_ROW {
- handler(statement!)
- }
- } else {
- print("\(TAG) 执行查询失败: \(errorMessage)")
+ deinit {
+ if db != nil {
+ sqlite3_close(db)
}
-
- sqlite3_finalize(statement)
}
- // MARK: - Public Methods
-
- /// 保存消息
- /// - Parameters:
- /// - sessionId: 会话ID
- /// - message: 消息内容
- /// - sender: 发送者
- /// - metadata: 元数据
- /// - Returns: 成功返回消息ID,失败返回-1
- func saveMessage(sessionId: String, message: String, sender: String, metadata: String? = nil) -> Int64 {
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
- let timestamp = dateFormatter.string(from: Date())
-
- let sql = """
- INSERT INTO \(TABLE_MESSAGES) (\(COLUMN_SESSION_ID), \(COLUMN_MESSAGE), \(COLUMN_SENDER), \(COLUMN_TIMESTAMP), \(COLUMN_METADATA))
- VALUES (?, ?, ?, ?, ?)
- """
-
- var parameters: [Any] = [sessionId, message, sender, timestamp]
- if let metadata = metadata {
- parameters.append(metadata)
+ /**
+ * 保存消息到数据库
+ * @param sessionId 会话ID
+ * @param message 消息内容
+ * @param sender 发送者("user" 或 "agent")
+ * @param metadata 可选的元数据
+ * @return 插入的消息ID,失败则返回-1
+ */
+ func saveMessage(sessionId: String, message: String, sender: String, metadata: String?) -> Int64 {
+ let insertStatementString = "INSERT INTO messages (session_id, message, sender, metadata, created_at) VALUES (?, ?, ?, ?, ?);"
+ var insertStatement: OpaquePointer?
+
+ if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
+ sqlite3_bind_text(insertStatement, 1, (sessionId as NSString).utf8String, -1, nil)
+ sqlite3_bind_text(insertStatement, 2, (message as NSString).utf8String, -1, nil)
+ sqlite3_bind_text(insertStatement, 3, (sender as NSString).utf8String, -1, nil)
+
+ if let metadata = metadata {
+ sqlite3_bind_text(insertStatement, 4, (metadata as NSString).utf8String, -1, nil)
+ } else {
+ sqlite3_bind_null(insertStatement, 4)
+ }
+
+ let currentTime = Int(Date().timeIntervalSince1970)
+ sqlite3_bind_int(insertStatement, 5, Int32(currentTime))
+
+ if sqlite3_step(insertStatement) == SQLITE_DONE {
+ let id = sqlite3_last_insert_rowid(db)
+ sqlite3_finalize(insertStatement)
+ return id
+ } else {
+ print("插入消息失败: \(String(cString: sqlite3_errmsg(db)!))")
+ }
+
+ sqlite3_finalize(insertStatement)
} else {
- parameters.append(NSNull())
+ print("插入消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
}
- if executeUpdate(sql: sql, parameters: parameters) {
- return sqlite3_last_insert_rowid(db)
- } else {
- print("\(TAG) 保存消息失败")
- return -1
- }
+ return -1
}
- /// 获取指定会话的消息
- /// - Parameters:
- /// - sessionId: 会话ID
- /// - page: 页码
- /// - pageSize: 每页大小
- /// - Returns: JSON字符串,包含消息列表和分页信息
- func getMessages(sessionId: String, page: Int = 1, pageSize: Int = 20) -> [String: Any] {
- var messages: [[String: Any]] = []
+ /**
+ * 获取特定会话的消息,支持分页
+ * @param sessionId 会话ID
+ * @param page 页码(从1开始)
+ * @param pageSize 每页消息数量
+ * @return 消息列表的JSON字符串
+ */
+ func getMessages(sessionId: String, page: Int, pageSize: Int) -> String {
let offset = (page - 1) * pageSize
-
- // 查询消息
- let sql = """
- SELECT * FROM \(TABLE_MESSAGES)
- WHERE \(COLUMN_SESSION_ID) = ?
- ORDER BY \(COLUMN_TIMESTAMP) DESC
- LIMIT ? OFFSET ?
+ var messagesArray: [[String: Any]] = []
+
+ let queryString = """
+ SELECT id, message, sender, metadata, created_at
+ FROM messages
+ WHERE session_id = ?
+ ORDER BY created_at DESC
+ LIMIT ? OFFSET ?
"""
- executeQuery(sql: sql, parameters: [sessionId, pageSize, offset]) { statement in
- var message: [String: Any] = [:]
-
- // 获取ID
- message["id"] = Int(sqlite3_column_int(statement, 0))
-
- // 获取会话ID
- if let sessionId = sqlite3_column_text(statement, 1) {
- message["sessionId"] = String(cString: sessionId)
- }
-
- // 获取消息内容
- if let messageText = sqlite3_column_text(statement, 2) {
- message["message"] = String(cString: messageText)
- }
+ var queryStatement: OpaquePointer?
+
+ if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
+ sqlite3_bind_text(queryStatement, 1, (sessionId as NSString).utf8String, -1, nil)
+ sqlite3_bind_int(queryStatement, 2, Int32(pageSize))
+ sqlite3_bind_int(queryStatement, 3, Int32(offset))
- // 获取发送者
- if let sender = sqlite3_column_text(statement, 3) {
- message["sender"] = String(cString: sender)
+ while sqlite3_step(queryStatement) == SQLITE_ROW {
+ let id = sqlite3_column_int(queryStatement, 0)
+
+ let messagePtr = sqlite3_column_text(queryStatement, 1)
+ let message = messagePtr != nil ? String(cString: messagePtr!) : ""
+
+ let senderPtr = sqlite3_column_text(queryStatement, 2)
+ let sender = senderPtr != nil ? String(cString: senderPtr!) : ""
+
+ let metadataPtr = sqlite3_column_text(queryStatement, 3)
+ let metadata = metadataPtr != nil ? String(cString: metadataPtr!) : nil
+
+ let createdAt = sqlite3_column_int(queryStatement, 4)
+
+ var messageDict: [String: Any] = [
+ "id": id,
+ "message": message,
+ "sender": sender,
+ "created_at": createdAt
+ ]
+
+ if let metadata = metadata {
+ messageDict["metadata"] = metadata
+ }
+
+ messagesArray.append(messageDict)
}
- // 获取时间戳
- if let timestamp = sqlite3_column_text(statement, 4) {
- message["timestamp"] = String(cString: timestamp)
- }
+ sqlite3_finalize(queryStatement)
- // 获取元数据(如果有)
- if sqlite3_column_type(statement, 5) != SQLITE_NULL {
- if let metadata = sqlite3_column_text(statement, 5) {
- message["metadata"] = String(cString: metadata)
+ do {
+ let jsonData = try JSONSerialization.data(withJSONObject: messagesArray, options: [])
+ if let jsonString = String(data: jsonData, encoding: .utf8) {
+ return jsonString
}
+ } catch {
+ print("转换消息为JSON失败: \(error.localizedDescription)")
}
-
- messages.append(message)
- }
-
- // 获取总记录数
- var totalCount = 0
- let countSql = "SELECT COUNT(*) FROM \(TABLE_MESSAGES) WHERE \(COLUMN_SESSION_ID) = ?"
-
- executeQuery(sql: countSql, parameters: [sessionId]) { statement in
- totalCount = Int(sqlite3_column_int(statement, 0))
+ } else {
+ print("查询消息失败: \(String(cString: sqlite3_errmsg(db)!))")
}
- // 计算总页数
- let totalPages = (totalCount + pageSize - 1) / pageSize
-
- return [
- "messages": messages,
- "page": page,
- "pageSize": pageSize,
- "totalCount": totalCount,
- "totalPages": totalPages
- ]
+ return "[]"
}
- /// 获取会话列表
- /// - Returns: 会话列表数据
- func getSessions() -> [[String: Any]] {
- var sessions: [[String: Any]] = []
-
- // 查询不同的会话ID及其最新消息
- let sql = """
- SELECT s1.\(COLUMN_SESSION_ID), s1.\(COLUMN_MESSAGE), s1.\(COLUMN_SENDER), s1.\(COLUMN_TIMESTAMP)
- FROM \(TABLE_MESSAGES) s1
- INNER JOIN (
- SELECT \(COLUMN_SESSION_ID), MAX(\(COLUMN_TIMESTAMP)) as max_timestamp
- FROM \(TABLE_MESSAGES)
- GROUP BY \(COLUMN_SESSION_ID)
- ) s2 ON s1.\(COLUMN_SESSION_ID) = s2.\(COLUMN_SESSION_ID) AND s1.\(COLUMN_TIMESTAMP) = s2.max_timestamp
- ORDER BY s1.\(COLUMN_TIMESTAMP) DESC
- """
-
- executeQuery(sql: sql) { statement in
- var session: [String: Any] = [:]
-
- // 获取会话ID
- if let sessionId = sqlite3_column_text(statement, 0) {
- let sessionIdStr = String(cString: sessionId)
- session["sessionId"] = sessionIdStr
+
+
+ /**
+ * 删除消息
+ * @param sessionId 可选的会话ID,指定则删除该会话所有消息
+ * @param messageIds 可选的消息ID列表,指定则删除特定消息
+ * @return 是否删除成功
+ */
+ func deleteMessages(sessionId: String?, messageIds: [Int]?) -> Bool {
+ if let sessionId = sessionId {
+ let deleteString = "DELETE FROM messages WHERE session_id = ?;"
+ var deleteStatement: OpaquePointer?
+
+ if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
+ sqlite3_bind_text(deleteStatement, 1, (sessionId as NSString).utf8String, -1, nil)
- // 获取该会话的消息数量
- var messageCount = 0
- let countSql = "SELECT COUNT(*) FROM \(TABLE_MESSAGES) WHERE \(COLUMN_SESSION_ID) = ?"
+ if sqlite3_step(deleteStatement) == SQLITE_DONE {
+ sqlite3_finalize(deleteStatement)
+ return true
+ } else {
+ print("删除会话消息失败: \(String(cString: sqlite3_errmsg(db)!))")
+ }
- executeQuery(sql: countSql, parameters: [sessionIdStr]) { countStmt in
- messageCount = Int(sqlite3_column_int(countStmt, 0))
+ sqlite3_finalize(deleteStatement)
+ } else {
+ print("删除会话消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
+ }
+ } else if let messageIds = messageIds, !messageIds.isEmpty {
+ // 构建参数占位符
+ let placeholders = String(repeating: "?,", count: messageIds.count).dropLast()
+ let deleteString = "DELETE FROM messages WHERE id IN (\(placeholders));"
+ var deleteStatement: OpaquePointer?
+
+ if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
+ for (index, id) in messageIds.enumerated() {
+ sqlite3_bind_int(deleteStatement, Int32(index + 1), Int32(id))
}
- session["messageCount"] = messageCount
- }
-
- // 获取最后一条消息
- if let message = sqlite3_column_text(statement, 1) {
- session["lastMessage"] = String(cString: message)
- }
-
- // 获取最后发送者
- if let sender = sqlite3_column_text(statement, 2) {
- session["lastSender"] = String(cString: sender)
- }
-
- // 获取时间戳
- if let timestamp = sqlite3_column_text(statement, 3) {
- session["timestamp"] = String(cString: timestamp)
+ if sqlite3_step(deleteStatement) == SQLITE_DONE {
+ sqlite3_finalize(deleteStatement)
+ return true
+ } else {
+ print("删除特定消息失败: \(String(cString: sqlite3_errmsg(db)!))")
+ }
+
+ sqlite3_finalize(deleteStatement)
+ } else {
+ print("删除特定消息语句准备失败: \(String(cString: sqlite3_errmsg(db)!))")
}
-
- sessions.append(session)
- }
-
- return sessions
- }
-
- /// 删除消息
- /// - Parameters:
- /// - sessionId: 会话ID,如果提供则删除整个会话
- /// - messageIds: 消息ID列表,如果提供则删除指定消息
- /// - Returns: 成功返回true,失败返回false
- func deleteMessages(sessionId: String? = nil, messageIds: [Int]? = nil) -> Bool {
- if let messageIds = messageIds, !messageIds.isEmpty {
- // 删除指定ID的消息
- let placeholders = Array(repeating: "?", count: messageIds.count).joined(separator: ",")
- let sql = "DELETE FROM \(TABLE_MESSAGES) WHERE \(COLUMN_ID) IN (\(placeholders))"
-
- return executeUpdate(sql: sql, parameters: messageIds.map { $0 })
- } else if let sessionId = sessionId, !sessionId.isEmpty {
- // 删除会话中的所有消息
- let sql = "DELETE FROM \(TABLE_MESSAGES) WHERE \(COLUMN_SESSION_ID) = ?"
- return executeUpdate(sql: sql, parameters: [sessionId])
+ } else {
+ return false
}
return false
}
- /// 清空数据库
- /// - Returns: 成功返回true,失败返回false
+ /**
+ * 清空数据库
+ * @return 是否清空成功
+ */
func clearDatabase() -> Bool {
- let sql = "DELETE FROM \(TABLE_MESSAGES)"
- return executeUpdate(sql: sql)
- }
-
- /// 获取指定会话的最近消息
- /// - Parameters:
- /// - sessionId: 会话ID
- /// - limit: 限制数量
- /// - Returns: 消息列表,按时间正序排列
- func getRecentMessages(sessionId: String, limit: Int) -> [[String: Any]] {
- var messages: [[String: Any]] = []
-
- // 查询语句 - 按时间倒序获取最近的N条,然后在结果中再按时间正序
- let sql = """
- SELECT * FROM (
- SELECT * FROM \(TABLE_MESSAGES)
- WHERE \(COLUMN_SESSION_ID) = ?
- ORDER BY \(COLUMN_TIMESTAMP) DESC
- LIMIT ?
- ) tmp ORDER BY \(COLUMN_TIMESTAMP) ASC
- """
+ let deleteString = "DELETE FROM messages;"
- executeQuery(sql: sql, parameters: [sessionId, limit]) { statement in
- var message: [String: Any] = [:]
-
- // 获取ID
- message["id"] = Int(sqlite3_column_int(statement, 0))
-
- // 获取会话ID
- if let sessionId = sqlite3_column_text(statement, 1) {
- message["sessionId"] = String(cString: sessionId)
- }
-
- // 获取消息内容
- if let messageText = sqlite3_column_text(statement, 2) {
- message["message"] = String(cString: messageText)
- }
-
- // 获取发送者
- if let sender = sqlite3_column_text(statement, 3) {
- message["sender"] = String(cString: sender)
- }
-
- // 获取时间戳
- if let timestamp = sqlite3_column_text(statement, 4) {
- message["timestamp"] = String(cString: timestamp)
- }
-
- // 获取元数据(如果有)
- if sqlite3_column_type(statement, 5) != SQLITE_NULL {
- if let metadata = sqlite3_column_text(statement, 5) {
- message["metadata"] = String(cString: metadata)
- }
- }
-
- messages.append(message)
+ if sqlite3_exec(db, deleteString, nil, nil, nil) == SQLITE_OK {
+ return true
+ } else {
+ print("清空数据库失败: \(String(cString: sqlite3_errmsg(db)!))")
+ return false
}
-
- return messages
}
+
+
}
\ No newline at end of file
diff --git a/local_plugins/chat_storage/ios/Classes/ChatStoragePlugin.swift b/local_plugins/chat_storage/ios/Classes/ChatStoragePlugin.swift
deleted file mode 100644
index 031cb370f..000000000
--- a/local_plugins/chat_storage/ios/Classes/ChatStoragePlugin.swift
+++ /dev/null
@@ -1,182 +0,0 @@
-import Flutter
-import UIKit
-
-public class ChatStoragePlugin: NSObject, FlutterPlugin {
- private let TAG = "ChatStoragePlugin"
- private var chatStorageHelper: ChatStorageHelper?
-
- public static func register(with registrar: FlutterPluginRegistrar) {
- let channel = FlutterMethodChannel(name: "chat_storage", binaryMessenger: registrar.messenger())
- let instance = ChatStoragePlugin()
- registrar.addMethodCallDelegate(instance, channel: channel)
- }
-
- override init() {
- super.init()
- chatStorageHelper = ChatStorageHelper(())
- print("\(TAG) 聊天存储插件已加载")
- }
-
- public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
- guard let helper = chatStorageHelper else {
- result(FlutterError(code: "NOT_INITIALIZED", message: "存储助手未初始化", details: nil))
- return
- }
-
- switch call.method {
- case "saveMessage":
- handleSaveMessage(call, result, helper)
-
- case "getMessages":
- handleGetMessages(call, result, helper)
-
- case "getSessions":
- handleGetSessions(call, result, helper)
-
- case "deleteMessages":
- handleDeleteMessages(call, result, helper)
-
- case "clearDatabase":
- handleClearDatabase(call, result, helper)
-
- case "getRecentMessages":
- handleGetRecentMessages(call, result, helper)
-
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
- // MARK: - Method handlers
-
- private func handleSaveMessage(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- guard let arguments = call.arguments as? [String: Any] else {
- result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
- return
- }
-
- guard let sessionId = arguments["sessionId"] as? String,
- let message = arguments["message"] as? String,
- let sender = arguments["sender"] as? String else {
- result(FlutterError(code: "MISSING_ARGS", message: "缺少必要参数", details: nil))
- return
- }
-
- let metadata = arguments["metadata"] as? String
-
- do {
- let id = helper.saveMessage(sessionId: sessionId, message: message, sender: sender, metadata: metadata)
-
- if id != -1 {
- result(["success": true, "id": id])
- } else {
- result(FlutterError(code: "DB_ERROR", message: "插入消息失败", details: nil))
- }
- } catch {
- print("\(TAG) 保存消息时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "保存消息时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-
- private func handleGetMessages(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- guard let arguments = call.arguments as? [String: Any] else {
- result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
- return
- }
-
- guard let sessionId = arguments["sessionId"] as? String else {
- result(FlutterError(code: "MISSING_ARGS", message: "缺少会话ID", details: nil))
- return
- }
-
- let page = arguments["page"] as? Int ?? 1
- let pageSize = arguments["pageSize"] as? Int ?? 20
-
- do {
- let messagesData = helper.getMessages(sessionId: sessionId, page: page, pageSize: pageSize)
-
- // 将结果转换为JSON字符串
- if let jsonData = try? JSONSerialization.data(withJSONObject: messagesData),
- let jsonString = String(data: jsonData, encoding: .utf8) {
- result(jsonString)
- } else {
- result(FlutterError(code: "JSON_ERROR", message: "无法序列化结果", details: nil))
- }
- } catch {
- print("\(TAG) 获取消息时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "获取消息时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-
- private func handleGetSessions(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- do {
- let sessions = helper.getSessions()
-
- // 将结果转换为JSON字符串
- if let jsonData = try? JSONSerialization.data(withJSONObject: sessions),
- let jsonString = String(data: jsonData, encoding: .utf8) {
- result(jsonString)
- } else {
- result(FlutterError(code: "JSON_ERROR", message: "无法序列化结果", details: nil))
- }
- } catch {
- print("\(TAG) 获取会话列表时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "获取会话列表时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-
- private func handleDeleteMessages(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- guard let arguments = call.arguments as? [String: Any] else {
- result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
- return
- }
-
- let sessionId = arguments["sessionId"] as? String
- let messageIds = arguments["messageIds"] as? [Int]
-
- do {
- let success = helper.deleteMessages(sessionId: sessionId, messageIds: messageIds)
-
- if success {
- result(["success": true])
- } else {
- result(FlutterError(code: "INVALID_ARGS", message: "必须提供sessionId或messageIds", details: nil))
- }
- } catch {
- print("\(TAG) 删除消息时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "删除消息时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-
- private func handleClearDatabase(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- do {
- let success = helper.clearDatabase()
- result(["success": success])
- } catch {
- print("\(TAG) 清空数据库时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "清空数据库时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-
- private func handleGetRecentMessages(_ call: FlutterMethodCall, _ result: @escaping FlutterResult, _ helper: ChatStorageHelper) {
- guard let arguments = call.arguments as? [String: Any] else {
- result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
- return
- }
-
- guard let sessionId = arguments["sessionId"] as? String else {
- result(FlutterError(code: "MISSING_ARGS", message: "缺少会话ID", details: nil))
- return
- }
-
- let limit = arguments["limit"] as? Int ?? 10
-
- do {
- let messages = helper.getRecentMessages(sessionId: sessionId, limit: limit)
- result(messages)
- } catch {
- print("\(TAG) 获取最近消息时发生错误: \(error.localizedDescription)")
- result(FlutterError(code: "EXCEPTION", message: "获取最近消息时发生错误: \(error.localizedDescription)", details: nil))
- }
- }
-}
\ No newline at end of file
diff --git a/local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift b/local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift
index bd72a61bc..daf865444 100644
--- a/local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift
+++ b/local_plugins/chat_storage/ios/Classes/SwiftChatStoragePlugin.swift
@@ -1,11 +1,119 @@
import Flutter
import UIKit
-// 这个类是为了满足Flutter插件的命名约定
-// 实际实现在ChatStoragePlugin.swift中
+/**
+ * SwiftChatStoragePlugin - Flutter插件接口
+ * 负责处理Flutter端的方法调用,并委托给ChatStorageHelper
+ */
public class SwiftChatStoragePlugin: NSObject, FlutterPlugin {
- public static func register(with registrar: FlutterPluginRegistrar) {
- // 调用实际实现类的注册方法
- ChatStoragePlugin.register(with: registrar)
- }
+ private let TAG = "ChatStoragePlugin"
+ private var chatStorageHelper: ChatStorageHelper!
+
+ public static func register(with registrar: FlutterPluginRegistrar) {
+ let channel = FlutterMethodChannel(name: "chat_storage", binaryMessenger: registrar.messenger())
+ let instance = SwiftChatStoragePlugin()
+ registrar.addMethodCallDelegate(instance, channel: channel)
+
+ print("\(instance.TAG): 聊天存储插件已加载")
+ }
+
+ override init() {
+ super.init()
+ chatStorageHelper = ChatStorageHelper()
+ }
+
+ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
+ switch call.method {
+ case "saveMessage":
+ do {
+ guard let args = call.arguments as? [String: Any],
+ let sessionId = args["sessionId"] as? String,
+ let message = args["message"] as? String,
+ let sender = args["sender"] as? String else {
+ result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
+ return
+ }
+
+ let metadata = args["metadata"] as? String
+
+ let id = chatStorageHelper.saveMessage(
+ sessionId: sessionId,
+ message: message,
+ sender: sender,
+ metadata: metadata
+ )
+
+ if id != -1 {
+ result(["success": true, "id": id])
+ } else {
+ result(FlutterError(code: "DB_ERROR", message: "插入消息失败", details: nil))
+ }
+ } catch {
+ print("\(TAG): 保存消息时发生错误 \(error.localizedDescription)")
+ result(FlutterError(code: "EXCEPTION", message: "保存消息时发生错误: \(error.localizedDescription)", details: nil))
+ }
+
+ case "getMessages":
+ do {
+ guard let args = call.arguments as? [String: Any],
+ let sessionId = args["sessionId"] as? String else {
+ result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
+ return
+ }
+
+ let page = args["page"] as? Int ?? 1
+ let pageSize = args["pageSize"] as? Int ?? 20
+
+ let messagesJson = chatStorageHelper.getMessages(
+ sessionId: sessionId,
+ page: page,
+ pageSize: pageSize
+ )
+
+ result(messagesJson)
+ } catch {
+ print("\(TAG): 获取消息时发生错误 \(error.localizedDescription)")
+ result(FlutterError(code: "EXCEPTION", message: "获取消息时发生错误: \(error.localizedDescription)", details: nil))
+ }
+
+
+ case "deleteMessages":
+ do {
+ guard let args = call.arguments as? [String: Any] else {
+ result(FlutterError(code: "INVALID_ARGS", message: "参数无效", details: nil))
+ return
+ }
+
+ let sessionId = args["sessionId"] as? String
+ let messageIds = args["messageIds"] as? [Int]
+
+ let success = chatStorageHelper.deleteMessages(
+ sessionId: sessionId,
+ messageIds: messageIds
+ )
+
+ if success {
+ result(["success": true])
+ } else {
+ result(FlutterError(code: "INVALID_ARGS", message: "必须提供sessionId或messageIds", details: nil))
+ }
+ } catch {
+ print("\(TAG): 删除消息时发生错误 \(error.localizedDescription)")
+ result(FlutterError(code: "EXCEPTION", message: "删除消息时发生错误: \(error.localizedDescription)", details: nil))
+ }
+
+ case "clearDatabase":
+ do {
+ let success = chatStorageHelper.clearDatabase()
+ result(["success": success])
+ } catch {
+ print("\(TAG): 清空数据库时发生错误 \(error.localizedDescription)")
+ result(FlutterError(code: "EXCEPTION", message: "清空数据库时发生错误: \(error.localizedDescription)", details: nil))
+ }
+
+
+ default:
+ result(FlutterMethodNotImplemented)
+ }
+ }
}
\ No newline at end of file
diff --git a/local_plugins/chat_storage/ios/README.md b/local_plugins/chat_storage/ios/README.md
index 4bf35eb37..3d685d41e 100644
--- a/local_plugins/chat_storage/ios/README.md
+++ b/local_plugins/chat_storage/ios/README.md
@@ -1,64 +1,47 @@
# Chat Storage 插件 - iOS 实现
-## 概述
+这是 Chat Storage 插件的 iOS 实现,用于本地存储聊天消息。
-Chat Storage 插件的 iOS 实现,为 Flutter 应用提供本地 SQLite 数据库支持,用于存储和管理聊天记录。
+## 功能
-## 功能特性
+- 保存消息到本地 SQLite 数据库
+- 分页获取会话消息
+- 获取会话列表
+- 删除指定会话或消息
+- 清空数据库
+- 获取最近消息
-- 基于 SQLite 的聊天记录存储
-- 支持按会话分组消息
-- 提供分页查询功能
-- 支持元数据存储
-- 完整的增删改查 API
+## 实现细节
-## 技术实现
+- 使用 Swift 语言编写
+- 使用原生 SQLite3 API 进行数据库操作
+- 数据库文件位置:应用文档目录下的 `chat_storage.db`
+- 表结构:
+ ```sql
+ CREATE TABLE messages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ session_id TEXT NOT NULL,
+ message TEXT NOT NULL,
+ sender TEXT NOT NULL,
+ metadata TEXT,
+ created_at INTEGER NOT NULL
+ );
+ ```
-本插件使用原生 Swift 实现,通过 SQLite C API 直接操作数据库,不依赖第三方 SQLite 封装库,以保持最小的依赖要求和最佳性能。
+## 构建要求
-### 核心组件
+- iOS 12.0+
+- Xcode 14.0+
+- Swift 5.0+
-- `ChatStorageHelper`: 负责 SQLite 数据库操作,提供完整的数据操作 API
-- `ChatStoragePlugin`: 插件主类,处理与 Flutter 的通信
+## 安装
-### 数据库结构
+该插件已包含在 Flutter 应用的依赖中,不需要单独安装。
-数据库包含一个 `messages` 表,结构如下:
+## 开发者
-```sql
-CREATE TABLE messages (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- session_id TEXT NOT NULL,
- message TEXT NOT NULL,
- sender TEXT NOT NULL,
- timestamp TEXT NOT NULL,
- metadata TEXT
-);
+若需修改插件代码,请确保:
--- 索引
-CREATE INDEX idx_session_id ON messages (session_id);
-CREATE INDEX idx_timestamp ON messages (timestamp);
-```
-
-## 使用注意事项
-
-### SQLite 多线程访问
-
-本插件所有数据库操作均在主线程执行,以避免 SQLite 多线程访问的复杂性。在处理大量数据时,建议在 Flutter 层面使用 `compute` 函数或 `Isolate` 进行异步处理。
-
-### 数据格式
-
-- 时间戳使用 `yyyy-MM-dd HH:mm:ss` 格式的字符串
-- JSON 转换遵循 Swift/Objective-C 的类型映射规则
-
-## 调试与故障排除
-
-- 日志前缀为 `ChatStoragePlugin` 和 `ChatStorageHelper`
-- 可通过 Console.app 查看日志输出
-- 数据库文件存储在应用的 Documents/Databases 目录下
-
-## 开发者须知
-
-如果需要扩展此插件的功能,比如添加新的表或字段,应同时更新 Android 和 iOS 端的实现,并确保在版本升级时提供适当的数据库迁移策略。
-
-数据库迁移逻辑目前实现为重建表,在生产环境中应该改为保留用户数据的方式。
\ No newline at end of file
+1. 修改后运行 `flutter clean` 和 `flutter pub get` 重新加载插件
+2. 若修改了数据库结构,需要考虑数据迁移方案
+3. 如需测试,可以使用 `pod lib lint` 验证插件的 iOS 部分
\ No newline at end of file
diff --git a/local_plugins/chat_storage/ios/chat_storage.podspec b/local_plugins/chat_storage/ios/chat_storage.podspec
index c397034ec..58ce1adf1 100644
--- a/local_plugins/chat_storage/ios/chat_storage.podspec
+++ b/local_plugins/chat_storage/ios/chat_storage.podspec
@@ -5,22 +5,20 @@
Pod::Spec.new do |s|
s.name = 'chat_storage'
s.version = '0.0.1'
- s.summary = '聊天记录存储插件'
+ s.summary = '聊天消息本地存储插件'
s.description = <<-DESC
-用于存储聊天记录的Flutter插件,支持SQLite数据库操作。
+聊天消息本地存储插件,提供基于SQLite的消息存储和检索功能。
DESC
- s.homepage = 'http://yunqiinnovation.com'
+ s.homepage = 'https://yunqiinnovation.com'
s.license = { :file => '../LICENSE' }
- s.author = { 'YunQi Innovation' => 'support@yunqiinnovation.com' }
+ s.author = { '云奇创新' => 'info@yunqiinnovation.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
+ s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.platform = :ios, '12.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
-
- # 添加SQLite库依赖
- s.library = 'sqlite3'
end
\ No newline at end of file
diff --git a/local_plugins/chat_storage/lib/chat_storage.dart b/local_plugins/chat_storage/lib/chat_storage.dart
index 66e229b5f..7e474e97a 100644
--- a/local_plugins/chat_storage/lib/chat_storage.dart
+++ b/local_plugins/chat_storage/lib/chat_storage.dart
@@ -44,6 +44,8 @@ class ChatMessage {
}
}
+
+
/// 分页结果数据模型
class PagedResult {
final List messages;
@@ -123,6 +125,8 @@ class ChatStorage {
}
}
+
+
/// 删除聊天记录,可按会话ID或消息ID列表删除
static Future