diff --git a/.mcp copy.json b/.mcp copy.json
deleted file mode 100644
index eaa12d328..000000000
--- a/.mcp copy.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "mcpServers": {
- "web-search": {
- "url": "http://mcp.ideapsound.com:8000/sse"
- },
- "amap-amap-sse": {
- "url": "https://mcp.amap.com/sse?key=df7b0b6747c632fc38ddad22e7176646"
- }
- }
-}
\ No newline at end of file
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/GoogleService-Info.plist b/GoogleService-Info.plist
index c90ada3a3..590c85a5f 100644
--- a/GoogleService-Info.plist
+++ b/GoogleService-Info.plist
@@ -3,9 +3,9 @@
CLIENT_ID
- 907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com
+ 907754492821-tl2blr39g82re40sttkve36ovt6qcu6c.apps.googleusercontent.com
REVERSED_CLIENT_ID
- com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278
+ com.googleusercontent.apps.907754492821-tl2blr39g82re40sttkve36ovt6qcu6c
ANDROID_CLIENT_ID
907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com
API_KEY
@@ -15,7 +15,7 @@
PLIST_VERSION
1
BUNDLE_ID
- com.yunqiinnovation.deepsound
+ com.chuangxiangyunqi.deapsound
PROJECT_ID
deepsound-e0e3d
STORAGE_BUCKET
@@ -31,6 +31,6 @@
IS_SIGNIN_ENABLED
GOOGLE_APP_ID
- 1:907754492821:ios:246794fc44b23174145c9c
+ 1:907754492821:ios:e4b4f3b37e58a7d1145c9c
\ No newline at end of file
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 89bcfdef7..44c9dd0dc 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -120,28 +120,8 @@ android {
}
dependencies {
-
- implementation("io.modelcontextprotocol:kotlin-sdk:0.4.0")
-
-
- // 添加本地插件模块依赖
- implementation(project(":spotify_sdk"))
- implementation(project(":deepvoice_utils"))
- implementation(project(":ble_service"))
- // 添加OkHttp依赖
- implementation("com.squareup.okhttp3:okhttp:4.9.3")
-
// 添加核心库反糖化
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
-
-
- // 添加androidx.media依赖
- implementation("androidx.media:media:1.6.0")
-
- // 添加legacy-support-v4依赖
- implementation("androidx.legacy:legacy-support-v4:1.0.0")
-
-
}
flutter {
diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
index bf9d92455..e7b34ac65 100644
--- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
@@ -12,7 +12,7 @@ import io.flutter.embedding.android.FlutterActivity
import android.content.Context
import android.os.Handler
import android.os.Looper
-import com.yunqiinnovation.deepvoice_utils.FileLogger
+import android.util.Log
class MainActivity: FlutterActivity() {
private val TAG = "MainActivity"
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 533e00619..412f86b15 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -35,7 +35,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24")
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
// 其他依赖...
}
}
diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts
index d350a0017..3dd7256fc 100644
--- a/android/settings.gradle.kts
+++ b/android/settings.gradle.kts
@@ -37,9 +37,9 @@ include(":chat_storage")
include(":jieli")
include(":spotify_sdk")
include(":ble_service")
-include(":deepvoice_utils")
include(":classic_bluetooth")
include(":agent_service")
+include(":speech")
// 设置azure_speech项目的路径
project(":azure_speech").projectDir = file("../local_plugins/azure_speech/android")
project(":open_ai_service").projectDir = file("../local_plugins/open_ai_service/android")
@@ -47,7 +47,6 @@ project(":chat_storage").projectDir = file("../local_plugins/chat_storage/androi
project(":jieli").projectDir = file("../local_plugins/jieli/android")
project(":spotify_sdk").projectDir = file("../local_plugins/spotify_sdk/android")
project(":ble_service").projectDir = file("../local_plugins/ble_service/android")
-project(":deepvoice_utils").projectDir = file("../local_plugins/deepvoice_utils/android")
project(":classic_bluetooth").projectDir = file("../local_plugins/classic_bluetooth/android")
project(":agent_service").projectDir = file("../local_plugins/agent_service/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..a2084703d 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
-# Uncomment this line to define a global platform for your project
-platform :ios, '13.0'
+# Define a global platform for your project
+platform :ios, '18.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@@ -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..336d0ad28 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -11,8 +11,45 @@ PODS:
- Flutter
- audio_waveforms (0.0.1):
- Flutter
+ - ble_service (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 +58,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 +126,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 +155,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 +179,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 +214,30 @@ 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`)
+ - ble_service (from `.symlinks/plugins/ble_service/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 +249,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 +288,13 @@ SPEC REPOS:
- GTMSessionFetcher
- libwebp
- Mantle
- - MicrosoftCognitiveServicesSpeech-iOS
+ - QCloudCore
+ - QCloudCOSXML
+ - QCloudTrack
- RecaptchaInterop
- SDWebImage
- SDWebImageWebPCoder
+ - SwiftyGif
- Try
- WechatOpenSDK-XCFramework
@@ -222,8 +305,16 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/audio_session/ios"
audio_waveforms:
:path: ".symlinks/plugins/audio_waveforms/ios"
+ ble_service:
+ :path: ".symlinks/plugins/ble_service/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 +337,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 +361,40 @@ 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
+ ble_service: 068ebe51800424f980282b16cbf757574eec4e08
+ 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 +405,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: f62382080fcf32f2d4640dc419aa07d69eff0328
COCOAPODS: 1.16.2
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index b46ef17e1..70369f46d 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,19 @@
/* 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 = ""; };
+ 0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AccessorySetupKit.framework; path = System/Library/Frameworks/AccessorySetupKit.framework; sourceTree = SDKROOT; };
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 +63,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 +76,50 @@
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 */,
+ 0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */,
+ 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 +140,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
- 776E5AB1CE0A62CBDBAB3996 /* Pods */,
- A5A9FDE94E78257E9A48E2E8 /* Frameworks */,
+ 1ACB686EC7DC36C6E33D372F /* Pods */,
+ 1E5DDD8701F6FB27AA502080 /* Frameworks */,
);
sourceTree = "";
};
@@ -164,18 +157,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 +166,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 +178,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 +197,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 +245,6 @@
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
- packageReferences = (
- 5912938D2D95215F004A65BB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
- );
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -297,37 +269,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 +311,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 +328,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 +365,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 +402,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 +461,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 +496,16 @@
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";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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 +516,53 @@
};
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;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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;
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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 +591,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 +649,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 +686,18 @@
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";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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 +711,16 @@
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";
+ IPHONEOS_DEPLOYMENT_TARGET = 18.0;
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 +763,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,63 @@
$(FLUTTER_BUILD_NAME)
CFBundleSignature
????
+ CFBundleURLTypes
+
+
+ CFBundleURLSchemes
+
+ fb3829022037361855
+
+
+
+ CFBundleTypeRole
+ Editor
+ CFBundleURLSchemes
+
+ wx5bbb7be9324f3e2d
+
+
+
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
+ UIBackgroundModes
+
+ bluetooth-central
+ bluetooth-peripheral
+ external-accessory
+
+ 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 +96,27 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ NSBluetoothAlwaysUsageDescription
+ 需要蓝牙权限用于连接和管理音频设备
+ NSBluetoothPeripheralUsageDescription
+ 需要蓝牙权限用于连接和管理音频设备
+ NSLocationWhenInUseUsageDescription
+ 需要位置权限用于搜索附近的蓝牙设备
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ 需要位置权限用于搜索附近的蓝牙设备
+ NSMicrophoneUsageDescription
+ 需要麦克风权限用于语音识别和录音功能
+ FacebookClientToken
+ 193d0a2b1757c41cfa557ea8e8f21c33
+ FacebookAppID
+ 3829022037361855
+ FacebookAdvertiserIDCollectionEnabled
+
+ FacebookAutoLogAppEventsEnabled
+
+ NSAccessorySetupKitSupports
+
+ Bluetooth
+
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/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart
index 8921a6fca..7f5b428bd 100644
--- a/lib/core/bindings/initial_binding.dart
+++ b/lib/core/bindings/initial_binding.dart
@@ -4,9 +4,7 @@ import '../../data/services/speech_factory.dart';
import '../../data/services/chat_history_service.dart';
import '../../data/services/volcano_translation_service.dart';
import '../../data/services/language_manager.dart';
-import '../../data/services/volcano_ai_service.dart';
import '../../core/utils/logger.dart';
-import '../../data/services/open_ai_service_adapter.dart';
import '../../data/services/bluetooth_manager.dart';
import '../../data/services/ble_manager.dart';
@@ -17,10 +15,11 @@ class InitialBinding extends Bindings {
Logger.warning('InitialBinding dependencies');
// 语言管理器(需要最先初始化)
- Get.put(LanguageManager(), permanent: true);
+ // 语言管理器(需要最先初始化)
+ Get.lazyPut(() => LanguageManager(), fenix: true);
//初始化语音工厂
- Get.put(SpeechFactory(), permanent: true);
+ Get.lazyPut(() => SpeechFactory(), fenix: true);
Get.find().initialize(initialType: SpeechServiceType.azure);
// 历史记录服务
@@ -30,30 +29,14 @@ class InitialBinding extends Bindings {
Get.lazyPut(() => VolcanoTranslationService(),
fenix: true);
- // OpenAI服务适配器
- Get.lazyPut(() {
- final adapter = OpenAIServiceAdapter();
- adapter.initialize();
- return adapter;
- }, fenix: true);
-
// 蓝牙服务
- Get.put(BluetoothManager(), permanent: true);
+ Get.lazyPut(() => BluetoothManager(), fenix: true);
// BleManager
- Get.put(BleManager(), permanent: true);
+ Get.lazyPut(() => BleManager(), fenix: true);
// MusicManager
- Get.put(MusicManager(), permanent: true);
+ Get.lazyPut(() => MusicManager(), fenix: true);
// 火山AI服务
- Get.lazyPut(() => VolcanoAIService(), fenix: true);
-
- // 蓝牙媒体按钮服务
- // Get.lazyPut(
- // () => BluetoothMediaButtonService(),
- // fenix: true);
-
- // // 语音交互服务
- // Get.lazyPut(() => VoiceInteractionService(),
- // fenix: true);
+ // Get.lazyPut(() => VolcanoAIService(), fenix: true);
// 杰理蓝牙服务
diff --git a/lib/core/themes/theme_service.dart b/lib/core/themes/theme_service.dart
index fd438f9d8..a70c3617c 100644
--- a/lib/core/themes/theme_service.dart
+++ b/lib/core/themes/theme_service.dart
@@ -21,7 +21,7 @@ class ThemeService {
Logger.info('从存储读取主题设置: $value');
return value ?? false;
} catch (e) {
- Logger.error('读取主题设置失败: $e');
+ Logger.error('读取主题设置失败: ${e.toString()}');
return false;
}
}
@@ -32,7 +32,7 @@ class ThemeService {
Logger.info('保存主题设置: ${isDarkMode ? "暗黑" : "浅色"}');
await _box.write(_key, isDarkMode);
} catch (e) {
- Logger.error('保存主题设置失败: $e');
+ Logger.error('保存主题设置失败: ${e.toString()}');
}
}
}
\ No newline at end of file
diff --git a/lib/core/utils/logger.dart b/lib/core/utils/logger.dart
index 3c5a15470..eba8701f5 100644
--- a/lib/core/utils/logger.dart
+++ b/lib/core/utils/logger.dart
@@ -15,77 +15,84 @@ class Logger {
/// 当前日志级别,低于此级别的日志不会被输出
static LogLevel _currentLevel = LogLevel.debug;
+ /// 默认标签
+ static const String _defaultTag = 'App';
+
/// 设置日志级别
static void setLevel(LogLevel level) {
_currentLevel = level;
}
+ /// 输出调试日志 (不带标签版本)
+ static void debug(String message) {
+ if (_currentLevel.index <= LogLevel.debug.index) {
+ _log('DEBUG', _defaultTag, message);
+ }
+ }
+
/// 输出调试日志
- static void debug(String message, {String? tag}) {
+ static void d(String tag, String message) {
if (_currentLevel.index <= LogLevel.debug.index) {
- _log('DEBUG', message, tag: tag);
+ _log('DEBUG', tag, message);
+ }
+ }
+
+ /// 输出信息日志 (不带标签版本)
+ static void info(String message) {
+ if (_currentLevel.index <= LogLevel.info.index) {
+ _log('INFO', _defaultTag, message);
}
}
/// 输出信息日志
- static void info(String message, {String? tag}) {
+ static void i(String tag, String message) {
if (_currentLevel.index <= LogLevel.info.index) {
- _log('INFO', message, tag: tag);
+ _log('INFO', tag, message);
+ }
+ }
+
+ /// 输出警告日志 (不带标签版本)
+ static void warning(String message) {
+ if (_currentLevel.index <= LogLevel.warning.index) {
+ _log('WARNING', _defaultTag, message);
}
}
/// 输出警告日志
- static void warning(String message, {String? tag}) {
+ static void w(String tag, String message) {
if (_currentLevel.index <= LogLevel.warning.index) {
- _log('WARNING', message, tag: tag);
+ _log('WARNING', tag, message);
}
}
- /// 输出错误日志
- static void error(String message, [dynamic error, StackTrace? stackTrace, String? tag]) {
+ /// 输出错误日志 (不带标签版本)
+ static void error(String message, [dynamic error, StackTrace? stackTrace]) {
if (_currentLevel.index <= LogLevel.error.index) {
- _log('ERROR', message, tag: tag);
+ _log('ERROR', _defaultTag, message);
if (error != null) {
- _log('ERROR', 'Original error: $error', tag: tag);
+ _log('ERROR', _defaultTag, 'Original error: $error');
}
if (stackTrace != null) {
- _log('ERROR', 'Stack trace: $stackTrace', tag: tag);
+ _log('ERROR', _defaultTag, 'Stack trace: $stackTrace');
}
}
}
- /// 获取当前调用栈的类名
- static String? _getCallerClassName() {
- try {
- final stackTrace = StackTrace.current.toString();
- final frames = stackTrace.split('\n');
-
- // 查找调用者帧 (跳过Logger框架内部的帧)
- for (int i = 0; i < frames.length; i++) {
- final frame = frames[i].trim();
- if (frame.contains('Logger.') || frame.contains('_log')) {
- continue;
- }
-
- // 简单的解析类名
- final regex = RegExp(r'#\d+\s+([^\.]+)\.([^\.]+)\s');
- final match = regex.firstMatch(frame);
- if (match != null && match.groupCount >= 1) {
- return match.group(1);
- }
- break;
+ /// 输出错误日志
+ static void e(String tag, String message, [dynamic error, StackTrace? stackTrace]) {
+ if (_currentLevel.index <= LogLevel.error.index) {
+ _log('ERROR', tag, message);
+ if (error != null) {
+ _log('ERROR', tag, 'Original error: $error');
+ }
+ if (stackTrace != null) {
+ _log('ERROR', tag, 'Stack trace: $stackTrace');
}
- } catch (e) {
- // 忽略解析错误
}
- return null;
}
/// 内部日志输出方法
- static void _log(String level, String message, {String? tag}) {
- final timestamp = DateTime.now().toString();
- final className = tag ?? _getCallerClassName() ?? '';
- final tagInfo = className.isNotEmpty ? '[$className] ' : '';
- developer.log('[$timestamp][$level][$tagInfo]: $message');
+ static void _log(String level, String tag, String message) {
+ developer.log('[$tag]: $message');
}
}
diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart
index 8ce3b77b6..138d559cd 100644
--- a/lib/data/services/ble_manager.dart
+++ b/lib/data/services/ble_manager.dart
@@ -54,7 +54,7 @@ class DeviceInfo {
this.rightEarCharging = false,
this.caseCharging = false,
this.firmwareVersion = '',
- this.firmwareProductId = 's3',
+ this.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();
@@ -168,7 +168,7 @@ class BleManager extends GetxService {
Future.delayed(const Duration(milliseconds: 300), () {
// 可选:检查是否仍处于连接状态
if (_isConnected.value) {
- fetchDeviceInfo();
+ // fetchDeviceInfo();
}
});
} else if (state == BleConnectionState.disconnected ||
@@ -198,7 +198,7 @@ class BleManager extends GetxService {
},
);
} catch (e) {
- Logger.error('设置BLE事件监听器失败: $e');
+ Logger.error('设置BLE事件监听器失败: ${e.toString()}');
}
}
@@ -225,7 +225,7 @@ class BleManager extends GetxService {
}
}
} catch (e) {
- Logger.error('处理设备信息异常: $e');
+ Logger.error('处理设备信息异常: ${e.toString()}');
}
}
@@ -303,10 +303,9 @@ class BleManager extends GetxService {
caseCharging: caseCharging,
);
- Logger.info(
- '设备电池信息已更新: 电量=$overallBattery%, 左耳=$leftBattery%, 右耳=$rightBattery%, 充电仓=$caseBattery%');
+ Logger.info('设备电池信息已更新: 电量=$overallBattery%, 左耳=$leftBattery%, 右耳=$rightBattery%, 充电仓=$caseBattery%');
} catch (e) {
- Logger.error('更新电池信息失败: $e');
+ Logger.error('更新电池信息失败: ${e.toString()}');
}
}
@@ -329,7 +328,7 @@ class BleManager extends GetxService {
Logger.info('设备版本信息已更新: 版本=$version');
} catch (e) {
- Logger.error('更新版本信息失败: $e');
+ Logger.error('更新版本信息失败: ${e.toString()}');
}
}
@@ -352,7 +351,7 @@ class BleManager extends GetxService {
Logger.info('设备设备id已更新: 设备id=$productId');
} catch (e) {
- Logger.error('更新设备id失败: $e');
+ Logger.error('更新设备id失败: ${e.toString()}');
}
}
@@ -440,7 +439,7 @@ class BleManager extends GetxService {
Logger.warning('产品id获取指令发送失败');
}
} catch (e) {
- Logger.error('获取BLE设备信息失败: $e');
+ Logger.error('获取BLE设备信息失败: ${e.toString()}');
}
}
@@ -455,7 +454,7 @@ class BleManager extends GetxService {
return _connectionState.value;
} catch (e) {
- Logger.error('获取BLE连接状态失败: $e');
+ Logger.error('获取BLE连接状态失败: ${e.toString()}');
return BleConnectionState.error;
}
}
@@ -497,6 +496,8 @@ class BleManager extends GetxService {
'openaiModel': openaiModel,
'systemPrompt': systemPrompt,
'mcpServer': jsonEncode(jsonData),
+ 'volcanoAppId': '2316081933',
+ 'volcanoToken': 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En',
};
// 记录日志(不记录敏感信息)
@@ -531,7 +532,7 @@ class BleManager extends GetxService {
// 如果设备信息存在,则获取详细信息
if (deviceInfo.value != null) {
// 获取详细设备信息
- fetchDeviceInfo();
+ // fetchDeviceInfo();
}
}
} else {
@@ -540,7 +541,7 @@ class BleManager extends GetxService {
return result;
} catch (e) {
- Logger.error('启动BLE服务失败: $e');
+ Logger.error('启动BLE服务失败: ${e.toString()}');
return false;
}
}
@@ -560,7 +561,7 @@ class BleManager extends GetxService {
return result;
} catch (e) {
- Logger.error('停止BLE服务失败: $e');
+ Logger.error('停止BLE服务失败: ${e.toString()}');
return false;
}
}
@@ -570,7 +571,7 @@ class BleManager extends GetxService {
try {
return await _bleService.getBatteryLevel();
} catch (e) {
- Logger.error('获取电池电量失败: $e');
+ Logger.error('获取电池电量失败: ${e.toString()}');
return 0;
}
}
@@ -580,7 +581,7 @@ class BleManager extends GetxService {
try {
return await _bleService.getBatteryInfo();
} catch (e) {
- Logger.error('获取电池信息失败: $e');
+ Logger.error('获取电池信息失败: ${e.toString()}');
return false;
}
}
@@ -590,7 +591,7 @@ class BleManager extends GetxService {
try {
return await _bleService.getProductId();
} catch (e) {
- Logger.error('获取设备版本失败: $e');
+ Logger.error('获取设备版本失败: ${e.toString()}');
return false;
}
}
@@ -600,7 +601,7 @@ class BleManager extends GetxService {
try {
return await _bleService.getDeviceVersion();
} catch (e) {
- Logger.error('获取设备版本失败: $e');
+ Logger.error('获取设备版本失败: ${e.toString()}');
return false;
}
}
@@ -610,7 +611,7 @@ class BleManager extends GetxService {
try {
return await _bleService.openEncoder();
} catch (e) {
- Logger.error('打开编码器失败: $e');
+ Logger.error('打开编码器失败: ${e.toString()}');
return false;
}
}
@@ -620,7 +621,7 @@ class BleManager extends GetxService {
try {
return await _bleService.openDecoder();
} catch (e) {
- Logger.error('打开解码器失败: $e');
+ Logger.error('打开解码器失败: ${e.toString()}');
return false;
}
}
@@ -630,7 +631,7 @@ class BleManager extends GetxService {
try {
return await _bleService.openA2DPDecoder();
} catch (e) {
- Logger.error('打开解码器失败: $e');
+ Logger.error('打开解码器失败: ${e.toString()}');
return false;
}
}
@@ -640,7 +641,7 @@ class BleManager extends GetxService {
try {
return await _bleService.closeCodec();
} catch (e) {
- Logger.error('关闭编解码器失败: $e');
+ Logger.error('关闭编解码器失败: ${e.toString()}');
return false;
}
}
@@ -650,7 +651,7 @@ class BleManager extends GetxService {
try {
return await _bleService.playA2DP();
} catch (e) {
- Logger.error('播放A2DP失败: $e');
+ Logger.error('播放A2DP失败: ${e.toString()}');
return false;
}
}
@@ -662,4 +663,4 @@ class BleManager extends GetxService {
}
return Future.value(Get.find());
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/bluetooth_manager.dart b/lib/data/services/bluetooth_manager.dart
index e6e26ec02..c52a7f4b5 100644
--- a/lib/data/services/bluetooth_manager.dart
+++ b/lib/data/services/bluetooth_manager.dart
@@ -75,11 +75,11 @@ class BluetoothManager extends GetxService {
}
},
onError: (error) {
- Logger.error('蓝牙事件流错误: $error');
+ Logger.error('蓝牙事件流错误: ${error.toString()}');
},
);
} catch (e) {
- Logger.error('设置蓝牙事件监听器失败: $e');
+ Logger.error('设置蓝牙事件监听器失败: ${e.toString()}');
}
}
@@ -106,7 +106,7 @@ class BluetoothManager extends GetxService {
Logger.info('未找到已连接的蓝牙设备');
} catch (e) {
- Logger.error('刷新设备连接失败: $e');
+ Logger.error('刷新设备连接失败: ${e.toString()}');
}
}
@@ -117,7 +117,7 @@ class BluetoothManager extends GetxService {
_bluetoothState.value = isEnabled ? 'on' : 'off';
return isEnabled;
} catch (e) {
- Logger.error('检查蓝牙状态失败: $e');
+ Logger.error('检查蓝牙状态失败: ${e.toString()}');
return false;
}
}
diff --git a/lib/data/services/chat_history_service.dart b/lib/data/services/chat_history_service.dart
index 8749c5714..9a1688314 100644
--- a/lib/data/services/chat_history_service.dart
+++ b/lib/data/services/chat_history_service.dart
@@ -39,7 +39,7 @@ class ChatHistoryService extends GetxService {
// 检查并清理过多的历史记录
await _cleanupOldMessages(agentId);
} catch (e) {
- Logger.error('保存聊天记录失败', e);
+ Logger.error('保存聊天记录失败', e.toString());
}
}
diff --git a/lib/data/services/deapsound_ai_service.dart b/lib/data/services/deapsound_ai_service.dart
index 022f2aa2e..4701bf674 100644
--- a/lib/data/services/deapsound_ai_service.dart
+++ b/lib/data/services/deapsound_ai_service.dart
@@ -112,12 +112,12 @@ class DeapsoundAIService implements AiService {
}
}
} catch (e) {
- Logger.error('JSON解析错误: $e');
+ Logger.error('JSON解析错误: ${e.toString()}');
}
}
}
} catch (e) {
- Logger.error('请求异常: $e');
+ Logger.error('请求异常: ${e.toString()}');
} finally {
client.close();
}
diff --git a/lib/data/services/kouzi_ai_service.dart b/lib/data/services/kouzi_ai_service.dart
index cf479ac56..c19f23d1a 100644
--- a/lib/data/services/kouzi_ai_service.dart
+++ b/lib/data/services/kouzi_ai_service.dart
@@ -82,14 +82,14 @@ class KouziAIService implements AiService {
}
}
} catch (e) {
- Logger.error('JSON解析错误: $e');
+ Logger.error('JSON解析错误: ${e.toString()}');
}
}
}
} catch (e) {
- Logger.error('请求异常: $e');
+ Logger.error('请求异常: ${e.toString()}');
} finally {
client.close();
}
}
-}
+}
\ No newline at end of file
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_factory.dart b/lib/data/services/speech_factory.dart
index b1c7a1ec5..dd9a79c68 100644
--- a/lib/data/services/speech_factory.dart
+++ b/lib/data/services/speech_factory.dart
@@ -88,7 +88,7 @@ class SpeechFactory extends GetxService {
Logger.info(
'语音服务初始化完成,当前类型: ${_currentServiceType.toString().split('.').last}');
} catch (e) {
- Logger.error('语音服务初始化失败: $e');
+ Logger.error('语音服务初始化失败: ${e.toString()}');
rethrow;
}
}
@@ -108,4 +108,4 @@ class SpeechFactory extends GetxService {
}
return Get.find();
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart
index 5c87577ee..fe0122717 100644
--- a/lib/data/services/speech_impl/azure_asr_service.dart
+++ b/lib/data/services/speech_impl/azure_asr_service.dart
@@ -106,7 +106,7 @@ class AzureAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别服务初始化${result ? '成功' : '失败'}');
return result;
} catch (e) {
- Logger.error('Azure 语音识别服务初始化失败: $e');
+ Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false;
rethrow;
}
@@ -144,7 +144,7 @@ class AzureAsrService extends GetxService implements AsrService {
throw Exception('无效的识别结果格式');
} catch (e) {
- Logger.error('语音识别失败: $e');
+ Logger.error('语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString());
}
}
@@ -177,7 +177,7 @@ class AzureAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
} catch (e) {
- Logger.error('开始连续语音识别失败: $e');
+ Logger.error('开始连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow;
}
@@ -195,7 +195,7 @@ class AzureAsrService extends GetxService implements AsrService {
_cleanupEventStream();
return result;
} catch (e) {
- Logger.error('停止连续语音识别失败: $e');
+ Logger.error('停止连续语音识别失败: ${e.toString()}');
_cleanupEventStream();
rethrow;
}
@@ -214,7 +214,6 @@ class AzureAsrService extends GetxService implements AsrService {
final String eventType = eventMap['type'] as String? ?? '';
// 添加日志帮助调试
- Logger.debug('收到语音识别事件: $eventType, 数据: $eventMap');
switch (eventType) {
case 'result':
@@ -260,7 +259,7 @@ class AzureAsrService extends GetxService implements AsrService {
final String errorDetails = eventMap['errorDetails'] as String? ?? '';
if (reason.isNotEmpty || errorDetails.isNotEmpty) {
- Logger.error('识别取消: $reason - $errorDetails');
+ Logger.error('识别取消: $reason - ${errorDetails.toString()}');
}
_eventStreamController?.add(RecognitionEvent(
@@ -271,7 +270,7 @@ class AzureAsrService extends GetxService implements AsrService {
case 'error':
final String error = eventMap['message'] as String? ?? '';
- Logger.error('识别错误: $error');
+ Logger.error('识别错误: ${error.toString()}');
_eventStreamController?.add(RecognitionEvent(
type: RecognitionEventType.error,
error: error,
@@ -282,7 +281,7 @@ class AzureAsrService extends GetxService implements AsrService {
/// 处理识别事件流错误
void _handleRecognitionError(Object error) {
- Logger.error('识别事件流错误: $error');
+ Logger.error('识别事件流错误: ${error.toString()}');
_eventStreamController?.addError(error);
_cleanupEventStream();
}
@@ -311,7 +310,7 @@ class AzureAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别资源已释放');
} catch (e) {
- Logger.error('释放语音识别资源失败: $e');
+ Logger.error('释放语音识别资源失败: ${e.toString()}');
_cleanupEventStream();
_isInitialized = false;
}
@@ -339,8 +338,8 @@ class AzureAsrService extends GetxService implements AsrService {
return true;
} catch (e) {
- Logger.error('推送音频数据失败: $e');
+ Logger.error('推送音频数据失败: ${e.toString()}');
return false;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/azure_tts_service.dart b/lib/data/services/speech_impl/azure_tts_service.dart
index 2e24a90b8..d6eccdf9b 100644
--- a/lib/data/services/speech_impl/azure_tts_service.dart
+++ b/lib/data/services/speech_impl/azure_tts_service.dart
@@ -69,7 +69,7 @@ class AzureTtsService extends GetxService implements TtsService {
_handleTtsEvent(event);
}
}, onError: (error) {
- Logger.error('TTS事件流错误: $error');
+ Logger.error('TTS事件流错误: ${error.toString()}');
_eventStreamController.addError(error);
});
}
@@ -78,7 +78,7 @@ class AzureTtsService extends GetxService implements TtsService {
void _handleTtsEvent(Map event) {
final String eventType = event['type'] as String? ?? '';
- // Logger.debug('收到TTS事件: $eventType, 数据: $event');
+ // Logger.debug('收到TTS事件: ${e.toString()}ventType, 数据: ${e.toString()}vent');
switch (eventType) {
case 'synthesis_started':
@@ -159,7 +159,7 @@ class AzureTtsService extends GetxService implements TtsService {
_isInitialized = result;
return result;
} catch (e) {
- Logger.error('初始化失败: $e');
+ Logger.error('初始化失败: ${e.toString()}');
return false;
}
}
@@ -177,7 +177,7 @@ class AzureTtsService extends GetxService implements TtsService {
if (result) _currentVoice = voiceName;
return result;
} catch (e) {
- Logger.error('设置语音失败: $e');
+ Logger.error('设置语音失败: ${e.toString()}');
return false;
}
}
@@ -195,7 +195,7 @@ class AzureTtsService extends GetxService implements TtsService {
return result == true;
} catch (e) {
- Logger.error('语音合成失败: $e');
+ Logger.error('语音合成失败: ${e.toString()}');
return false;
}
}
@@ -250,7 +250,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('处理流式文本失败: $e');
+ Logger.error('处理流式文本失败: ${e.toString()}');
return false;
}
}
@@ -316,7 +316,7 @@ class AzureTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成');
return result;
} catch (e) {
- Logger.error('刷新TTS流失败: $e');
+ Logger.error('刷新TTS流失败: ${e.toString()}');
return false;
}
}
@@ -358,7 +358,7 @@ class AzureTtsService extends GetxService implements TtsService {
_textQueue.removeAt(0);
}
} catch (e) {
- Logger.error('处理语音队列出错: $e');
+ Logger.error('处理语音队列出错: ${e.toString()}');
// 出错时移除当前项并尝试继续
if (_textQueue.isNotEmpty) {
@@ -389,7 +389,7 @@ class AzureTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('停止语音合成出错: $e');
+ Logger.error('停止语音合成出错: ${e.toString()}');
return false;
}
}
@@ -410,7 +410,7 @@ class AzureTtsService extends GetxService implements TtsService {
await _channel.invokeMethod('dispose');
_isInitialized = false;
} catch (e) {
- Logger.error('释放资源失败: $e');
+ Logger.error('释放资源失败: ${e.toString()}');
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/flutter_asr_service.dart b/lib/data/services/speech_impl/flutter_asr_service.dart
index 438953af8..c8716ad8f 100644
--- a/lib/data/services/speech_impl/flutter_asr_service.dart
+++ b/lib/data/services/speech_impl/flutter_asr_service.dart
@@ -107,7 +107,7 @@ class FlutterAsrService extends GetxService implements AsrService {
} catch (e) {
_isAvailable.value = false;
_asrState.value = AsrState.error;
- Logger.error('语音识别初始化失败: $e');
+ Logger.error('语音识别初始化失败: ${e.toString()}');
}
}
@@ -203,7 +203,7 @@ class FlutterAsrService extends GetxService implements AsrService {
return result;
} catch (e) {
- Logger.error('启动语音识别失败: $e');
+ Logger.error('启动语音识别失败: ${e.toString()}');
_asrState.value = AsrState.error;
return false;
}
@@ -253,7 +253,7 @@ class FlutterAsrService extends GetxService implements AsrService {
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
}
} catch (e) {
- Logger.error('重启连续识别失败: $e');
+ Logger.error('重启连续识别失败: ${e.toString()}');
// 发送错误给控制器
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
@@ -304,7 +304,7 @@ class FlutterAsrService extends GetxService implements AsrService {
await _initSpeech();
return _isAvailable.value;
} catch (e) {
- Logger.error('语音识别初始化失败: $e');
+ Logger.error('语音识别初始化失败: ${e.toString()}');
return false;
}
}
@@ -357,7 +357,7 @@ class FlutterAsrService extends GetxService implements AsrService {
},
);
} catch (e) {
- Logger.error('一次性语音识别失败: $e');
+ Logger.error('一次性语音识别失败: ${e.toString()}');
return RecognitionEvent.error('$e');
}
}
@@ -406,7 +406,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = true;
return _continuousRecognitionController!.stream;
} catch (e) {
- Logger.error('启动连续语音识别失败: $e');
+ Logger.error('启动连续语音识别失败: ${e.toString()}');
// 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) {
@@ -442,7 +442,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_asrState.value = AsrState.done;
return true;
} catch (e) {
- Logger.error('停止连续语音识别失败: $e');
+ Logger.error('停止连续语音识别失败: ${e.toString()}');
// 强制重置状态
_isListening = false;
@@ -472,7 +472,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_isListening = false;
_asrState.value = AsrState.notInitialized;
} catch (e) {
- Logger.error('释放语音识别资源失败: $e');
+ Logger.error('释放语音识别资源失败: ${e.toString()}');
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/flutter_tts_service.dart b/lib/data/services/speech_impl/flutter_tts_service.dart
index 810f82787..aca6fca66 100644
--- a/lib/data/services/speech_impl/flutter_tts_service.dart
+++ b/lib/data/services/speech_impl/flutter_tts_service.dart
@@ -96,7 +96,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('FlutterTtsService 已初始化');
} catch (e) {
- Logger.error('TTS初始化失败: $e');
+ Logger.error('TTS初始化失败: ${e.toString()}');
}
}
@@ -118,7 +118,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('支持的语音: ${_supportedVoices.join(", ")}');
return true;
} catch (e) {
- Logger.error('TTS初始化失败: $e');
+ Logger.error('TTS初始化失败: ${e.toString()}');
return false;
}
}
@@ -149,7 +149,7 @@ class FlutterTtsService extends GetxService implements TtsService {
_currentVoice.value = _supportedVoices.first;
}
} catch (e) {
- Logger.error('加载TTS语音失败: $e');
+ Logger.error('加载TTS语音失败: ${e.toString()}');
// 添加默认语音
if (_supportedVoices.isEmpty) {
@@ -175,7 +175,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName');
return true;
} catch (e) {
- Logger.error('设置TTS语音失败: $e');
+ Logger.error('设置TTS语音失败: ${e.toString()}');
return false;
}
}
@@ -200,7 +200,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('设置语音参数失败: $e');
+ Logger.error('设置语音参数失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('设置语音参数失败: $e'));
@@ -227,7 +227,7 @@ class FlutterTtsService extends GetxService implements TtsService {
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
- Logger.error('TTS播放失败: $e');
+ Logger.error('TTS播放失败: ${e.toString()}');
return false;
}
}
@@ -288,7 +288,7 @@ class FlutterTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('处理流式文本失败: $e');
+ Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@@ -333,7 +333,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS队列播放完成');
return true;
} catch (e) {
- Logger.error('等待TTS队列播放完成失败: $e');
+ Logger.error('等待TTS队列播放完成失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@@ -382,7 +382,7 @@ class FlutterTtsService extends GetxService implements TtsService {
}
}
} catch (e) {
- Logger.error('TTS处理队列失败: $e');
+ Logger.error('TTS处理队列失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
@@ -422,7 +422,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS停止播放完成');
return true;
} catch (e) {
- Logger.error('TTS停止播放失败: $e');
+ Logger.error('TTS停止播放失败: ${e.toString()}');
// 即使出错也尝试强制重置状态
_isSpeaking.value = false;
@@ -443,7 +443,7 @@ class FlutterTtsService extends GetxService implements TtsService {
Logger.info('TTS资源已释放');
} catch (e) {
- Logger.error('释放TTS资源失败: $e');
+ Logger.error('释放TTS资源失败: ${e.toString()}');
}
}
@@ -462,7 +462,7 @@ class FlutterTtsService extends GetxService implements TtsService {
await flushStream();
return true;
} catch (e) {
- Logger.error('处理文本流时出错: $e');
+ Logger.error('处理文本流时出错: ${e.toString()}');
return false;
}
}
diff --git a/lib/data/services/speech_impl/voice_clone_tts_service.dart b/lib/data/services/speech_impl/voice_clone_tts_service.dart
index 2f57939a9..43079325f 100644
--- a/lib/data/services/speech_impl/voice_clone_tts_service.dart
+++ b/lib/data/services/speech_impl/voice_clone_tts_service.dart
@@ -76,7 +76,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
);
return true;
} catch (e) {
- Logger.error('TTS初始化失败: $e');
+ Logger.error('TTS初始化失败: ${e.toString()}');
return false;
}
}
@@ -88,7 +88,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Logger.info('设置TTS语音: $voiceName');
return true;
} catch (e) {
- Logger.error('设置TTS语音失败: $e');
+ Logger.error('设置TTS语音失败: ${e.toString()}');
return false;
}
}
@@ -127,7 +127,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
_handle(message);
},
onError: (error) {
- Logger.error('连接失败: $error');
+ Logger.error('连接失败: ${error.toString()}');
},
onDone: () {
Logger.error('WebSocket 连接已关闭!');
@@ -135,7 +135,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
cancelOnError: true,
);
} catch (e) {
- Logger.error('初始化异常: $e');
+ Logger.error('初始化异常: ${e.toString()}');
}
}
@@ -226,7 +226,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
_isSpeaking.value = false;
- Logger.error('语音合成失败: $e');
+ Logger.error('语音合成失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('语音合成失败: $e'));
@@ -285,7 +285,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('处理流式文本失败: $e');
+ Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('处理流式文本失败: $e'));
@@ -319,7 +319,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
return true;
} catch (e) {
- Logger.error('刷新TTS流失败: $e');
+ Logger.error('刷新TTS流失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('刷新TTS流失败: $e'));
@@ -347,7 +347,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await speakOnce(item);
}
} catch (e) {
- Logger.error('处理语音队列出错: $e');
+ Logger.error('处理语音队列出错: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('处理语音队列出错: $e'));
@@ -370,7 +370,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
}
return true;
} catch (e) {
- Logger.error('停止语音合成出错: $e');
+ Logger.error('停止语音合成出错: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('停止语音合成出错: $e'));
return false;
@@ -387,7 +387,7 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
await _eventController.close();
_isInitialized = false;
} catch (e) {
- Logger.error('释放资源失败: $e');
+ Logger.error('释放资源失败: ${e.toString()}');
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/volcano_asr_api_service.dart b/lib/data/services/speech_impl/volcano_asr_api_service.dart
index 3ffc4e75e..26f18e1c5 100644
--- a/lib/data/services/speech_impl/volcano_asr_api_service.dart
+++ b/lib/data/services/speech_impl/volcano_asr_api_service.dart
@@ -99,7 +99,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('火山语音API已初始化');
return true;
} catch (e) {
- Logger.error('初始化火山语音API失败: $e');
+ Logger.error('初始化火山语音API失败: ${e.toString()}');
return false;
}
}
@@ -151,7 +151,7 @@ class VolcanoAsrApiService implements AsrService {
return resultCompleter.future;
} catch (e) {
- Logger.error('单次语音识别失败: $e');
+ Logger.error('单次语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString());
}
}
@@ -184,7 +184,7 @@ class VolcanoAsrApiService implements AsrService {
return _recognitionController!.stream;
} catch (e) {
- Logger.error('开始连续语音识别失败: $e');
+ Logger.error('开始连续语音识别失败: ${e.toString()}');
await _cleanupResources();
// 返回一个包含错误的流
@@ -222,7 +222,7 @@ class VolcanoAsrApiService implements AsrService {
return true;
} catch (e) {
- Logger.error('停止连续语音识别失败: $e');
+ Logger.error('停止连续语音识别失败: ${e.toString()}');
// 确保资源被清理
await _cleanupResources();
@@ -250,7 +250,7 @@ class VolcanoAsrApiService implements AsrService {
_isInitialized = false;
Logger.info('火山语音API资源已释放');
} catch (e) {
- Logger.error('释放火山语音API资源失败: $e');
+ Logger.error('释放火山语音API资源失败: ${e.toString()}');
_isContinuousRecognitionActive = false;
_isInitialized = false;
}
@@ -284,7 +284,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音机初始化成功');
} catch (e) {
// _recorder = null;
- Logger.error('初始化录音机失败: $e');
+ Logger.error('初始化录音机失败: ${e.toString()}');
rethrow;
}
}
@@ -344,7 +344,7 @@ class VolcanoAsrApiService implements AsrService {
_connectionCompleter!.completeError(e);
}
- Logger.error('连接WebSocket失败: $e');
+ Logger.error('连接WebSocket失败: ${e.toString()}');
rethrow;
}
@@ -432,7 +432,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(heartbeatMsg));
} catch (e) {
- Logger.error('发送心跳包失败: $e');
+ Logger.error('发送心跳包失败: ${e.toString()}');
}
}
}
@@ -461,7 +461,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(startCommand));
Logger.info('已发送开始识别指令');
} catch (e) {
- Logger.error('发送开始识别指令失败: $e');
+ Logger.error('发送开始识别指令失败: ${e.toString()}');
rethrow;
}
}
@@ -496,7 +496,7 @@ class VolcanoAsrApiService implements AsrService {
Logger.info('录音已开始');
} catch (e) {
_isRecording = false;
- Logger.error('开始录音失败: $e');
+ Logger.error('开始录音失败: ${e.toString()}');
rethrow;
}
*/
@@ -544,7 +544,7 @@ class VolcanoAsrApiService implements AsrService {
await _sendAudioData(audioData);
}
} catch (e) {
- Logger.error('捕获和发送音频失败: $e');
+ Logger.error('捕获和发送音频失败: ${e.toString()}');
}
*/
}
@@ -562,7 +562,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(audioMessage));
} catch (e) {
- Logger.error('发送音频数据失败: $e');
+ Logger.error('发送音频数据失败: ${e.toString()}');
}
}
@@ -579,7 +579,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.add(jsonEncode(endCommand));
Logger.info('已发送结束指令');
} catch (e) {
- Logger.error('发送结束指令失败: $e');
+ Logger.error('发送结束指令失败: ${e.toString()}');
}
}
@@ -596,7 +596,7 @@ class VolcanoAsrApiService implements AsrService {
_isRecording = false;
Logger.info('录音已停止');
} catch (e) {
- Logger.error('停止录音失败: $e');
+ Logger.error('停止录音失败: ${e.toString()}');
}
}
*/
@@ -641,7 +641,7 @@ class VolcanoAsrApiService implements AsrService {
}
}
} catch (e) {
- Logger.error('处理WebSocket消息失败: $e');
+ Logger.error('处理WebSocket消息失败: ${e.toString()}');
if (_recognitionController != null && !_recognitionController!.isClosed) {
_recognitionController!.addError('处理消息失败: $e');
@@ -675,7 +675,7 @@ class VolcanoAsrApiService implements AsrService {
}
}
} catch (e) {
- Logger.error('处理结果消息失败: $e');
+ Logger.error('处理结果消息失败: ${e.toString()}');
}
}
@@ -741,7 +741,7 @@ class VolcanoAsrApiService implements AsrService {
/// 处理WebSocket错误
void _handleWebSocketError(dynamic error) {
- Logger.error('WebSocket错误: $error');
+ Logger.error('WebSocket错误: ${e.toString()}rror');
if (_connectionCompleter != null && !_connectionCompleter!.isCompleted) {
_connectionCompleter!.completeError(error);
@@ -789,7 +789,7 @@ class VolcanoAsrApiService implements AsrService {
_channel!.sink.close();
_channel = null;
} catch (e) {
- Logger.error('关闭WebSocket连接失败: $e');
+ Logger.error('关闭WebSocket连接失败: ${e.toString()}');
}
}
}
@@ -811,4 +811,4 @@ class VolcanoAsrApiService implements AsrService {
_isContinuousRecognitionActive = false;
_isRecognitionEndRequested = false;
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/speech_impl/volcano_tts_api_service.dart b/lib/data/services/speech_impl/volcano_tts_api_service.dart
index e01e253ed..8a3196c03 100644
--- a/lib/data/services/speech_impl/volcano_tts_api_service.dart
+++ b/lib/data/services/speech_impl/volcano_tts_api_service.dart
@@ -357,7 +357,7 @@ class VolcanoTtsApiService extends GetxController {
_handleMessage,
onError: (error) {
// 直接处理错误,不尝试重连
- Logger.error('WebSocket错误: $error');
+ Logger.error('WebSocket错误: ${error.toString()}');
if (!_eventController.isClosed) {
_eventController.add({
'eventType': 'error',
@@ -391,7 +391,7 @@ class VolcanoTtsApiService extends GetxController {
});
}
} catch (e) {
- Logger.error('处理TTS连接超时事件失败: $e');
+ Logger.error('处理TTS连接超时事件失败: ${e.toString()}');
}
}
timeout = null;
@@ -427,7 +427,7 @@ class VolcanoTtsApiService extends GetxController {
return result;
} catch (e) {
// 只记录错误,不尝试重连
- Logger.error('连接过程中发生异常: $e');
+ Logger.error('连接过程中发生异常: ${e.toString()}');
return false;
} finally {
isConnecting.value = false;
@@ -479,7 +479,7 @@ class VolcanoTtsApiService extends GetxController {
'error': '会话开始超时'
});
} catch (e) {
- Logger.error('处理TTS会话超时事件失败: $e');
+ Logger.error('处理TTS会话超时事件失败: ${e.toString()}');
}
}
timeout = null;
@@ -515,7 +515,7 @@ class VolcanoTtsApiService extends GetxController {
return result;
} catch (e) {
// 只记录错误,不尝试恢复
- Logger.error('开始TTS会话失败: $e');
+ Logger.error('开始TTS会话失败: ${e.toString()}');
return false;
}
}
@@ -902,10 +902,10 @@ class VolcanoTtsApiService extends GetxController {
'error': error.toString()
});
- Logger.error('火山语音TTS错误: $error');
+ Logger.error('火山语音TTS错误: ${error.toString()}');
} catch (e) {
// 如果在处理错误时发生异常,记录到日志但不再抛出异常
- Logger.error('处理TTS错误时发生异常: $e,原始错误: $error');
+ Logger.error('处理TTS错误时发生异常: ${e.toString()},原始错误: ${error.toString()}');
}
}
@@ -1445,7 +1445,7 @@ class VolcanoTtsApiService extends GetxController {
return success;
} catch (e) {
- Logger.error('TTS一次性合成失败: $e');
+ Logger.error('TTS一次性合成失败: ${e.toString()}');
// 确保会话结束
endSession();
return false;
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/data/services/speech_impl/xunfei_asr_service.dart b/lib/data/services/speech_impl/xunfei_asr_service.dart
index ef6791e3f..00b151019 100644
--- a/lib/data/services/speech_impl/xunfei_asr_service.dart
+++ b/lib/data/services/speech_impl/xunfei_asr_service.dart
@@ -54,7 +54,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别服务初始化成功');
return true;
} catch (e) {
- Logger.error('Azure 语音识别服务初始化失败: $e');
+ Logger.error('Azure 语音识别服务初始化失败: ${e.toString()}');
_isInitialized = false;
rethrow;
}
@@ -69,7 +69,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
try {
throw Exception('无效的识别结果格式');
} catch (e) {
- Logger.error('语音识别失败: $e');
+ Logger.error('语音识别失败: ${e.toString()}');
return RecognitionEvent.error(e.toString());
}
}
@@ -112,7 +112,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = true;
return _eventStreamController!.stream;
} catch (e) {
- Logger.error('开始连续语音识别失败: $e');
+ Logger.error('开始连续语音识别失败: ${e.toString()}');
rethrow;
}
}
@@ -152,7 +152,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
},
onError: (error) {
isConnected = false;
- Logger.error('连接失败: $error');
+ Logger.error('连接失败: ${error.toString()}');
},
onDone: () {
isConnected = false;
@@ -161,7 +161,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
cancelOnError: true,
);
} catch (e) {
- Logger.error("初始化异常----------------$e");
+ Logger.error("初始化异常----------------${e.toString()}");
}
}
@@ -185,7 +185,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
}
}
- Logger.info(
+ Logger.info(
"识别到的内容 type:${data['cn']['st']['type']} msg:$msg rl:$role");
if (msg.isNotEmpty) {
if (data['cn']['st']['type'] == "0") {
@@ -206,7 +206,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
}
}
} catch (e) {
- Logger.error("接受消息异常 $e");
+ Logger.error("接受消息异常 ${e.toString()}");
}
}
@@ -242,7 +242,7 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isContinuousRecognitionActive = false;
return true;
} catch (e) {
- Logger.error('停止连续语音识别失败: $e');
+ Logger.error('停止连续语音识别失败: ${e.toString()}');
rethrow;
}
}
@@ -266,8 +266,8 @@ class XunfeiAsrService extends GetxService implements AsrService {
Logger.info('Azure 语音识别资源已释放');
} catch (e) {
- Logger.error('释放语音识别资源失败: $e');
+ Logger.error('释放语音识别资源失败: ${e.toString()}');
_isInitialized = false;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart
index f4771ffac..5dd9f96e7 100644
--- a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart
+++ b/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart
@@ -76,7 +76,7 @@ class AndroidVoiceInteractionService extends GetxService {
_handleVoiceInteractionEvent(event);
}
}, onError: (error) {
- Logger.error('语音交互事件通道错误: $error');
+ Logger.error('语音交互事件通道错误: ${error.toString()}');
});
}
@@ -106,7 +106,7 @@ class AndroidVoiceInteractionService extends GetxService {
Logger.info('语音交互服务初始化完成');
return this;
} catch (e) {
- Logger.error('语音交互服务初始化失败: $e');
+ Logger.error('语音交互服务初始化失败: ${e.toString()}');
return this;
}
}
@@ -123,7 +123,7 @@ class AndroidVoiceInteractionService extends GetxService {
_isServiceRunning.value = isRunning;
Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}');
} catch (e) {
- Logger.error('检查语音交互服务状态失败: $e');
+ Logger.error('检查语音交互服务状态失败: ${e.toString()}');
_isServiceRunning.value = false;
}
}
@@ -151,7 +151,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
- Logger.error('启动语音交互服务失败: $e');
+ Logger.error('启动语音交互服务失败: ${e.toString()}');
return false;
}
}
@@ -175,7 +175,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
- Logger.error('停止语音交互服务失败: $e');
+ Logger.error('停止语音交互服务失败: ${e.toString()}');
return false;
}
}
@@ -199,7 +199,7 @@ class AndroidVoiceInteractionService extends GetxService {
return result;
} catch (e) {
- Logger.error('暂停语音交互失败: $e');
+ Logger.error('暂停语音交互失败: ${e.toString()}');
return false;
}
}
@@ -213,7 +213,7 @@ class AndroidVoiceInteractionService extends GetxService {
final int timestamp = eventMap['timestamp'] as int? ?? 0;
// 添加时间戳日志,帮助调试
- Logger.info('收到原生端事件: $eventType, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
+ Logger.info('收到原生端事件: ${eventType.toString()}, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}');
switch (eventType) {
case 'chatHistory':
@@ -252,7 +252,7 @@ class AndroidVoiceInteractionService extends GetxService {
break;
default:
- Logger.warning('收到未知类型的语音交互事件: $eventType');
+ Logger.warning('收到未知类型的语音交互事件: ${eventType.toString()}');
break;
}
}
@@ -297,7 +297,7 @@ class AndroidVoiceInteractionService extends GetxService {
// 保存更新后的历史记录
await chatHistoryService.saveHistory(agentId, existingMessages);
} catch (e) {
- Logger.error('保存聊天记录失败: $e');
+ Logger.error('保存聊天记录失败: ${e.toString()}');
}
}
diff --git a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
index 46dbb2391..1fc80ba89 100644
--- a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
+++ b/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart
@@ -66,7 +66,7 @@ class IosVoiceInteractionService extends GetxService {
// 保持idle状态,不自动启动语音识别
Logger.info('iOS语音交互服务初始化完成,处于idle状态');
} catch (e) {
- Logger.error('iOS语音交互服务初始化失败: $e');
+ Logger.error('iOS语音交互服务初始化失败: ${e.toString()}');
}
return this;
@@ -110,13 +110,13 @@ class IosVoiceInteractionService extends GetxService {
// 监听识别结果
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent, onError: (error) {
- Logger.error('语音识别错误: $error');
+ Logger.error('语音识别错误: ${error.toString()}');
_startContinuousListening(); // 尝试重新启动
});
Logger.info('开始连续语音识别');
} catch (e) {
- Logger.error('启动语音识别失败: $e');
+ Logger.error('启动语音识别失败: ${e.toString()}');
}
}
@@ -263,7 +263,7 @@ class IosVoiceInteractionService extends GetxService {
// 处理完成
isProcessingAI.value = false;
} catch (e) {
- Logger.error('AI响应处理失败: $e');
+ Logger.error('AI响应处理失败: ${e.toString()}');
// 错误恢复
isProcessingAI.value = false;
}
@@ -437,7 +437,7 @@ class IosVoiceInteractionService extends GetxService {
Logger.info('保存聊天历史记录成功');
} catch (e) {
- Logger.error('保存聊天历史记录失败: $e');
+ Logger.error('保存聊天历史记录失败: ${e.toString()}');
}
}
@@ -463,4 +463,4 @@ class IosVoiceInteractionService extends GetxService {
_eventStreamController.add(event);
Logger.info('已发送识别开始事件');
}
-}
+}
\ No newline at end of file
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/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart
index 34174a658..536514dbf 100644
--- a/lib/modules/agent/controllers/agent_controller.dart
+++ b/lib/modules/agent/controllers/agent_controller.dart
@@ -5,12 +5,12 @@ import 'package:deep_voice/data/services/music_manager.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:agent_service/agent_service.dart';
-import 'package:logger/logger.dart';
import 'package:chat_storage/chat_storage.dart';
import 'package:image_picker/image_picker.dart';
import 'package:get_storage/get_storage.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:app_settings/app_settings.dart';
+import 'package:deep_voice/core/utils/logger.dart';
class Message {
final bool isUser; //标识信息是否来自用户还是AI
@@ -36,8 +36,6 @@ class Message {
class AgentController extends GetxController {
static AgentController get to => Get.find(); //单例访问模式
- final logger = Logger();
-
// 聊天消息列表
final messages = [].obs;
@@ -78,6 +76,8 @@ class AgentController extends GetxController {
bool _isNewAssistantResponse = true;
String _currentResponseId = '';
+ final TAG = 'AgentController';
+
// 会话ID
final String sessionId = 'default_agent';
@@ -109,7 +109,7 @@ class AgentController extends GetxController {
isTtsEnabled.value = storage.read('agent_tts_enabled') ?? true;
} catch (e) {
// 如果读取失败,使用默认值
- logger.e('加载TTS偏好设置失败: $e');
+ Logger.e(TAG, '加载TTS偏好设置失败: $e');
isTtsEnabled.value = true;
}
}
@@ -128,14 +128,14 @@ class AgentController extends GetxController {
final storage = GetStorage();
await storage.write('agent_tts_enabled', isTtsEnabled.value);
} catch (e) {
- logger.e('保存TTS偏好设置失败: $e');
+ Logger.e(TAG, '保存TTS偏好设置失败: $e');
}
}
// 加载聊天历史记录
Future _loadChatHistory() async {
try {
- logger.i('正在加载聊天历史...');
+ Logger.i(TAG, '正在加载聊天历史...');
final result = await ChatStorage.getMessages(
sessionId: sessionId,
page: 1,
@@ -160,14 +160,14 @@ class AgentController extends GetxController {
historyMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
messages.assignAll(historyMessages);
- logger.i('成功加载 ${historyMessages.length} 条聊天记录');
+ Logger.i(TAG, '成功加载 ${historyMessages.length} 条聊天记录');
} else {
// 没有历史记录时添加欢迎消息
_addWelcomeMessage();
- logger.i('没有历史记录,添加欢迎消息');
+ Logger.i(TAG, '没有历史记录,添加欢迎消息');
}
} catch (e) {
- logger.e('加载聊天历史失败: $e');
+ Logger.e(TAG, '加载聊天历史失败: $e');
_addWelcomeMessage();
} finally {
isHistoryLoaded.value = true;
@@ -272,12 +272,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.ttsStarted:
isSpeaking.value = true;
+ Logger.i(TAG, 'TTS开始播放');
break;
case AgentServiceEventType.ttsCompleted:
case AgentServiceEventType.ttsStopped:
case AgentServiceEventType.ttsCanceled:
isSpeaking.value = false;
+ Logger.i(TAG, 'TTS停止播放');
break;
case AgentServiceEventType.imageProcessing:
@@ -339,7 +341,7 @@ class AgentController extends GetxController {
messages.refresh();
}
- logger.e('代理服务错误: ${event.data['message']}');
+ Logger.e(TAG, '代理服务错误: ${event.data['message']}');
break;
case AgentServiceEventType.autoStop:
@@ -373,14 +375,14 @@ class AgentController extends GetxController {
case AgentServiceEventType.functionCall:
final functionName = event.data['name'] ?? '';
final arguments = event.data['arguments'] ?? '';
- // logger.i('Fultter 开始调用 mcp: $functionName, $arguments');
+ // debugPrint('Fultter 开始调用 mcp: $functionName, $arguments');
break;
case AgentServiceEventType.functionCallResult:
-final responseId = event.data['responseId'] ?? '';
+ final responseId = event.data['responseId'] ?? '';
final result = event.data['result'] ?? '';
final functionCall = event.data['function_call'] ?? '';
- logger.i('Flutter 结束调用 mcp: $result, $functionCall');
+ Logger.i(TAG, 'Flutter 结束调用 mcp: $result, $functionCall');
if (result.isNotEmpty) {
final parsedResult = jsonDecode(result);
final meta = parsedResult["meta"];
@@ -390,7 +392,7 @@ final responseId = event.data['responseId'] ?? '';
try {
metaResult = jsonDecode(meta);
} catch (e) {
- print('解析meta数据失败: $e');
+ Logger.e(TAG, '解析meta数据失败: $e');
}
}
if(metaResult.isNotEmpty){
@@ -452,7 +454,7 @@ final responseId = event.data['responseId'] ?? '';
await AgentService.processTextInput(text,
speakResponse: isTtsEnabled.value);
} catch (e) {
- logger.e('发送文本消息失败: $e');
+ Logger.e(TAG, '发送文本消息失败: $e');
isProcessing.value = false;
}
}
@@ -460,7 +462,7 @@ final responseId = event.data['responseId'] ?? '';
// 发送图片消息
Future sendImageMessage(String imagePath, {String? text}) async {
if (imagePath.isEmpty || !File(imagePath).existsSync()) {
- logger.e('图片不存在: $imagePath');
+ Logger.e(TAG, '图片不存在: $imagePath');
return;
}
@@ -494,7 +496,7 @@ final responseId = event.data['responseId'] ?? '';
speakResponse: isTtsEnabled.value,
);
} catch (e) {
- logger.e('处理图片失败: $e');
+ Logger.e(TAG, '处理图片失败: $e');
isProcessing.value = false;
isImageProcessing.value = false;
}
@@ -531,36 +533,73 @@ final responseId = event.data['responseId'] ?? '';
// 检查麦克风权限
Future _checkMicrophonePermission() async {
try {
- PermissionStatus status = await Permission.microphone.status;
-
- // 如果已授权,直接返回 true
- if (status.isGranted) {
+ // 创建待请求权限列表
+ List permissionsToRequest = [];
+
+ if (Platform.isAndroid) {
+ // Android 需要考虑SDK版本不同的权限要求
+ permissionsToRequest.add(Permission.microphone);
+
+
+ } else if (Platform.isIOS) {
+ // iOS 只需要麦克风权限
+ permissionsToRequest.add(Permission.microphone);
+ }
+
+ // 批量请求所有需要的权限
+ final statuses = await permissionsToRequest.request();
+
+ // 检查是否所有权限都已授予
+ bool allGranted = true;
+ Permission? deniedPermission;
+
+ statuses.forEach((permission, status) {
+ if (!status.isGranted) {
+ allGranted = false;
+ deniedPermission = permission;
+ Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status');
+ }
+ });
+
+ // 单独检查麦克风权限状态,因为这是主要需求
+ final micStatus = await Permission.microphone.status;
+
+ // 如果所有权限都已授予,或至少麦克风权限已授予,返回true
+ if (allGranted || micStatus.isGranted) {
return true;
}
-
- // 如果被永久拒绝,提示用户前往设置页面
- if (status.isPermanentlyDenied) {
+
+ // 处理特定权限被永久拒绝的情况
+ if (micStatus.isPermanentlyDenied) {
_showPermissionDialog(true);
return false;
}
-
- // 如果之前拒绝但不是永久拒绝,或首次请求,尝试请求权限
- if (status.isDenied) {
- status = await Permission.microphone.request();
- if (status.isGranted) {
- return true;
- } else {
- _showPermissionDialog(false);
- return false;
- }
+
+ // 普通拒绝情况
+ if (deniedPermission != null) {
+ _showPermissionDialog(false);
}
-
+
return false;
} catch (e) {
- logger.e('检查麦克风权限失败: $e');
+ Logger.e(TAG, '检查麦克风权限失败: $e');
return false;
}
}
+
+ // 获取Android SDK版本
+ Future _getAndroidSdkVersion() async {
+ if (Platform.isAndroid) {
+ try {
+ // 这里我们简单返回一个较高的值,确保请求所有可能需要的权限
+ // 在实际应用中,你可能需要使用platform channel获取真实的SDK版本
+ return 31; // 对应Android 12
+ } catch (e) {
+ Logger.e(TAG, '获取Android SDK版本失败: $e');
+ }
+ }
+ return 0;
+ }
// 显示权限提示对话框
void _showPermissionDialog(bool isPermanent) {
@@ -605,7 +644,7 @@ final responseId = event.data['responseId'] ?? '';
try {
await AgentService.startConversation();
} catch (e) {
- logger.e('启动语音输入失败: $e');
+ Logger.e(TAG, '启动语音输入失败: $e');
}
}
@@ -616,7 +655,7 @@ final responseId = event.data['responseId'] ?? '';
try {
await AgentService.stopConversation();
} catch (e) {
- logger.e('停止语音输入失败: $e');
+ Logger.e(TAG, '停止语音输入失败: $e');
}
}
@@ -634,7 +673,7 @@ final responseId = event.data['responseId'] ?? '';
// 重置回复状态
_isNewAssistantResponse = true;
} catch (e) {
- logger.e('清除聊天历史失败: $e');
+ Logger.e(TAG, '清除聊天历史失败: $e');
}
}
@@ -656,7 +695,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true;
}
} catch (e) {
- logger.e('选择图片失败: $e');
+ Logger.e(TAG, '选择图片失败: $e');
}
}
@@ -678,7 +717,7 @@ final responseId = event.data['responseId'] ?? '';
isImageInputActive.value = true;
}
} catch (e) {
- logger.e('拍照失败: $e');
+ Logger.e(TAG, '拍照失败: $e');
}
}
@@ -720,7 +759,7 @@ final responseId = event.data['responseId'] ?? '';
// 播放音乐
void musicStatus() async {
var status = await _musiceManager.getStatus();
- // logger.i('获取当前音乐播放状态 $status');
+ // debugPrint('获取当前音乐播放状态 $status');
isPlaying.value = status['isPlaying'];
currentUrl.value = status['url'];
}
diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart
index 40e372c43..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
@@ -451,7 +437,7 @@ class ChatController extends GetxController {
}
isPlay.value = true;
} catch (e) {
- Logger.error('加载异常: $e');
+ Logger.error('加载异常: ${e.toString()}');
}
}
@@ -469,7 +455,7 @@ class ChatController extends GetxController {
isPlay.value = true;
audiourl.value = card['url'] as String;
} catch (e) {
- Logger.error('加载异常: $e');
+ Logger.error('加载异常: ${e.toString()}');
}
}
@@ -479,7 +465,7 @@ class ChatController extends GetxController {
_spotifService.pause();
isPlay.value = false;
} catch (e) {
- Logger.error('加载异常: $e');
+ Logger.error('加载异常: ${e.toString()}');
}
}
@@ -872,7 +858,7 @@ class ChatController extends GetxController {
}
// 删除数据库中的历史记录
ChatStorage.deleteMessages(sessionId: agent.id).catchError((e) {
- Logger.error('清空数据库聊天记录失败: $e');
+ Logger.error('清空数据库聊天记录失败: ${e.toString()}');
});
}
@@ -1003,4 +989,4 @@ class ChatController extends GetxController {
.abs() <
5000);
}
-}
+}
\ No newline at end of file
diff --git a/lib/modules/common/webview_page.dart b/lib/modules/common/webview_page.dart
index daa78ecf2..b80bf348b 100644
--- a/lib/modules/common/webview_page.dart
+++ b/lib/modules/common/webview_page.dart
@@ -23,13 +23,13 @@ class WebViewPage extends StatelessWidget {
centerTitle: true,
title: Text(title),
leading: IconButton(
- icon: Icon(
- Icons.arrow_back_ios,
- color: isDarkMode ? Colors.white : Colors.black,
- size: 20.sp,
+ icon: Icon(
+ Icons.arrow_back_ios,
+ color: isDarkMode ? Colors.white : Colors.black,
+ size: 20.sp,
+ ),
+ onPressed: () => Get.back(),
),
- onPressed: () => Get.back(),
- ),
backgroundColor: isDarkMode ? Colors.black : Colors.white,
foregroundColor: isDarkMode ? Colors.white : Colors.black,
elevation: 0,
@@ -39,4 +39,4 @@ class WebViewPage extends StatelessWidget {
),
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/modules/jieli_test/bindings/jieli_test_binding.dart b/lib/modules/jieli_test/bindings/jieli_test_binding.dart
deleted file mode 100644
index 9df61ef94..000000000
--- a/lib/modules/jieli_test/bindings/jieli_test_binding.dart
+++ /dev/null
@@ -1,11 +0,0 @@
-import 'package:get/get.dart';
-import '../controllers/jieli_test_controller.dart';
-
-class JieliTestBinding extends Bindings {
- @override
- void dependencies() {
- Get.lazyPut(
- () => JieliTestController(),
- );
- }
-}
\ No newline at end of file
diff --git a/lib/modules/jieli_test/controllers/jieli_test_controller.dart b/lib/modules/jieli_test/controllers/jieli_test_controller.dart
deleted file mode 100644
index b6d896836..000000000
--- a/lib/modules/jieli_test/controllers/jieli_test_controller.dart
+++ /dev/null
@@ -1,323 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-import 'package:flutter/material.dart' hide ConnectionState;
-import 'package:get/get.dart';
-import 'package:jieli/jieli.dart';
-
-/// 杰理测试控制器
-class JieliTestController extends GetxController {
- // Jieli插件实例
- late Jieli jieli;
-
- // 状态变量
- var isScanning = false.obs;
- var deviceInfo = '未获取'.obs;
- var connectStatus = '未连接'.obs;
- var currentDevice = Rx(null);
- var scannedDevices = RxList([]);
- var wakeupReceived = false.obs;
- var audioDataSize = 0.obs;
- var receivedFrames = 0.obs;
- var audioDataReceiving = false.obs;
- var volumeCommandResult = ''.obs;
-
- // 事件订阅
- StreamSubscription? _scanSubscription;
- StreamSubscription? _connectionSubscription;
- StreamSubscription? _wakeupSubscription;
- StreamSubscription? _audioSubscription;
- StreamSubscription? _commandResultSubscription;
-
- @override
- void onInit() {
- super.onInit();
- _initializeJieli();
- }
-
- @override
- void onClose() {
- _cancelAllSubscriptions();
- _disposeJieli();
- super.onClose();
- }
-
- /// 取消所有订阅
- void _cancelAllSubscriptions() {
- _scanSubscription?.cancel();
- _connectionSubscription?.cancel();
- _wakeupSubscription?.cancel();
- _audioSubscription?.cancel();
- _commandResultSubscription?.cancel();
- }
-
- /// 初始化杰理SDK
- void _initializeJieli() {
- try {
- jieli = Jieli();
- _setupEventListeners();
- } catch (e) {
- // 初始化错误处理
- }
- }
-
- /// 设置事件监听器
- void _setupEventListeners() {
- // 监听扫描结果
- _scanSubscription = jieli.scanResults.listen((devices) {
- scannedDevices.assignAll(devices);
- });
-
- // 监听连接状态
- _connectionSubscription = jieli.connectionState.listen((state) {
- switch (state) {
- case ConnectionState.connected:
- connectStatus.value = '已连接';
- break;
- case ConnectionState.disconnected:
- connectStatus.value = '已断开';
- currentDevice.value = null;
- break;
- case ConnectionState.connecting:
- connectStatus.value = '连接中...';
- break;
- case ConnectionState.disconnecting:
- connectStatus.value = '断开中...';
- break;
- case ConnectionState.error:
- connectStatus.value = '连接错误';
- break;
- }
- });
-
- // 监听唤醒信号
- _wakeupSubscription = jieli.wakeupStream.listen((wakeup) {
- wakeupReceived.value = true;
- Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号');
- });
-
- // 监听音频数据
- _audioSubscription = jieli.audioDataStream.listen((data) {
- // 更新音频数据统计
- audioDataSize.value += data.length;
- receivedFrames.value++;
- audioDataReceiving.value = true;
- });
-
- // 监听命令结果
- _commandResultSubscription = jieli.commandResultStream.listen((result) {
- // 处理音量调整命令结果
- if (result.commandType == Jieli.commandTypeVolume) {
- volumeCommandResult.value = result.success ? '音量调整成功' : '音量调整失败';
- Get.snackbar(
- result.success ? '音量调整成功' : '音量调整失败',
- result.success ? '设备已更新音量' : '设备未能更新音量',
- snackPosition: SnackPosition.BOTTOM,
- backgroundColor: result.success ? Colors.green.withOpacity(0.7) : Colors.red.withOpacity(0.7),
- colorText: Colors.white,
- duration: Duration(seconds: 2),
- );
- }
- });
- }
-
- /// 检查蓝牙状态
- Future checkBluetoothStatus() async {
- try {
- final isEnabled = await jieli.isBluetoothEnabled();
- if (!isEnabled) {
- return await jieli.requestBluetoothEnable();
- }
- return true;
- } catch (e) {
- return false;
- }
- }
-
- /// 开始扫描设备
- Future startScan() async {
- if (isScanning.value) return;
-
- final bluetoothReady = await checkBluetoothStatus();
- if (!bluetoothReady) {
- Get.snackbar('蓝牙未启用', '请启用蓝牙后再试');
- return;
- }
-
- try {
- isScanning.value = true;
-
- // 清空之前的设备列表
- scannedDevices.clear();
-
- // 开始扫描,10秒后自动停止
- final success = await jieli.startScan(scanTimeout: 10000);
-
- if (!success) {
- isScanning.value = false;
- Get.snackbar('扫描失败', '启动扫描设备失败,请检查蓝牙权限');
- }
-
- // 10秒后自动更新UI状态
- Future.delayed(Duration(milliseconds: 10000), () {
- isScanning.value = false;
- });
-
- } catch (e) {
- isScanning.value = false;
- Get.snackbar('扫描错误', e.toString());
- }
- }
-
- /// 停止扫描
- Future stopScan() async {
- if (!isScanning.value) return;
-
- try {
- await jieli.stopScan();
- isScanning.value = false;
- } catch (e) {
- // 停止扫描错误处理
- }
- }
-
- /// 连接到指定设备
- Future connectToDevice(BleDevice device) async {
- try {
- // 先断开当前连接
- if (currentDevice.value != null) {
- await disconnectDevice();
- }
-
- // 更新UI状态
- connectStatus.value = '连接中...';
-
- // 重置音频和唤醒相关的状态
- wakeupReceived.value = false;
- audioDataSize.value = 0;
- receivedFrames.value = 0;
- audioDataReceiving.value = false;
-
- // 连接设备
- final success = await jieli.connect(device.id);
-
- if (success) {
- // 连接成功,更新当前设备
- currentDevice.value = device;
- deviceInfo.value = '设备名称: ${device.name}\n设备ID: ${device.id}\nRSSI: ${device.rssi} dBm';
- } else {
- deviceInfo.value = '连接失败';
- }
- } catch (e) {
- deviceInfo.value = '连接错误: ${e.toString()}';
- }
- }
-
- /// 断开设备连接
- Future disconnectDevice() async {
- if (currentDevice.value == null) return;
-
- try {
- connectStatus.value = '断开中...';
-
- final success = await jieli.disconnect();
-
- if (success) {
- deviceInfo.value = '未连接设备';
- }
-
- } catch (e) {
- // 断开连接错误处理
- }
- }
-
- /// 发送开始录音命令
- Future sendStartRecordingCommand() async {
- if (currentDevice.value == null) {
- Get.snackbar('操作失败', '未连接设备');
- return;
- }
-
- try {
- final success = await jieli.sendStartRecordingCommand();
-
- if (success) {
- Get.snackbar('命令已发送', '已发送开始录音命令');
- } else {
- Get.snackbar('命令发送失败', '发送开始录音命令失败');
- }
- } catch (e) {
- Get.snackbar('命令错误', e.toString());
- }
- }
-
- /// 发送停止录音命令
- Future sendStopRecordingCommand() async {
- if (currentDevice.value == null) {
- Get.snackbar('操作失败', '未连接设备');
- return;
- }
-
- try {
- final success = await jieli.sendStopRecordingCommand();
-
- if (success) {
- Get.snackbar('命令已发送', '已发送停止录音命令');
- } else {
- Get.snackbar('命令发送失败', '发送停止录音命令失败');
- }
- } catch (e) {
- Get.snackbar('命令错误', e.toString());
- }
- }
-
- /// 增加音量
- Future increaseVolume() async {
- if (currentDevice.value == null) {
- Get.snackbar('操作失败', '未连接设备');
- return;
- }
-
- try {
- volumeCommandResult.value = '发送中...';
- final success = await jieli.increaseVolume();
-
- if (!success) {
- Get.snackbar('命令发送失败', '发送增加音量命令失败');
- volumeCommandResult.value = '发送失败';
- }
- } catch (e) {
- Get.snackbar('命令错误', e.toString());
- volumeCommandResult.value = '发送出错';
- }
- }
-
- /// 减小音量
- Future decreaseVolume() async {
- if (currentDevice.value == null) {
- Get.snackbar('操作失败', '未连接设备');
- return;
- }
-
- try {
- volumeCommandResult.value = '发送中...';
- final success = await jieli.decreaseVolume();
-
- if (!success) {
- Get.snackbar('命令发送失败', '发送减小音量命令失败');
- volumeCommandResult.value = '发送失败';
- }
- } catch (e) {
- Get.snackbar('命令错误', e.toString());
- volumeCommandResult.value = '发送出错';
- }
- }
-
- /// 清理资源
- void _disposeJieli() {
- try {
- jieli.dispose();
- } catch (e) {
- // 释放资源错误处理
- }
- }
-}
\ No newline at end of file
diff --git a/lib/modules/jieli_test/views/jieli_test_view.dart b/lib/modules/jieli_test/views/jieli_test_view.dart
deleted file mode 100644
index 46409651b..000000000
--- a/lib/modules/jieli_test/views/jieli_test_view.dart
+++ /dev/null
@@ -1,568 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:flutter_screenutil/flutter_screenutil.dart';
-import '../controllers/jieli_test_controller.dart';
-import 'package:jieli/jieli.dart';
-
-class JieliTestView extends GetView {
- const JieliTestView({Key? key}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- final isDarkMode = Theme.of(context).brightness == Brightness.dark;
-
- return Scaffold(
- backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
- appBar: AppBar(
- backgroundColor: isDarkMode ? Colors.black : Colors.white,
- elevation: 0.5,
- title: Text(
- '杰理蓝牙测试',
- style: TextStyle(
- fontSize: 17.sp,
- fontWeight: FontWeight.w600,
- color: isDarkMode ? Colors.white : Colors.black,
- ),
- ),
- centerTitle: true,
- leading: IconButton(
- icon: Icon(
- Icons.arrow_back_ios,
- color: isDarkMode ? Colors.white : Colors.black,
- size: 20.sp,
- ),
- onPressed: () => Get.back(),
- ),
- ),
- body: Padding(
- padding: EdgeInsets.all(16.w),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- _buildStatusCard(isDarkMode),
- SizedBox(height: 16.h),
- _buildActionButtons(isDarkMode),
- SizedBox(height: 16.h),
- _buildDeviceInfo(isDarkMode),
- SizedBox(height: 16.h),
- _buildDevicesList(isDarkMode),
- ],
- ),
- ),
- );
- }
-
- Widget _buildStatusCard(bool isDarkMode) {
- return Container(
- padding: EdgeInsets.all(16.w),
- decoration: BoxDecoration(
- color: isDarkMode ? Colors.grey[900] : Colors.white,
- borderRadius: BorderRadius.circular(12.r),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.05),
- blurRadius: 10,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- Text(
- '连接状态: ',
- style: TextStyle(
- fontSize: 14.sp,
- color: isDarkMode ? Colors.white : Colors.black87,
- ),
- ),
- Obx(() => Text(
- controller.connectStatus.value,
- style: TextStyle(
- fontSize: 14.sp,
- fontWeight: FontWeight.bold,
- color: _getStatusColor(controller.connectStatus.value),
- ),
- )),
- ],
- ),
- SizedBox(height: 8.h),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Obx(() => _buildStatusChip(
- label: '唤醒信号',
- isActive: controller.wakeupReceived.value,
- activeColor: Colors.orange,
- isDarkMode: isDarkMode,
- )),
- Obx(() => _buildStatusChip(
- label: '音频数据',
- isActive: controller.audioDataReceiving.value,
- activeColor: Colors.green,
- isDarkMode: isDarkMode,
- )),
- ],
- ),
- SizedBox(height: 8.h),
- Obx(() => controller.audioDataReceiving.value
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '音频数据统计:',
- style: TextStyle(
- fontSize: 12.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- SizedBox(height: 4.h),
- Text(
- '已接收 ${(controller.audioDataSize.value / 1024).toStringAsFixed(2)} KB',
- style: TextStyle(
- fontSize: 12.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- Text(
- '共 ${controller.receivedFrames.value} 帧',
- style: TextStyle(
- fontSize: 12.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- ],
- )
- : SizedBox.shrink()
- ),
- ],
- ),
- );
- }
-
- Widget _buildStatusChip({
- required String label,
- required bool isActive,
- required Color activeColor,
- required bool isDarkMode,
- }) {
- return Container(
- padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
- decoration: BoxDecoration(
- color: isActive
- ? activeColor
- : (isDarkMode ? Colors.grey[800] : Colors.grey[200]),
- borderRadius: BorderRadius.circular(16.r),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- width: 8.w,
- height: 8.w,
- decoration: BoxDecoration(
- color: isActive ? Colors.white : Colors.grey,
- shape: BoxShape.circle,
- ),
- ),
- SizedBox(width: 6.w),
- Text(
- label,
- style: TextStyle(
- fontSize: 12.sp,
- color: isActive
- ? Colors.white
- : (isDarkMode ? Colors.white70 : Colors.black54),
- fontWeight: isActive ? FontWeight.bold : FontWeight.normal,
- ),
- ),
- ],
- ),
- );
- }
-
- Widget _buildActionButtons(bool isDarkMode) {
- return Column(
- children: [
- Row(
- children: [
- Expanded(
- child: Obx(() => ElevatedButton.icon(
- onPressed: controller.isScanning.value
- ? controller.stopScan
- : controller.startScan,
- icon: Icon(
- controller.isScanning.value ? Icons.stop : Icons.bluetooth_searching,
- size: 18.sp,
- ),
- label: Text(
- controller.isScanning.value ? '停止扫描' : '扫描设备',
- style: TextStyle(fontSize: 14.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: controller.isScanning.value
- ? Colors.red[600]
- : Colors.blue[600],
- padding: EdgeInsets.symmetric(vertical: 12.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- )),
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(
- children: [
- Expanded(
- child: ElevatedButton.icon(
- onPressed: controller.sendStartRecordingCommand,
- icon: Icon(
- Icons.mic,
- size: 18.sp,
- ),
- label: Text(
- '开始录音',
- style: TextStyle(fontSize: 14.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.green[600],
- padding: EdgeInsets.symmetric(vertical: 12.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- ),
- ),
- SizedBox(width: 8.w),
- Expanded(
- child: ElevatedButton.icon(
- onPressed: controller.sendStopRecordingCommand,
- icon: Icon(
- Icons.stop_circle,
- size: 18.sp,
- ),
- label: Text(
- '停止录音',
- style: TextStyle(fontSize: 14.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.red[600],
- padding: EdgeInsets.symmetric(vertical: 12.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- ),
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Row(
- children: [
- Expanded(
- child: ElevatedButton.icon(
- onPressed: controller.increaseVolume,
- icon: Icon(
- Icons.volume_up,
- size: 18.sp,
- ),
- label: Text(
- '增加音量',
- style: TextStyle(fontSize: 14.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.blue[600],
- padding: EdgeInsets.symmetric(vertical: 12.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- ),
- ),
- SizedBox(width: 8.w),
- Expanded(
- child: ElevatedButton.icon(
- onPressed: controller.decreaseVolume,
- icon: Icon(
- Icons.volume_down,
- size: 18.sp,
- ),
- label: Text(
- '减小音量',
- style: TextStyle(fontSize: 14.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.orange[600],
- padding: EdgeInsets.symmetric(vertical: 12.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- ),
- ),
- ],
- ),
- SizedBox(height: 8.h),
- Obx(() => controller.volumeCommandResult.value.isNotEmpty
- ? Container(
- width: double.infinity,
- padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w),
- decoration: BoxDecoration(
- color: controller.volumeCommandResult.value.contains('成功')
- ? Colors.green[100]
- : (controller.volumeCommandResult.value == '发送中...'
- ? Colors.blue[100]
- : Colors.red[100]),
- borderRadius: BorderRadius.circular(8.r),
- ),
- child: Text(
- '音量控制: ${controller.volumeCommandResult.value}',
- style: TextStyle(
- fontSize: 13.sp,
- color: controller.volumeCommandResult.value.contains('成功')
- ? Colors.green[900]
- : (controller.volumeCommandResult.value == '发送中...'
- ? Colors.blue[900]
- : Colors.red[900]),
- ),
- textAlign: TextAlign.center,
- ),
- )
- : SizedBox.shrink()
- ),
- ],
- );
- }
-
- Widget _buildDeviceInfo(bool isDarkMode) {
- return Container(
- padding: EdgeInsets.all(16.w),
- decoration: BoxDecoration(
- color: isDarkMode ? Colors.grey[900] : Colors.white,
- borderRadius: BorderRadius.circular(12.r),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.05),
- blurRadius: 10,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '设备信息:',
- style: TextStyle(
- fontSize: 14.sp,
- color: isDarkMode ? Colors.white : Colors.black87,
- ),
- ),
- SizedBox(height: 8.h),
- Container(
- padding: EdgeInsets.all(8.w),
- decoration: BoxDecoration(
- color: isDarkMode ? Colors.black : Colors.grey[100],
- borderRadius: BorderRadius.circular(8.r),
- ),
- child: Obx(() => Text(
- controller.deviceInfo.value,
- style: TextStyle(
- fontSize: 13.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- height: 1.4,
- ),
- )),
- ),
- SizedBox(height: 16.h),
- Obx(() => controller.currentDevice.value != null
- ? ElevatedButton.icon(
- onPressed: controller.disconnectDevice,
- icon: Icon(
- Icons.bluetooth_disabled,
- size: 16.sp,
- ),
- label: Text(
- '断开连接',
- style: TextStyle(fontSize: 13.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.red[600],
- padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8.r),
- ),
- ),
- )
- : SizedBox.shrink()
- ),
- ],
- ),
- );
- }
-
- Widget _buildDevicesList(bool isDarkMode) {
- return Expanded(
- child: Container(
- decoration: BoxDecoration(
- color: isDarkMode ? Colors.grey[900] : Colors.white,
- borderRadius: BorderRadius.circular(12.r),
- boxShadow: [
- BoxShadow(
- color: Colors.black.withOpacity(0.05),
- blurRadius: 10,
- offset: const Offset(0, 2),
- ),
- ],
- ),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Padding(
- padding: EdgeInsets.all(16.w),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Text(
- '扫描到的设备',
- style: TextStyle(
- fontSize: 16.sp,
- fontWeight: FontWeight.w600,
- color: isDarkMode ? Colors.white : Colors.black87,
- ),
- ),
- Obx(() => Text(
- '${controller.scannedDevices.length}个设备',
- style: TextStyle(
- fontSize: 12.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- )),
- ],
- ),
- ),
- Divider(height: 1, thickness: 1, color: isDarkMode ? Colors.grey[800] : Colors.grey[200]),
- Expanded(
- child: Obx(() => controller.scannedDevices.isEmpty
- ? Center(
- child: controller.isScanning.value
- ? Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- CircularProgressIndicator(),
- SizedBox(height: 16.h),
- Text(
- '正在扫描...',
- style: TextStyle(
- fontSize: 14.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- ],
- )
- : Text(
- '未扫描到设备',
- style: TextStyle(
- fontSize: 14.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- )
- : ListView.separated(
- padding: EdgeInsets.symmetric(vertical: 8.h),
- itemCount: controller.scannedDevices.length,
- separatorBuilder: (context, index) => Divider(
- height: 1,
- thickness: 1,
- color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
- ),
- itemBuilder: (context, index) {
- final device = controller.scannedDevices[index];
- return ListTile(
- dense: true,
- title: Text(
- device.name.isEmpty ? '未知设备' : device.name,
- style: TextStyle(
- fontSize: 14.sp,
- fontWeight: FontWeight.w500,
- color: isDarkMode ? Colors.white : Colors.black87,
- ),
- ),
- subtitle: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- '设备ID: ${device.id}',
- style: TextStyle(
- fontSize: 11.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- Text(
- 'RSSI: ${device.rssi} dBm',
- style: TextStyle(
- fontSize: 11.sp,
- color: isDarkMode ? Colors.white70 : Colors.black54,
- ),
- ),
- ],
- ),
- trailing: Obx(() => controller.currentDevice.value?.id == device.id
- ? Chip(
- label: Text(
- '已连接',
- style: TextStyle(
- fontSize: 12.sp,
- color: Colors.white,
- ),
- ),
- backgroundColor: Colors.green,
- padding: EdgeInsets.symmetric(horizontal: 8.w),
- )
- : ElevatedButton(
- onPressed: () => controller.connectToDevice(device),
- child: Text(
- '连接',
- style: TextStyle(fontSize: 12.sp),
- ),
- style: ElevatedButton.styleFrom(
- backgroundColor: Colors.green[600],
- padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(6.r),
- ),
- ),
- ),
- ),
- contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
- isThreeLine: true,
- );
- },
- ),
- ),
- ),
- ],
- ),
- ),
- );
- }
-
- Color _getStatusColor(String status) {
- switch (status) {
- case '连接中...':
- return Colors.orange;
- case '已连接':
- return Colors.green;
- case '连接失败':
- return Colors.red;
- case '已断开':
- return Colors.grey;
- default:
- return Colors.grey;
- }
- }
-}
\ No newline at end of file
diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart
index f60bfd8d6..15275b1e7 100644
--- a/lib/modules/meeting/controllers/meeting_details_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_details_controller.dart
@@ -106,7 +106,7 @@ class MeetingDetailsController extends GetxController
final GetStorage storage = GetStorage();
Map userInfo = storage.read("user_info");
_userId = userInfo['user']['uid'];
- _systemPrompt = AppConfig.agentsystemPrompt("personal_assistant") ?? '';
+ _systemPrompt = AppConfig.agentsystemPrompt("meet_assistant") ?? '';
}
void _getDataDetails() async {
@@ -258,7 +258,7 @@ class MeetingDetailsController extends GetxController
Logger.debug('taskId: $taskId');
return taskId;
} catch (e) {
- Logger.error('Submit error: $e');
+ Logger.error('Submit error: ${e.toString()}');
rethrow;
}
}
@@ -290,7 +290,7 @@ class MeetingDetailsController extends GetxController
final respDic = jsonDecode(response.body);
return respDic;
} catch (e) {
- Logger.error('Query error: $e');
+ Logger.error('Query error: ${e.toString()}');
rethrow;
}
}
@@ -322,7 +322,7 @@ class MeetingDetailsController extends GetxController
}
}
} catch (e) {
- Logger.error('Error occurred: $e');
+ Logger.error('Error occurred: ${e.toString()}');
}
}
@@ -335,7 +335,9 @@ class MeetingDetailsController extends GetxController
if (element['additions'].isNotEmpty) {
speaker = 'Speaker ${element['additions']['speaker']}';
text += '$speaker:';
- personnel += '[$speaker] ';
+ if (!personnel.contains(speaker)) {
+ personnel += '[$speaker] ';
+ }
}
list.add({
'meetingid': id,
diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart
index 4e974e27d..d1b2ec7a5 100644
--- a/lib/modules/meeting/views/meeting_details_view.dart
+++ b/lib/modules/meeting/views/meeting_details_view.dart
@@ -22,139 +22,7 @@ class MeetingDetailsView extends GetView {
() => Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(isDarkMode),
- body: NestedScrollView(
- controller: controller.scrollController,
- headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
- return [
- SliverAppBar(
- automaticallyImplyLeading: false,
- expandedHeight: 210.w,
- backgroundColor: Colors.transparent,
- flexibleSpace: FlexibleSpaceBar(
- collapseMode: CollapseMode.pin,
- background: Align(
- alignment: Alignment.topCenter,
- child: Container(
- height: 200.w,
- decoration: BoxDecoration(
- color: Colors.grey[200]!,
- borderRadius: BorderRadius.vertical(
- bottom: Radius.circular(20.r),
- ),
- ),
- child: Column(
- children: [
- Container(
- margin: EdgeInsets.only(bottom: 5.w),
- decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(8.r),
- ),
- child: AudioFileWaveforms(
- size: Size(350.w, 120.w),
- playerController: controller.playerController,
- playerWaveStyle: PlayerWaveStyle(
- fixedWaveColor: Colors.grey[300]!,
- liveWaveColor: Colors.red,
- seekLineColor: Colors.red,
- scaleFactor: 600,
- ),
- ),
- ),
- SizedBox(
- width: 350.w,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- StreamBuilder(
- stream: controller.playerController
- .onCurrentDurationChanged,
- builder: (context, snapshot) {
- final position = snapshot.data ?? 0;
- return Text(
- DateUtil.formatDateMs(
- position,
- format: 'HH:mm:ss',
- isUtc: true,
- ),
- style: TextStyle(
- color: isDarkMode
- ? Colors.grey[400]
- : Colors.grey[500],
- fontSize: 12.sp,
- ),
- );
- },
- ),
- Obx(
- () => Text(
- DateUtil.formatDateMs(
- controller.durationMax.value,
- format: 'HH:mm:ss',
- isUtc: true,
- ),
- style: TextStyle(
- color: isDarkMode
- ? Colors.grey[400]
- : Colors.grey[500],
- fontSize: 12.sp,
- ),
- ),
- ),
- ],
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: [
- Icon(
- Icons.replay_10,
- color: Colors.black,
- size: 30.w,
- ),
- StreamBuilder(
- stream: controller
- .playerController.onPlayerStateChanged,
- builder: (context, snapshot) {
- bool isPlay =
- snapshot.data?.isPlaying ?? false;
- return GestureDetector(
- onTap: () {
- if (isPlay) {
- controller.playerController
- .pausePlayer();
- } else {
- controller.playerController
- .startPlayer();
- }
- },
- child: Icon(
- isPlay
- ? Icons.pause_circle
- : Icons.play_circle,
- color: Colors.black,
- size: 48.w,
- ),
- );
- },
- ),
- Icon(
- Icons.forward_10,
- color: Colors.black,
- size: 30.w,
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ];
- },
- body: _buildBody(context, isDarkMode),
- ),
+ body: _buildBody(context, isDarkMode),
),
);
}
@@ -295,6 +163,7 @@ class MeetingDetailsView extends GetView {
Widget _buildBody(BuildContext context, bool isDarkMode) {
return Column(
children: [
+ _soundWave(isDarkMode),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w),
margin: EdgeInsets.symmetric(vertical: 10.w),
@@ -328,6 +197,113 @@ class MeetingDetailsView extends GetView {
);
}
+ Widget _soundWave(bool isDarkMode) {
+ return Container(
+ height: 200.w,
+ margin: EdgeInsets.only(bottom: 10.w),
+ decoration: BoxDecoration(
+ color: Colors.grey[200]!,
+ borderRadius: BorderRadius.vertical(
+ bottom: Radius.circular(20.r),
+ ),
+ ),
+ child: Column(
+ children: [
+ Container(
+ margin: EdgeInsets.only(bottom: 5.w),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ borderRadius: BorderRadius.circular(8.r),
+ ),
+ child: AudioFileWaveforms(
+ size: Size(350.w, 120.w),
+ playerController: controller.playerController,
+ playerWaveStyle: PlayerWaveStyle(
+ fixedWaveColor: Colors.grey[300]!,
+ liveWaveColor: Colors.red,
+ seekLineColor: Colors.red,
+ scaleFactor: 600,
+ ),
+ ),
+ ),
+ SizedBox(
+ width: 350.w,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ StreamBuilder(
+ stream: controller.playerController.onCurrentDurationChanged,
+ builder: (context, snapshot) {
+ final position = snapshot.data ?? 0;
+ return Text(
+ DateUtil.formatDateMs(
+ position,
+ format: 'HH:mm:ss',
+ isUtc: true,
+ ),
+ style: TextStyle(
+ color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
+ fontSize: 12.sp,
+ ),
+ );
+ },
+ ),
+ Obx(
+ () => Text(
+ DateUtil.formatDateMs(
+ controller.durationMax.value,
+ format: 'HH:mm:ss',
+ isUtc: true,
+ ),
+ style: TextStyle(
+ color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
+ fontSize: 12.sp,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ Icon(
+ Icons.replay_10,
+ color: Colors.black,
+ size: 30.w,
+ ),
+ StreamBuilder(
+ stream: controller.playerController.onPlayerStateChanged,
+ builder: (context, snapshot) {
+ bool isPlay = snapshot.data?.isPlaying ?? false;
+ return GestureDetector(
+ onTap: () {
+ if (isPlay) {
+ controller.playerController.pausePlayer();
+ } else {
+ controller.playerController.startPlayer();
+ }
+ },
+ child: Icon(
+ isPlay ? Icons.pause_circle : Icons.play_circle,
+ color: Colors.black,
+ size: 48.w,
+ ),
+ );
+ },
+ ),
+ Icon(
+ Icons.forward_10,
+ color: Colors.black,
+ size: 30.w,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+
Widget _iconItem(IconData icon, String title, int index) {
bool isActive = controller.currentIndex.value == index;
diff --git a/lib/modules/meeting/views/mind_map_tab.dart b/lib/modules/meeting/views/mind_map_tab.dart
index a4e783ba9..1f93e04fa 100644
--- a/lib/modules/meeting/views/mind_map_tab.dart
+++ b/lib/modules/meeting/views/mind_map_tab.dart
@@ -1,12 +1,99 @@
import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:webview_flutter/webview_flutter.dart';
-class MindMapTab extends StatelessWidget {
+import '../controllers/meeting_details_controller.dart';
+
+class MindMapTab extends StatefulWidget {
const MindMapTab({super.key});
+ @override
+ State createState() => _MindMapTabState();
+}
+
+class _MindMapTabState extends State {
+ late final WebViewController _controller;
+ final _meetingDetailsController = Get.find();
+
+ @override
+ void initState() {
+ super.initState();
+ String html = '''
+
+
+
+
+
+
+ MindMap
+
+
+
+
+
+
+
+
+
+
+
+
+ ''';
+ _controller = WebViewController()
+ ..setJavaScriptMode(JavaScriptMode.unrestricted)
+ ..setBackgroundColor(Colors.transparent)
+ ..loadHtmlString(html);
+ }
+
@override
Widget build(BuildContext context) {
- return SingleChildScrollView(
- child: Text(''),
+ return WebViewWidget(
+ controller: _controller,
);
}
}
diff --git a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
index 4aa7f04f6..992ca6838 100644
--- a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
+++ b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
@@ -22,7 +22,7 @@ class OtaUpgradeController extends GetxController {
final isUpdateAvailable = true.obs;
final RxDouble progress = 0.0.obs;
-
+ final JlOta jla = JlOta();
// 蓝牙服务
final _bleManager = Get.find();
@override
@@ -53,68 +53,65 @@ class OtaUpgradeController extends GetxController {
}
Future _getCurrentVersion() async {
- JlOta().getPlatformVersion();
- //try {
-
- // // 添加获取设备信息的重试机制
- // DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
-
- // // 第一次检查设备信息
- // if (deviceInfo == null) {
- // // 显示加载提示
- // isFetchingDeviceInfo.value = true;
-
- // // 尝试重新获取设备信息
- // await _bleManager.fetchDeviceInfo();
-
- // // 等待后再次获取设备信息
- // deviceInfo = _bleManager.deviceInfo.value;
- // isFetchingDeviceInfo.value = false;
-
- // // 仍然为空则抛出异常
- // if (deviceInfo == null) {
- // throw const FormatException('无法获取设备信息,请检查设备连接');
- // }
- // }
-
- // // 检查固件信息有效性
- // if (deviceInfo.firmwareVersion.isEmpty
- // // ||
- // // deviceInfo.firmwareProductId.isEmpty
- // ) {
- // throw const FormatException('设备固件信息不完整');
- // }
-
- // // 正常流程
- // currentVersion.value = "0"; //deviceInfo.firmwareVersion;
-
- // // API请求
- // final response = await Api.getequipmentinfo({
- // "id": "s3",
- // });
-
- // // 处理API响应
- // if (response['info'] == null) {
- // throw const FormatException('API 响应格式错误');
- // }
-
- // final resp = OtaConfigResp.fromJson(response);
- // latestVersion.value = resp.systemversion!;
- // updatepackageaddress = resp.updatepackageaddress!;
-
- // // 版本检查
- // isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
- // } catch (e) {
- // // 统一错误处理
- // errorMessage.value = e.toString();
- // Get.snackbar('错误', e.toString(),
- // snackPosition: SnackPosition.BOTTOM,
- // backgroundColor: Colors.red,
- // colorText: Colors.white);
- // // 重要:重新抛出异常以便上层处理(可选)
- // } finally {
- // isFetchingDeviceInfo.value = false;
- // }
+ try {
+ // 添加获取设备信息的重试机制
+ DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
+
+ // 第一次检查设备信息
+ if (deviceInfo == null) {
+ // 显示加载提示
+ isFetchingDeviceInfo.value = true;
+
+ // 尝试重新获取设备信息
+ await _bleManager.fetchDeviceInfo();
+
+ // 等待后再次获取设备信息
+ deviceInfo = _bleManager.deviceInfo.value;
+ isFetchingDeviceInfo.value = false;
+
+ // 仍然为空则抛出异常
+ if (deviceInfo == null) {
+ throw const FormatException('无法获取设备信息,请检查设备连接');
+ }
+ }
+
+ // 检查固件信息有效性
+ if (deviceInfo.firmwareVersion.isEmpty ||
+ deviceInfo.firmwareProductId.isEmpty) {
+ throw const FormatException('设备固件信息不完整');
+ }
+
+ // 正常流程
+ currentVersion.value = deviceInfo.firmwareVersion;
+ print('getequipmentinfo,id: ${deviceInfo.firmwareProductId}');
+ // API请求
+ final response = await Api.getequipmentinfo({
+ "id": deviceInfo.firmwareProductId,
+ });
+
+ // 处理API响应
+ if (response['info'] == null) {
+ throw const FormatException('API 响应格式错误');
+ }
+
+ final resp = OtaConfigResp.fromJson(response);
+ print('getequipmentinfo,resp.systemversion: ${resp.systemversion}');
+ latestVersion.value = "1";
+ updatepackageaddress = resp.updatepackageaddress!;
+
+ // 版本检查
+ isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
+ } catch (e) {
+ // 统一错误处理
+ errorMessage.value = e.toString();
+ Get.snackbar('错误', e.toString(),
+ snackPosition: SnackPosition.BOTTOM,
+ backgroundColor: Colors.red,
+ colorText: Colors.white);
+ // 重要:重新抛出异常以便上层处理(可选)
+ } finally {
+ isFetchingDeviceInfo.value = false;
+ }
}
// 版本比较方法优化
@@ -295,6 +292,7 @@ class OtaUpgradeController extends GetxController {
// 开始升级
void _installApp(String path) async {
+ jla.getPlatformVersion();
// JlOtaPlugin()
// final File file = File(path);
// if (await file.exists()) {
diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart
index 55311e0d0..7f11158bf 100644
--- a/lib/modules/pairing/controllers/pairing_controller.dart
+++ b/lib/modules/pairing/controllers/pairing_controller.dart
@@ -9,13 +9,12 @@ 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 {
static PairingController get to => Get.find();
- // 需要设备
- final bool needdeivice = false;
// 状态标志
final isLoading = false.obs;
@@ -64,6 +63,9 @@ class PairingController extends GetxController {
// BLE服务
final _bleService = BleService();
+ // 日志tag
+ static const _tag = 'PairingController';
+
@override
void onInit() {
super.onInit();
@@ -83,6 +85,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;
@@ -96,7 +103,7 @@ class PairingController extends GetxController {
// 检查蓝牙状态并刷新设备
await _checkBluetoothAndRefresh();
} catch (e) {
- Logger.error('初始化配对控制器失败: $e');
+ Logger.e(_tag, '初始化配对控制器失败: ${e.toString()}');
statusDescription.value = 'initializationFailure'.tr;
} finally {
isLoading.value = false;
@@ -128,13 +135,12 @@ class PairingController extends GetxController {
/// 检查设备配对状态
Future _checkDevicePairingStatus() async {
- // 只在Android平台且设备已连接时执行
- if (!Platform.isAndroid || !isDeviceConnected) return;
+ if (!isDeviceConnected) return;
try {
final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) {
- Logger.info('设备不支持Companion功能');
+ Logger.i(_tag, '设备不支持Companion功能');
_companionAssociated.value = true; // 不支持则视为已配对
_userRejectedAssociation.value = false; // 重置拒绝状态
return;
@@ -143,16 +149,16 @@ class PairingController extends GetxController {
final pairedMac = await _bleService.getPairedMacAddress();
if (pairedMac == null) {
// 没有配对记录,需要开始配对
- Logger.info('无配对记录,需要开始设备配对');
+ Logger.i(_tag, '无配对记录,需要开始设备配对');
return;
}
- Logger.info('发现已关联的Companion设备: $pairedMac');
+ Logger.i(_tag, '发现已关联的Companion设备: $pairedMac');
// 检查当前连接设备与已保存的设备是否一致
if (currentDevice!.address != pairedMac) {
// MAC地址不匹配
- Logger.warning('当前连接的设备与已配对的DeepSound设备不符');
+ Logger.w(_tag, '当前连接的设备与已配对的DeepSound设备不符');
statusDescription.value = 'pleaseConnectPairedDevice'.tr;
return;
}
@@ -164,13 +170,13 @@ class PairingController extends GetxController {
}
// 设备匹配且合规,标记为已配对
- Logger.info('当前设备与已配对设备匹配');
+ Logger.i(_tag, '当前设备与已配对设备匹配');
_companionAssociated.value = true;
_userRejectedAssociation.value = false; // 重置拒绝状态
await _bleService.registerBackgroundScan();
_autoNavigateToHome();
} catch (e) {
- Logger.error('检查设备配对状态失败: $e');
+ Logger.e(_tag, '检查设备配对状态失败: ${e.toString()}');
}
}
@@ -186,27 +192,121 @@ class PairingController extends GetxController {
if (Platform.isAndroid) {
permissionsToRequest.add(Permission.locationWhenInUse);
- permissionsToRequest.add(Permission.microphone);
+ // permissionsToRequest.add(Permission.microphone);
if (await _getAndroidSdkVersion() >= 31) {
permissionsToRequest.add(Permission.bluetoothScan);
permissionsToRequest.add(Permission.bluetoothConnect);
}
} else if (Platform.isIOS) {
+ // iOS需要请求的权限
permissionsToRequest.add(Permission.bluetooth);
+ permissionsToRequest.add(Permission.location);
+
+ // 如果需要麦克风权限(例如语音功能)
+ // permissionsToRequest.add(Permission.microphone);
+
+ try {
+ // 先尝试请求权限
+ if (permissionsToRequest.isNotEmpty) {
+ statuses = await permissionsToRequest.request();
+ bool allGranted = true;
+ statuses.forEach((permission, status) {
+ if (!status.isGranted) {
+ allGranted = false;
+ Logger.w(_tag, 'iOS权限 ${permission.toString()} 未被授予: $status');
+
+ // 特殊处理蓝牙权限
+ if (permission == Permission.bluetooth) {
+ Logger.w(_tag, 'iOS蓝牙权限未授予,状态: $status');
+ }
+ }
+ });
+
+ // 如果有权限未授予,但不是永久拒绝,继续尝试
+ if (!allGranted) {
+ bool hasPermanentlyDenied = statuses.values.any((status) => status.isPermanentlyDenied);
+ if (hasPermanentlyDenied) {
+ // 有权限被永久拒绝,需要引导用户去设置
+ _showPermissionSettingsDialog('部分权限被永久拒绝,请在设置中启用');
+ return false;
+ }
+ }
+ }
+
+ // 直接尝试使用蓝牙功能,这会触发iOS原生权限请求
+ bool isEnabled = await _bluetoothService.isBluetoothEnabled();
+ Logger.i(_tag, 'iOS蓝牙状态检查: ${isEnabled ? "已开启" : "未开启"}');
+
+ // 检测AccessorySetupKit支持情况(iOS 18+特性)
+ bool companionSupported = await _bleService.isCompanionSupported();
+ Logger.i(_tag, 'iOS Companion支持检查: ${companionSupported ? "支持" : "不支持"}');
+
+ if (companionSupported) {
+ // 针对iOS 18+,尝试预热AccessorySetupKit框架
+ try {
+ // 此调用会触发AccessorySetupKit相关权限请求
+ final pairedMac = await _bleService.getPairedMacAddress();
+ Logger.i(_tag, '获取已配对设备MAC: ${pairedMac ?? "无"}');
+ } catch (e) {
+ Logger.w(_tag, 'iOS 18+ AccessorySetupKit初始化失败: ${e.toString()}');
+ }
+ }
+
+ // 在iOS上视为权限已授予
+ _hasPermissions.value = true;
+ return true;
+ } catch (e) {
+ Logger.e(_tag, "iOS蓝牙初始化失败: ${e.toString()}");
+
+ // 显示自定义对话框引导用户
+ _showPermissionSettingsDialog('需要开启蓝牙权限才能配对设备');
+ return false;
+ }
}
-
- 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.w(_tag, '权限 ${permission.toString()} 未被授予: $status');
+ }
+ });
+
+ if (!allGranted) {
+ return false;
}
- });
+ }
- _hasPermissions.value = allGranted;
- return allGranted;
+ _hasPermissions.value = true;
+ return true;
+ }
+
+ /// 显示权限设置对话框
+ void _showPermissionSettingsDialog(String message) {
+ Get.dialog(
+ AlertDialog(
+ title: Text('需要权限'),
+ content: Text(message),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text('稍后'),
+ ),
+ TextButton(
+ onPressed: () {
+ Get.back();
+ openBluetoothSettings();
+ },
+ child: Text('去设置'),
+ ),
+ ],
+ ),
+ barrierDismissible: false,
+ );
}
// 辅助方法获取Android SDK版本
@@ -230,14 +330,11 @@ class PairingController extends GetxController {
_deviceWorker =
ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
if (device != null && !_isNavigatingToHome) {
- Logger.info('配对页面发现设备已连接: ${device.name}');
+ Logger.i(_tag, '配对页面发现设备已连接: ${device.name}');
_handleDeviceConnected();
}
_updateStatusDescription();
});
- if (needdeivice) {
- _handleDeviceConnected();
- }
}
/// 处理设备连接成功事件
@@ -296,9 +393,8 @@ class PairingController extends GetxController {
/// 启动Companion设备关联流程
Future _startCompanionAssociation() async {
- // 如果不是Android平台,已跳转,已完成关联或正在进行关联,直接返回
- if (!Platform.isAndroid ||
- _isNavigatingToHome ||
+ // 如果已跳转,已完成关联或正在进行关联,直接返回
+ if (_isNavigatingToHome ||
_companionAssociated.value ||
_isAssociating ||
!isDeviceConnected) {
@@ -316,7 +412,7 @@ class PairingController extends GetxController {
// 检查是否支持Companion功能
final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) {
- Logger.info('设备不支持Companion功能,跳过关联');
+ Logger.i(_tag, '设备不支持Companion功能,跳过关联');
_companionAssociated.value = true;
_autoNavigateToHome();
return;
@@ -329,7 +425,7 @@ class PairingController extends GetxController {
return;
}
- Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}');
+ Logger.i(_tag, '开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}');
var success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name,
@@ -338,7 +434,7 @@ class PairingController extends GetxController {
_isAssociating = false;
if (success) {
- Logger.info('Companion设备关联成功');
+ Logger.i(_tag, 'Companion设备关联成功');
_companionAssociated.value = true;
_userRejectedAssociation.value = false;
@@ -349,14 +445,14 @@ class PairingController extends GetxController {
// 关联成功后跳转
_autoNavigateToHome();
} else {
- Logger.error('Companion设备关联失败或被用户拒绝');
+ Logger.e(_tag, 'Companion设备关联失败或被用户拒绝');
// 关联失败 - 用户拒绝关联
_userRejectedAssociation.value = true;
// 确保UI状态更新
_updateStatusDescription();
}
} catch (e) {
- Logger.error('Companion设备关联过程发生错误: $e');
+ Logger.e(_tag, 'Companion设备关联过程发生错误: ${e.toString()}');
_isAssociating = false;
_userRejectedAssociation.value = true;
_updateStatusDescription();
@@ -365,13 +461,13 @@ class PairingController extends GetxController {
/// 自动跳转到主页
Future _autoNavigateToHome() async {
- if (needdeivice && (_isNavigatingToHome || !isDeviceConnected)) return;
+ if (_isNavigatingToHome || !isDeviceConnected) return;
// 启动BLE后台服务
await _startAgentService();
_isNavigatingToHome = true;
- Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
+ Logger.i(_tag, '已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home);
@@ -381,12 +477,13 @@ class PairingController extends GetxController {
/// 启动BLE后台服务
Future _startAgentService() async {
try {
- if (needdeivice && !isDeviceConnected) return;
+ // 注释掉设备连接检查,确保无论是否连接设备都能启动服务
+ // if (!isDeviceConnected) return;
final result = await BleManager.to.startAgentService();
- Logger.info(result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败');
+ Logger.i(_tag, result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败');
} catch (e) {
- Logger.error('启动BLE后台服务发生错误: $e');
+ Logger.e(_tag, '启动BLE后台服务发生错误: ${e.toString()}');
}
}
@@ -394,9 +491,9 @@ class PairingController extends GetxController {
Future _stopAgentService() async {
try {
final result = await BleManager.to.stopAgentService();
- Logger.info(result ? 'BLE后台服务停止成功' : 'BLE后台服务停止失败');
+ Logger.i(_tag, result ? 'BLE后台服务停止成功' : 'BLE后台服务停止失败');
} catch (e) {
- Logger.error('停止BLE后台服务发生错误: $e');
+ Logger.e(_tag, '停止BLE后台服务发生错误: ${e.toString()}');
}
}
@@ -419,18 +516,93 @@ class PairingController extends GetxController {
} else if (Platform.isAndroid && !isBluetoothOn) {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
} else if (Platform.isIOS) {
- openAppSettings();
+ // iOS上优先尝试打开蓝牙设置
+ if (!isBluetoothOn) {
+ // iOS上无法直接打开蓝牙设置,只能打开应用设置
+ AppSettings.openAppSettings();
+ } else {
+ // 如果蓝牙开启但有其他权限问题,打开应用设置
+ openAppSettings();
+ }
} else {
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
}
} catch (e) {
- Logger.error('打开设置失败: $e');
+ Logger.e(_tag, '打开设置失败: ${e.toString()}');
}
}
+ /// 重新初始化蓝牙
+ Future reinitializeBluetooth() async {
+ try {
+ isLoading.value = true;
+ statusDescription.value = '正在重新初始化蓝牙...';
+
+ // iOS上尝试重置权限状态
+ if (Platform.isIOS) {
+ await Future.delayed(Duration(milliseconds: 500));
+
+ // 检查蓝牙权限
+ PermissionStatus bluetoothStatus = await Permission.bluetooth.status;
+ Logger.i(_tag, "iOS蓝牙权限当前状态: $bluetoothStatus");
+
+ if (bluetoothStatus.isPermanentlyDenied) {
+ Logger.i(_tag, "iOS蓝牙权限处于永久拒绝状态,提示用户手动开启");
+ _showPermissionSettingsDialog('蓝牙权限被拒绝,请在设置中手动开启');
+ return;
+ }
+
+ // 尝试重新请求权限
+ if (!bluetoothStatus.isGranted) {
+ bluetoothStatus = await Permission.bluetooth.request();
+ Logger.i(_tag, "iOS蓝牙权限请求结果: $bluetoothStatus");
+ }
+
+ // 尝试检查蓝牙状态,这会重新触发系统权限弹窗
+ try {
+ bool isEnabled = await _bluetoothService.isBluetoothEnabled();
+ Logger.i(_tag, "iOS蓝牙状态: ${isEnabled ? '开启' : '关闭'}");
+ } catch (e) {
+ Logger.e(_tag, "iOS蓝牙状态检查失败: ${e.toString()}");
+ }
+
+ // 检查Companion功能(iOS 18+)
+ try {
+ bool isSupported = await _bleService.isCompanionSupported();
+ if (isSupported) {
+ Logger.i(_tag, "iOS 18+ Companion功能可用,尝试初始化");
+ // 尝试预热AccessorySetupKit
+ await _bleService.getPairedMacAddress();
+ }
+ } catch (e) {
+ Logger.w(_tag, "iOS Companion功能检查失败: ${e.toString()}");
+ }
+ }
+
+ // 重新检查权限
+ if (await _checkAndRequestPermissions()) {
+ // 重新检查蓝牙状态
+ await _checkBluetoothAndRefresh();
+ }
+ } catch (e) {
+ Logger.e(_tag, '重新初始化蓝牙失败: ${e.toString()}');
+ } finally {
+ isLoading.value = false;
+ _updateStatusDescription();
+ }
+ }
+
/// 打开应用设置
Future openAppSettings() async {
- await AppSettings.openAppSettings();
+ try {
+ await AppSettings.openAppSettings();
+ // 返回后重新检查权限状态
+ Future.delayed(Duration(milliseconds: 500), () {
+ reinitializeBluetooth();
+ });
+ } catch (e) {
+ Logger.e(_tag, '打开应用设置失败: ${e.toString()}');
+ }
}
/// 重新开始关联
@@ -442,9 +614,13 @@ class PairingController extends GetxController {
/// 跳过连接
void skipConnecting() async {
- // 启动BLE后台服务
+ // 即使没有设备连接,也启动BLE后台服务
await _startAgentService();
_isNavigatingToHome = true;
+
+ // 记录跳过配对的日志
+ Logger.i(_tag, '用户跳过设备配对,直接跳转到主页');
+
Get.offAllNamed(Routes.home);
}
-}
+}
\ No newline at end of file
diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart
index 05bca4804..984376632 100644
--- a/lib/modules/settings/controllers/settings_controller.dart
+++ b/lib/modules/settings/controllers/settings_controller.dart
@@ -12,6 +12,9 @@ import '../../../data/models/user_Info.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
+import 'package:permission_handler/permission_handler.dart';
+import 'dart:async';
+import 'package:app_settings/app_settings.dart';
class SettingsController extends GetxController {
// 语言设置
@@ -101,6 +104,81 @@ class SettingsController extends GetxController {
final appVersion = ''.obs;
final buildNumber = ''.obs;
+ // 权限相关
+ // 权限组,用于在UI中显示
+ final List