Browse Source

add

newdev_shunjiawei
wolfplus 1 year ago
parent
commit
a132719987
  1. 2
      ios/Flutter/Profile.xcconfig
  2. 1
      ios/Flutter/Release.xcconfig
  3. 5
      ios/Podfile
  4. 30
      ios/Podfile.lock
  5. 128
      ios/Runner.xcodeproj/project.pbxproj
  6. 5
      ios/Runner/Info.plist
  7. 18
      lib/data/services/speech_factory.dart
  8. 478
      lib/data/services/speech_impl/flutter_asr_service.dart
  9. 484
      lib/data/services/speech_impl/flutter_tts_service.dart
  10. 1
      lib/modules/login/controllers/login_controller.dart
  11. 211
      lib/modules/pairing/controllers/pairing_controller.dart
  12. 34
      local_plugins/azure_speech/ios/.gitignore
  13. 857
      local_plugins/azure_speech/ios/Classes/AzureAsrHelper.swift
  14. 4
      local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.h
  15. 12
      local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.m
  16. 530
      local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.swift
  17. 633
      local_plugins/azure_speech/ios/Classes/AzureTtsHelper.swift
  18. 6
      local_plugins/azure_speech/ios/Classes/azure_speech-Bridging-Header.h
  19. 5
      local_plugins/azure_speech/ios/Classes/azure_speech.h
  20. 26
      local_plugins/azure_speech/ios/Flutter/AppFrameworkInfo.plist
  21. 1
      local_plugins/azure_speech/ios/Flutter/Debug.xcconfig
  22. 1
      local_plugins/azure_speech/ios/Flutter/Release.xcconfig
  23. 619
      local_plugins/azure_speech/ios/Runner.xcodeproj/project.pbxproj
  24. 7
      local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  25. 8
      local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  26. 8
      local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  27. 99
      local_plugins/azure_speech/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  28. 7
      local_plugins/azure_speech/ios/Runner.xcworkspace/contents.xcworkspacedata
  29. 8
      local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  30. 8
      local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  31. 13
      local_plugins/azure_speech/ios/Runner/AppDelegate.swift
  32. 122
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
  33. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
  34. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
  35. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
  36. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
  37. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
  38. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
  39. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
  40. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
  41. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
  42. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
  43. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
  44. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
  45. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
  46. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
  47. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
  48. 23
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
  49. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  50. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
  51. BIN
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
  52. 5
      local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
  53. 37
      local_plugins/azure_speech/ios/Runner/Base.lproj/LaunchScreen.storyboard
  54. 26
      local_plugins/azure_speech/ios/Runner/Base.lproj/Main.storyboard
  55. 49
      local_plugins/azure_speech/ios/Runner/Info.plist
  56. 1
      local_plugins/azure_speech/ios/Runner/Runner-Bridging-Header.h
  57. 12
      local_plugins/azure_speech/ios/RunnerTests/RunnerTests.swift
  58. 11
      local_plugins/azure_speech/ios/azure_speech.podspec
  59. 20
      local_plugins/azure_speech/lib/main.dart
  60. 109
      local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift
  61. 95
      local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift
  62. 3
      local_plugins/ble_service/ios/ble_service.podspec
  63. 35
      local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift
  64. 10
      local_plugins/classic_bluetooth/ios/Classes/SwiftClassicBluetoothPlugin.swift
  65. 3
      local_plugins/classic_bluetooth/ios/classic_bluetooth.podspec
  66. BIN
      local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock
  67. 4
      local_plugins/opus/.gradle/buildOutputCleanup/cache.properties
  68. 0
      local_plugins/speech/LICENSE
  69. 17
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/IAsrService.kt
  70. 33
      local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/SpeechPlugin.kt
  71. 132
      local_plugins/speech/ios/Classes/IAsrService.swift
  72. 147
      local_plugins/speech/ios/Classes/ITtsService.swift
  73. 4
      local_plugins/speech/ios/Classes/SpeechPlugin.h
  74. 13
      local_plugins/speech/ios/Classes/SpeechPlugin.m
  75. 22
      local_plugins/speech/ios/Classes/SpeechPlugin.swift
  76. 6
      local_plugins/speech/ios/Classes/speech-Bridging-Header.h
  77. 5
      local_plugins/speech/ios/Classes/speech.h
  78. 29
      local_plugins/speech/ios/README.md
  79. 23
      local_plugins/speech/ios/speech.podspec
  80. 8
      local_plugins/speech/lib/speech.dart
  81. 26
      local_plugins/speech/pubspec.yaml
  82. 3
      pubspec.yaml

2
ios/Flutter/Profile.xcconfig

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
#include "Generated.xcconfig"

1
ios/Flutter/Release.xcconfig

@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"
#include "Generated.xcconfig" #include "Generated.xcconfig"

5
ios/Podfile

@ -39,5 +39,10 @@ end
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
# Ensure all pods use the same iOS deployment target
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '18.0'
end
end end
end end

30
ios/Podfile.lock

@ -11,6 +11,10 @@ PODS:
- Flutter - Flutter
- audio_waveforms (0.0.1): - audio_waveforms (0.0.1):
- Flutter - Flutter
- azure_speech (0.0.1):
- Flutter
- MicrosoftCognitiveServicesSpeech-iOS (~> 1.30.0)
- speech
- ble_service (0.0.1): - ble_service (0.0.1):
- Flutter - Flutter
- chat_storage (0.0.1): - chat_storage (0.0.1):
@ -179,6 +183,7 @@ PODS:
- Mantle (2.2.0): - Mantle (2.2.0):
- Mantle/extobjc (= 2.2.0) - Mantle/extobjc (= 2.2.0)
- Mantle/extobjc (2.2.0) - Mantle/extobjc (2.2.0)
- MicrosoftCognitiveServicesSpeech-iOS (1.30.0)
- music_service (0.0.1): - music_service (0.0.1):
- Flutter - Flutter
- open_ai_service (0.0.1): - open_ai_service (0.0.1):
@ -210,10 +215,8 @@ PODS:
- SDWebImage/Core (~> 5.17) - SDWebImage/Core (~> 5.17)
- share_plus (0.0.1): - share_plus (0.0.1):
- Flutter - Flutter
- speech_to_text (0.0.1): - speech (0.0.1):
- Flutter - Flutter
- FlutterMacOS
- Try
- spotify_sdk (0.0.1): - spotify_sdk (0.0.1):
- Flutter - Flutter
- sqflite_darwin (0.0.4): - sqflite_darwin (0.0.4):
@ -223,7 +226,6 @@ PODS:
- tencentcloud_cos_sdk_plugin (1.2.3): - tencentcloud_cos_sdk_plugin (1.2.3):
- Flutter - Flutter
- QCloudCOSXML (= 6.4.7) - QCloudCOSXML (= 6.4.7)
- Try (2.1.1)
- webview_flutter_wkwebview (0.0.1): - webview_flutter_wkwebview (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
@ -233,6 +235,7 @@ DEPENDENCIES:
- app_settings (from `.symlinks/plugins/app_settings/ios`) - app_settings (from `.symlinks/plugins/app_settings/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
- audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`) - audio_waveforms (from `.symlinks/plugins/audio_waveforms/ios`)
- azure_speech (from `.symlinks/plugins/azure_speech/ios`)
- ble_service (from `.symlinks/plugins/ble_service/ios`) - ble_service (from `.symlinks/plugins/ble_service/ios`)
- chat_storage (from `.symlinks/plugins/chat_storage/ios`) - chat_storage (from `.symlinks/plugins/chat_storage/ios`)
- classic_bluetooth (from `.symlinks/plugins/classic_bluetooth/ios`) - classic_bluetooth (from `.symlinks/plugins/classic_bluetooth/ios`)
@ -260,7 +263,7 @@ DEPENDENCIES:
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record_darwin (from `.symlinks/plugins/record_darwin/ios`) - record_darwin (from `.symlinks/plugins/record_darwin/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- speech_to_text (from `.symlinks/plugins/speech_to_text/darwin`) - speech (from `.symlinks/plugins/speech/ios`)
- spotify_sdk (from `.symlinks/plugins/spotify_sdk/ios`) - spotify_sdk (from `.symlinks/plugins/spotify_sdk/ios`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- tencentcloud_cos_sdk_plugin (from `.symlinks/plugins/tencentcloud_cos_sdk_plugin/ios`) - tencentcloud_cos_sdk_plugin (from `.symlinks/plugins/tencentcloud_cos_sdk_plugin/ios`)
@ -288,6 +291,7 @@ SPEC REPOS:
- GTMSessionFetcher - GTMSessionFetcher
- libwebp - libwebp
- Mantle - Mantle
- MicrosoftCognitiveServicesSpeech-iOS
- QCloudCore - QCloudCore
- QCloudCOSXML - QCloudCOSXML
- QCloudTrack - QCloudTrack
@ -295,7 +299,6 @@ SPEC REPOS:
- SDWebImage - SDWebImage
- SDWebImageWebPCoder - SDWebImageWebPCoder
- SwiftyGif - SwiftyGif
- Try
- WechatOpenSDK-XCFramework - WechatOpenSDK-XCFramework
EXTERNAL SOURCES: EXTERNAL SOURCES:
@ -305,6 +308,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/audio_session/ios" :path: ".symlinks/plugins/audio_session/ios"
audio_waveforms: audio_waveforms:
:path: ".symlinks/plugins/audio_waveforms/ios" :path: ".symlinks/plugins/audio_waveforms/ios"
azure_speech:
:path: ".symlinks/plugins/azure_speech/ios"
ble_service: ble_service:
:path: ".symlinks/plugins/ble_service/ios" :path: ".symlinks/plugins/ble_service/ios"
chat_storage: chat_storage:
@ -359,8 +364,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/record_darwin/ios" :path: ".symlinks/plugins/record_darwin/ios"
share_plus: share_plus:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
speech_to_text: speech:
:path: ".symlinks/plugins/speech_to_text/darwin" :path: ".symlinks/plugins/speech/ios"
spotify_sdk: spotify_sdk:
:path: ".symlinks/plugins/spotify_sdk/ios" :path: ".symlinks/plugins/spotify_sdk/ios"
sqflite_darwin: sqflite_darwin:
@ -375,7 +380,8 @@ SPEC CHECKSUMS:
AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf audio_waveforms: a6dde7fe7c0ea05f06ffbdb0f7c1b2b2ba6cedcf
ble_service: 068ebe51800424f980282b16cbf757574eec4e08 azure_speech: d65fa5c1f04be9cee7303abf4caea30680e814fd
ble_service: 3414a81261f88a6e673cbc71178d2353d96b2a1c
chat_storage: 933a96144157a07a2fef7f6acb28c78ff18465d8 chat_storage: 933a96144157a07a2fef7f6acb28c78ff18465d8
classic_bluetooth: c5e5c0c86acab8dcd6b8e9cef7fca654c3518034 classic_bluetooth: c5e5c0c86acab8dcd6b8e9cef7fca654c3518034
device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896 device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896
@ -414,6 +420,7 @@ SPEC CHECKSUMS:
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
MicrosoftCognitiveServicesSpeech-iOS: f53991647f295e18407619816a8064908a8700f4
music_service: 85abecc8fa21599a06907ad50d83f48a58e0dc0d music_service: 85abecc8fa21599a06907ad50d83f48a58e0dc0d
open_ai_service: 82ea947b1a081a2a17bc0c8e5467dcbd7cf9b232 open_ai_service: 82ea947b1a081a2a17bc0c8e5467dcbd7cf9b232
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
@ -427,15 +434,14 @@ SPEC CHECKSUMS:
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868 SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
speech_to_text: 9dc43a5df3cbc2813f8c7cc9bd0fbf94268ed7ac speech: 89e648ea83c83d32b25ad2247fdec80fdddd7fb6
spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca spotify_sdk: a48400bb29f70c4fe251ebfdc9135c37097ac5ca
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b tencentcloud_cos_sdk_plugin: c3d78b2c3bae42b5a20009dc047f3f1621e6272b
Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96
webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2 webview_flutter_wkwebview: 1821ceac936eba6f7984d89a9f3bcb4dea99ebb2
WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f WechatOpenSDK-XCFramework: 36fb2bea0754266c17184adf4963d7e6ff98b69f
PODFILE CHECKSUM: f62382080fcf32f2d4640dc419aa07d69eff0328 PODFILE CHECKSUM: c27b71cd46016bc2643e657c88b59244c316e68a
COCOAPODS: 1.16.2 COCOAPODS: 1.16.2

128
ios/Runner.xcodeproj/project.pbxproj

@ -7,16 +7,16 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
094D08D02FDF5BCF4300904C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 89BDD8FCD8E4A95DFFAC950E /* Pods_Runner.framework */; };
0AD944122DD9DB5C00557D21 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */; }; 0AD944122DD9DB5C00557D21 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */; };
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.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 */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
8C1EC4B8888A2A7B8741B084 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 971EED197AE19E5EA8C93AB7 /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
A7986DFCFDA104638080424B /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -50,12 +50,13 @@
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
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 = "<group>"; }; 4AC518688D83E399D06C65D9 /* 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 = "<group>"; };
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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
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 = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
89BDD8FCD8E4A95DFFAC950E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8AEFEF9B7F1F2853E3BE4328 /* 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 = "<group>"; };
971EED197AE19E5EA8C93AB7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -63,12 +64,11 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>"; }; AE1151546CDAD222B8569F48 /* 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 = "<group>"; };
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 = "<group>"; }; CD485A3D7C665ACD79DE1A05 /* 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 = "<group>"; };
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 = "<group>"; };
D2958658C06A5ADF62D6ABA2 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; }; D2958658C06A5ADF62D6ABA2 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
E27AD0DBE6531F2655DB9986 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D45B01309FCD51D40429D22A /* 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 = "<group>"; };
F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D6F4721705FE7CF4B2C9A7C7 /* 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 = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -76,7 +76,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
6077538D78AAB34545D7F3DD /* Pods_Runner.framework in Frameworks */, 094D08D02FDF5BCF4300904C /* Pods_Runner.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -84,7 +84,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A7986DFCFDA104638080424B /* Pods_RunnerTests.framework in Frameworks */, 8C1EC4B8888A2A7B8741B084 /* Pods_RunnerTests.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -94,12 +94,12 @@
1ACB686EC7DC36C6E33D372F /* Pods */ = { 1ACB686EC7DC36C6E33D372F /* Pods */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
762C8874AAE15CA0CB901347 /* Pods-Runner.debug.xcconfig */, 4AC518688D83E399D06C65D9 /* Pods-Runner.debug.xcconfig */,
B3EC1304D6F5453D58E0BDB0 /* Pods-Runner.release.xcconfig */, AE1151546CDAD222B8569F48 /* Pods-Runner.release.xcconfig */,
6189535D5FBB81D8377BE874 /* Pods-Runner.profile.xcconfig */, D6F4721705FE7CF4B2C9A7C7 /* Pods-Runner.profile.xcconfig */,
C15B3030DF1AED99D3829135 /* Pods-RunnerTests.debug.xcconfig */, 8AEFEF9B7F1F2853E3BE4328 /* Pods-RunnerTests.debug.xcconfig */,
742A00629970110BA5008064 /* Pods-RunnerTests.release.xcconfig */, CD485A3D7C665ACD79DE1A05 /* Pods-RunnerTests.release.xcconfig */,
D1CF75E5BD49BB58812D5F36 /* Pods-RunnerTests.profile.xcconfig */, D45B01309FCD51D40429D22A /* Pods-RunnerTests.profile.xcconfig */,
); );
path = Pods; path = Pods;
sourceTree = "<group>"; sourceTree = "<group>";
@ -108,8 +108,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */, 0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */,
E27AD0DBE6531F2655DB9986 /* Pods_Runner.framework */, 89BDD8FCD8E4A95DFFAC950E /* Pods_Runner.framework */,
F79ADB762804BBED65875C03 /* Pods_RunnerTests.framework */, 971EED197AE19E5EA8C93AB7 /* Pods_RunnerTests.framework */,
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
@ -178,7 +178,7 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = ( buildPhases = (
D21548F12B1535261EB87672 /* [CP] Check Pods Manifest.lock */, 4555DFF031A57477C09E2572 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */, 331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */, 331C807F294A63A400263BE5 /* Resources */,
B3515CD9628E3332AFC88CD4 /* Frameworks */, B3515CD9628E3332AFC88CD4 /* Frameworks */,
@ -197,15 +197,15 @@
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = ( buildPhases = (
90215E32C3610039A2575557 /* [CP] Check Pods Manifest.lock */, 1A23E53C090955A193472A0D /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */, 9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */, 97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */, 97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */, 97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */, 9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
510B02006C08E514BF31EFD9 /* [CP] Embed Pods Frameworks */, 4F94027D1F57DF2EE72F6FDE /* [CP] Embed Pods Frameworks */,
298589D956D542AE0EE16DAE /* [CP] Copy Pods Resources */, 56059DFFB76EF409A9B11842 /* [CP] Copy Pods Resources */,
); );
buildRules = ( buildRules = (
); );
@ -278,21 +278,26 @@
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
298589D956D542AE0EE16DAE /* [CP] Copy Pods Resources */ = { 1A23E53C090955A193472A0D /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( 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 = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", );
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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; showEnvVarsInLog = 0;
}; };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
@ -311,81 +316,76 @@
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
}; };
510B02006C08E514BF31EFD9 /* [CP] Embed Pods Frameworks */ = { 4555DFF031A57477C09E2572 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "[CP] Embed Pods Frameworks"; inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", );
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.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; showEnvVarsInLog = 0;
}; };
90215E32C3610039A2575557 /* [CP] Check Pods Manifest.lock */ = { 4F94027D1F57DF2EE72F6FDE /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = ( name = "[CP] Embed Pods Frameworks";
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = ( outputFileListPaths = (
); "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 56059DFFB76EF409A9B11842 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
name = "Run Script"; name = "[CP] Copy Pods Resources";
outputPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
}; };
D21548F12B1535261EB87672 /* [CP] Check Pods Manifest.lock */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = (
);
inputPaths = ( inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
); );
name = "Run Script";
outputPaths = ( outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; 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 = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
showEnvVarsInLog = 0;
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
@ -516,7 +516,7 @@
}; };
331C8088294A63A400263BE5 /* Debug */ = { 331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = C15B3030DF1AED99D3829135 /* Pods-RunnerTests.debug.xcconfig */; baseConfigurationReference = 8AEFEF9B7F1F2853E3BE4328 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@ -535,7 +535,7 @@
}; };
331C8089294A63A400263BE5 /* Release */ = { 331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 742A00629970110BA5008064 /* Pods-RunnerTests.release.xcconfig */; baseConfigurationReference = CD485A3D7C665ACD79DE1A05 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
@ -552,7 +552,7 @@
}; };
331C808A294A63A400263BE5 /* Profile */ = { 331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = D1CF75E5BD49BB58812D5F36 /* Pods-RunnerTests.profile.xcconfig */; baseConfigurationReference = D45B01309FCD51D40429D22A /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;

5
ios/Runner/Info.plist

@ -117,6 +117,11 @@
<key>NSAccessorySetupKitSupports</key> <key>NSAccessorySetupKitSupports</key>
<array> <array>
<string>Bluetooth</string> <string>Bluetooth</string>
<string>WiFi</string>
</array>
<key>NSAccessorySetupBluetoothCompanyIdentifiers</key>
<array>
<string>ABEF</string>
</array> </array>
</dict> </dict>
</plist> </plist>

18
lib/data/services/speech_factory.dart

@ -3,8 +3,6 @@ import 'asr_service.dart';
import 'tts_service.dart'; import 'tts_service.dart';
import 'speech_impl/azure_asr_service.dart'; import 'speech_impl/azure_asr_service.dart';
import 'speech_impl/azure_tts_service.dart'; import 'speech_impl/azure_tts_service.dart';
import 'speech_impl/flutter_asr_service.dart';
import 'speech_impl/flutter_tts_service.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
@ -53,27 +51,17 @@ class SpeechFactory extends GetxService {
Get.delete<TtsService>(force: true); Get.delete<TtsService>(force: true);
} }
// 根据类型注册新服务
switch (_currentServiceType) {
case SpeechServiceType.azure:
Get.lazyPut<AsrService>(() => AzureAsrService()); Get.lazyPut<AsrService>(() => AzureAsrService());
Get.lazyPut<TtsService>(() => AzureTtsService()); Get.lazyPut<TtsService>(() => AzureTtsService());
break;
case SpeechServiceType.flutter:
Get.lazyPut<AsrService>(() => FlutterAsrService());
Get.lazyPut<TtsService>(() => FlutterTtsService());
break;
}
} }
/// 初始化语音服务 /// 初始化语音服务
Future<void> initialize({SpeechServiceType? initialType}) async { Future<void> initialize({SpeechServiceType? initialType}) async {
try { try {
// 如果指定了初始类型,则设置
if (initialType != null) {
_currentServiceType = initialType;
}
// 注册服务 // 注册服务
_registerServices(); _registerServices();

478
lib/data/services/speech_impl/flutter_asr_service.dart

@ -1,478 +0,0 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:speech_to_text/speech_to_text.dart' as stt;
import 'package:speech_to_text/speech_recognition_result.dart';
import 'package:speech_to_text/speech_recognition_error.dart';
import '../../../core/utils/logger.dart';
import '../asr_service.dart';
/// ASR状态枚举
enum AsrState {
/// 未初始化
notInitialized,
/// 已初始化但未开始监听
initialized,
/// 正在监听
listening,
/// 正在处理识别结果
processing,
/// 识别完成
done,
/// 发生错误
error,
}
/// Flutter ASR服务,负责本地语音转文本功能
/// 使用speech_to_text插件实现语音识别功能
class FlutterAsrService extends GetxService implements AsrService {
static final FlutterAsrService to = Get.put(FlutterAsrService());
// speech_to_text 实例
final stt.SpeechToText _speech = stt.SpeechToText();
// 可用的语音识别语言
final List<String> _supportedLanguages = [];
@override
List<String> get supportedLanguages => _supportedLanguages;
// 当前选择的语言
final RxString _currentLocale = ''.obs;
String get currentLocale => _currentLocale.value;
// 是否可用
final RxBool _isAvailable = false.obs;
bool get isAvailable => _isAvailable.value;
// 是否正在监听
bool _isListening = false;
// 当前ASR状态
final Rx<AsrState> _asrState = AsrState.notInitialized.obs;
AsrState get asrState => _asrState.value;
// 连续识别模式的结果流控制器
StreamController<RecognitionEvent>? _continuousRecognitionController;
FlutterAsrService() {
_initSpeech();
}
/// 初始化语音识别
Future<void> _initSpeech() async {
try {
_asrState.value = AsrState.notInitialized;
// 初始化语音识别,使用较短的最终超时时间,提高响应速度
_isAvailable.value = await _speech.initialize(
onStatus: _onSpeechStatus,
onError: _onSpeechError,
debugLogging: false,
finalTimeout: const Duration(milliseconds: 800), // 更短的最终超时时间,加速结果返回
);
if (_isAvailable.value) {
// 获取支持的语言列表
final systemLocales = await _speech.locales();
// 清空并重新填充支持的语言列表
_supportedLanguages.clear();
for (var locale in systemLocales) {
if (!_supportedLanguages.contains(locale.localeId)) {
_supportedLanguages.add(locale.localeId);
}
}
// 设置默认语言(优先使用系统语言或中文)
var systemLocale = await _speech.systemLocale();
if (systemLocale != null) {
_currentLocale.value = systemLocale.localeId;
} else if (_supportedLanguages.contains('zh-CN')) {
_currentLocale.value = 'zh-CN';
} else if (_supportedLanguages.isNotEmpty) {
_currentLocale.value = _supportedLanguages.first;
}
_asrState.value = AsrState.initialized;
Logger.info('语音识别初始化成功,支持 ${_supportedLanguages.length} 种语言');
} else {
_asrState.value = AsrState.error;
Logger.error('语音识别不可用');
}
} catch (e) {
_isAvailable.value = false;
_asrState.value = AsrState.error;
Logger.error('语音识别初始化失败: ${e.toString()}');
}
}
/// 处理语音识别状态变化
void _onSpeechStatus(String status) {
Logger.info('语音识别状态: $status');
switch (status) {
case 'listening':
_asrState.value = AsrState.listening;
_isListening = true;
break;
case 'notListening':
if (_asrState.value == AsrState.listening) {
_asrState.value = AsrState.processing;
}
_isListening = false;
break;
case 'done':
_asrState.value = AsrState.done;
_isListening = false;
// 在连续识别模式下,当一次识别完成后立即开始下一次识别
if (_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition();
}
break;
}
}
/// 处理语音识别错误
void _onSpeechError(SpeechRecognitionError error) {
Logger.error('语音识别错误: ${error.errorMsg} (永久性错误: ${error.permanent})');
_asrState.value = AsrState.error;
// 向连续识别控制器发送错误事件(如果存在)
_continuousRecognitionController
?.add(RecognitionEvent.error(error.errorMsg));
// 在连续识别模式下,尝试从错误中恢复(非永久性错误)
if (!error.permanent &&
_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition();
}
}
/// 启动语音识别的统一方法
Future<bool> _startListening({
required Function(SpeechRecognitionResult) onResult,
Duration listenFor = const Duration(seconds: 30),
Duration pauseFor = const Duration(seconds: 3),
bool autoPunctuation = true,
}) async {
try {
// 如果当前正在监听,先停止
if (_speech.isListening) {
await _speech.stop();
// 延长等待时间,确保资源完全释放
await Future.delayed(const Duration(milliseconds: 800));
}
// 在启动前再次检查状态
if (_speech.isListening) {
Logger.warning('停止识别后,仍处于监听状态,可能有系统冲突');
await _speech.stop();
await Future.delayed(const Duration(milliseconds: 500));
}
// 启动语音识别前确保状态正确
_asrState.value = AsrState.listening;
// 启动语音识别
final result = await _speech.listen(
onResult: onResult,
localeId: _currentLocale.value,
listenFor: listenFor,
pauseFor: pauseFor,
listenOptions: stt.SpeechListenOptions(
partialResults: true,
autoPunctuation: autoPunctuation,
// 使用独白模式,减少与系统交互冲突
listenMode: stt.ListenMode.dictation,
),
) ??
false;
if (!result) {
Logger.error('语音识别启动失败');
_asrState.value = AsrState.error;
}
return result;
} catch (e) {
Logger.error('启动语音识别失败: ${e.toString()}');
_asrState.value = AsrState.error;
return false;
}
}
/// 重新启动连续识别
Future<void> _restartContinuousRecognition() async {
try {
// 确保控制器有效
if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('连续识别控制器已关闭,跳过重启');
return;
}
// 检查系统状态,避免频繁重启
if (_speech.isListening) {
Logger.info('当前已在监听中,无需重启');
return;
}
// 延长等待时间,避免与系统资源冲突
await Future.delayed(const Duration(milliseconds: 1000));
// 再次检查状态
if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('延迟期间控制器已关闭,跳过重启');
return;
}
Logger.info('尝试重新启动连续识别');
// 使用统一方法启动识别
final success = await _startListening(
onResult: _onSpeechResult,
// 增加超时时间和暂停时间
listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 4),
);
if (success) {
Logger.info('连续识别重启成功');
} else {
Logger.error('连续识别重启失败');
_continuousRecognitionController
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
}
} catch (e) {
Logger.error('重启连续识别失败: ${e.toString()}');
// 发送错误给控制器
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
}
}
/// 处理语音识别结果
void _onSpeechResult(SpeechRecognitionResult result) {
// 如果是连续识别,发送到连续识别控制器
if (_continuousRecognitionController != null) {
if (result.finalResult) {
Logger.info('收到最终识别结果: "${result.recognizedWords}"');
_continuousRecognitionController!.add(RecognitionEvent.finalResult(
text: result.recognizedWords,
detectedLanguage: currentLocale,
));
} else {
_continuousRecognitionController!.add(RecognitionEvent(
type: RecognitionEventType.intermediateResult,
text: result.recognizedWords,
detectedLanguage: currentLocale,
));
}
}
}
/// 设置当前语言
void setLocale(String localeId) {
if (_supportedLanguages.contains(localeId)) {
_currentLocale.value = localeId;
Logger.info('设置语音识别语言: $localeId');
} else {
Logger.error('不支持的语言: $localeId');
}
}
@override
Future<bool> initialize({
List<String>? supportedLanguages,
}) async {
try {
if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages);
}
// 初始化语音识别
await _initSpeech();
return _isAvailable.value;
} catch (e) {
Logger.error('语音识别初始化失败: ${e.toString()}');
return false;
}
}
@override
Future<RecognitionEvent> recognizeOnce() async {
try {
if (!_isAvailable.value) {
return RecognitionEvent.error('语音识别不可用');
}
if (_isListening) {
await stopContinuousRecognition();
}
// 创建Completer来等待最终结果
final completer = Completer<RecognitionEvent>();
// 开始监听
_asrState.value = AsrState.listening;
// 定义结果处理函数
void resultListener(SpeechRecognitionResult result) {
if (result.finalResult) {
// 只有在最终结果时才完成completer
completer.complete(RecognitionEvent.finalResult(
text: result.recognizedWords,
detectedLanguage: currentLocale,
));
}
}
// 使用统一方法启动识别,单次识别使用更短的时间参数
final success = await _startListening(
onResult: resultListener,
listenFor: const Duration(seconds: 15),
pauseFor: const Duration(seconds: 2),
);
if (!success) {
return RecognitionEvent.error('启动语音识别失败');
}
// 设置较短的超时时间
return await completer.future.timeout(
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果
onTimeout: () {
_speech.stop();
return RecognitionEvent.error('语音识别超时');
},
);
} catch (e) {
Logger.error('一次性语音识别失败: ${e.toString()}');
return RecognitionEvent.error('$e');
}
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition(
bool audioSourceType) async {
try {
if (!_isAvailable.value) {
final controller = StreamController<RecognitionEvent>.broadcast();
controller.add(RecognitionEvent.error('语音识别不可用'));
controller.close();
return controller.stream;
}
// 如果已经在监听,先停止
if (_isListening) {
await stopContinuousRecognition();
}
// 创建新的流控制器
_continuousRecognitionController =
StreamController<RecognitionEvent>.broadcast();
// 开始会话
_continuousRecognitionController!
.add(RecognitionEvent(type: RecognitionEventType.sessionStarted));
// 开始连续监听
_asrState.value = AsrState.listening;
// 使用统一方法启动识别,连续识别使用较长的参数
final success = await _startListening(
onResult: _onSpeechResult,
);
if (!success) {
// 添加错误事件,但保留流
_continuousRecognitionController!
.add(RecognitionEvent.error('启动连续语音识别失败'));
// 返回已有的流控制器
return _continuousRecognitionController!.stream;
}
_isListening = true;
return _continuousRecognitionController!.stream;
} catch (e) {
Logger.error('启动连续语音识别失败: ${e.toString()}');
// 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) {
_continuousRecognitionController!.add(RecognitionEvent.error('$e'));
return _continuousRecognitionController!.stream;
} else {
// 否则创建新的控制器
final controller = StreamController<RecognitionEvent>.broadcast();
controller.add(RecognitionEvent.error('$e'));
return controller.stream;
}
}
}
@override
Future<bool> stopContinuousRecognition() async {
try {
// 无论是否正在监听,都尝试停止
if (_speech.isListening) {
await _speech.stop();
}
_isListening = false;
// 发送会话结束事件
_continuousRecognitionController
?.add(RecognitionEvent(type: RecognitionEventType.sessionStopped));
// 关闭流控制器
await _continuousRecognitionController?.close();
_continuousRecognitionController = null;
_asrState.value = AsrState.done;
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: ${e.toString()}');
// 强制重置状态
_isListening = false;
_asrState.value = AsrState.error;
return false;
}
}
@override
bool isContinuousRecognitionActive() {
return _isListening;
}
@override
Future<void> dispose() async {
try {
// 确保停止所有识别活动
if (_speech.isListening) {
await _speech.stop();
}
// 关闭控制器
await _continuousRecognitionController?.close();
_continuousRecognitionController = null;
_isListening = false;
_asrState.value = AsrState.notInitialized;
} catch (e) {
Logger.error('释放语音识别资源失败: ${e.toString()}');
}
}
}

484
lib/data/services/speech_impl/flutter_tts_service.dart

@ -1,484 +0,0 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:get/get.dart';
import 'package:flutter_tts/flutter_tts.dart';
import '../../../core/utils/logger.dart';
import '../tts_service.dart';
/// Flutter TTS服务,负责基本的文本转语音功能
///
/// 使用flutter_tts插件实现TtsService接口
class FlutterTtsService extends GetxService implements TtsService {
static final FlutterTtsService to = Get.put(FlutterTtsService());
// flutter_tts实例
late final FlutterTts _flutterTts;
// 支持的语音列表
final List<String> _supportedVoices = [];
List<String> get supportedVoices => _supportedVoices;
// 当前语音
final RxString _currentVoice = ''.obs;
@override
String get currentVoice => _currentVoice.value;
// 播放状态
final RxBool _isSpeaking = false.obs;
@override
bool get isSpeaking => _isSpeaking.value;
// 文本队列
final Queue<_SpeechItem> _textQueue = Queue<_SpeechItem>();
bool _isProcessingQueue = false;
// 事件流控制器
final StreamController<TtsEvent> _eventController = StreamController<TtsEvent>.broadcast();
@override
Stream<TtsEvent> get onEvent => _eventController.stream;
// 语言
String _language = 'zh-CN';
// 流式文本缓冲区
String _streamBuffer = '';
// 当前参数设置
double _volume = 1.0;
double _rate = 0.5;
double _pitch = 1.0;
FlutterTtsService() {
_flutterTts = FlutterTts();
_initTts();
}
/// 初始化TTS
Future<void> _initTts() async {
try {
// 设置完成回调
_flutterTts.setCompletionHandler(() {
_isSpeaking.value = false;
_eventController.add(TtsEvent(type: TtsEventType.completed));
});
// 设置开始回调
_flutterTts.setStartHandler(() {
_isSpeaking.value = true;
_eventController.add(TtsEvent(type: TtsEventType.started));
});
// 设置错误回调
_flutterTts.setErrorHandler((msg) {
_isSpeaking.value = false;
_eventController.add(TtsEvent.error(msg));
Logger.error('TTS错误: $msg');
});
// 设置取消回调
_flutterTts.setCancelHandler(() {
_isSpeaking.value = false;
_eventController.add(TtsEvent(type: TtsEventType.canceled));
});
// 设置默认语言
await _flutterTts.setLanguage(_language);
// 设置音量
await _flutterTts.setVolume(_volume);
// 设置语速
await _flutterTts.setSpeechRate(_rate);
// 设置音调
await _flutterTts.setPitch(_pitch);
Logger.info('FlutterTtsService 已初始化');
} catch (e) {
Logger.error('TTS初始化失败: ${e.toString()}');
}
}
@override
Future<bool> initialize({
List<String>? supportedLanguages,
}) async {
try {
// 获取可用的语音
await _loadVoices();
// 更新支持的语言列表(如果提供)
if (supportedLanguages != null && supportedLanguages.isNotEmpty) {
_language = supportedLanguages.first;
await _flutterTts.setLanguage(_language);
}
Logger.info('FlutterTtsService 初始化成功');
Logger.info('支持的语音: ${_supportedVoices.join(", ")}');
return true;
} catch (e) {
Logger.error('TTS初始化失败: ${e.toString()}');
return false;
}
}
/// 加载可用的语音
Future<void> _loadVoices() async {
try {
_supportedVoices.clear();
// 获取可用的语音
final voices = await _flutterTts.getVoices;
if (voices != null && voices is List) {
for (final voice in voices) {
if (voice is Map && voice.containsKey('name')) {
_supportedVoices.add(voice['name'].toString());
}
}
}
// 如果没有找到语音,添加默认语音
if (_supportedVoices.isEmpty) {
_supportedVoices.add('default');
}
// 设置默认语音
if (_currentVoice.value.isEmpty && _supportedVoices.isNotEmpty) {
_currentVoice.value = _supportedVoices.first;
}
} catch (e) {
Logger.error('加载TTS语音失败: ${e.toString()}');
// 添加默认语音
if (_supportedVoices.isEmpty) {
_supportedVoices.add('default');
_currentVoice.value = 'default';
}
}
}
@override
Future<bool> setVoice(String voiceName) async {
try {
// 检查语音是否存在
if (!_supportedVoices.contains(voiceName) && voiceName != 'default') {
Logger.warning('语音不存在: $voiceName,使用默认语音');
voiceName = _supportedVoices.isNotEmpty ? _supportedVoices.first : 'default';
}
// 设置语音
await _flutterTts.setVoice({"name": voiceName});
_currentVoice.value = voiceName;
Logger.info('设置TTS语音: $voiceName');
return true;
} catch (e) {
Logger.error('设置TTS语音失败: ${e.toString()}');
return false;
}
}
/// 设置语音参数
Future<bool> setSpeechParams({double? rate, double? pitch, double? volume}) async {
try {
if (volume != null) {
await _flutterTts.setVolume(volume);
_volume = volume;
}
if (rate != null) {
await _flutterTts.setSpeechRate(rate);
_rate = rate;
}
if (pitch != null) {
await _flutterTts.setPitch(pitch);
_pitch = pitch;
}
return true;
} catch (e) {
Logger.error('设置语音参数失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error('设置语音参数失败: $e'));
return false;
}
}
@override
Future<bool> speakOnce(String text) async {
if (text.isEmpty) return false;
try {
// 如果正在播放,先停止
if (_isSpeaking.value) {
await stop();
}
// 播放文本
await _flutterTts.speak(text);
return true;
} catch (e) {
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
Logger.error('TTS播放失败: ${e.toString()}');
return false;
}
}
@override
Future<bool> speakStream(String text) async {
if (text.isEmpty) return false;
try {
// 添加文本到缓冲区
_streamBuffer += text;
// 如果缓冲区为空,直接返回
if (_streamBuffer.isEmpty) return true;
// 使用正则表达式匹配句子,包括结束符号
// 匹配任意字符,直到遇到句子结束符号
final sentenceRegex = RegExp(
r'([^。.!!??;;:\n\r]+[。.!!??;;::\n\r]|[^。.!!??;;:\n\r]+(?:\.{3,}|…)|[^。.!!??;;:\n\r]+["」』])'
);
bool hasProcessed = false;
// 查找所有完整句子
final matches = sentenceRegex.allMatches(_streamBuffer);
final List<String> sentences = [];
int lastMatchEnd = 0;
for (final match in matches) {
// 提取完整句子(包含结束符号)
final sentence = match.group(1)?.trim();
if (sentence != null && sentence.isNotEmpty) {
sentences.add(sentence);
lastMatchEnd = match.end;
}
}
// 处理找到的句子
for (final sentence in sentences) {
_textQueue.add(_SpeechItem(
text: sentence,
rate: _rate,
pitch: _pitch,
volume: _volume,
));
hasProcessed = true;
}
// 更新缓冲区,只保留未完成的部分
if (lastMatchEnd > 0) {
_streamBuffer = _streamBuffer.substring(lastMatchEnd);
}
// 如果处理了文本并且队列未在处理中,开始处理队列
if (hasProcessed && !_isProcessingQueue) {
_processQueue();
}
return true;
} catch (e) {
Logger.error('处理流式文本失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
return false;
}
}
@override
Future<bool> flushStream() async {
if (_textQueue.isEmpty && _streamBuffer.isEmpty) return true;
try {
// 处理缓冲区中可能的完整句子
await speakStream('');
// 如果缓冲区仍有剩余文本,将其添加到播放队列
if (_streamBuffer.isNotEmpty && _streamBuffer.trim().isNotEmpty) {
_textQueue.add(_SpeechItem(
text: _streamBuffer,
rate: _rate,
pitch: _pitch,
volume: _volume,
));
// 清空缓冲区
_streamBuffer = '';
// 如果队列未在处理中,开始处理队列
if (!_isProcessingQueue) {
_processQueue();
}
}
Logger.info('等待TTS队列播放完成,剩余${_textQueue.length}条');
// 等待队列处理完成
while (_isProcessingQueue || _isSpeaking.value) {
await Future.delayed(const Duration(milliseconds: 100));
}
Logger.info('TTS队列播放完成');
return true;
} catch (e) {
Logger.error('等待TTS队列播放完成失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
return false;
}
}
/// 处理队列中的文本
Future<void> _processQueue() async {
if (_isProcessingQueue) return;
_isProcessingQueue = true;
try {
while (_textQueue.isNotEmpty) {
if (!_isSpeaking.value) {
final item = _textQueue.removeFirst();
// 如果参数有变化,更新参数
if (item.rate != _rate || item.pitch != _pitch || item.volume != _volume) {
await setSpeechParams(
rate: item.rate,
pitch: item.pitch,
volume: item.volume,
);
}
// 播放文本
await _flutterTts.speak(item.text);
// 等待开始播放
int attempts = 0;
while (!_isSpeaking.value && attempts < 10) {
await Future.delayed(const Duration(milliseconds: 50));
attempts++;
}
// 等待播放完成
while (_isSpeaking.value) {
await Future.delayed(const Duration(milliseconds: 100));
}
} else {
// 等待一小段时间再检查队列
await Future.delayed(const Duration(milliseconds: 100));
}
}
} catch (e) {
Logger.error('TTS处理队列失败: ${e.toString()}');
// 发送错误事件
_eventController.add(TtsEvent.error(e.toString()));
} finally {
_isProcessingQueue = false;
}
}
@override
Future<bool> stop() async {
try {
Logger.info('尝试停止TTS播放...');
// 清空队列
_textQueue.clear();
_streamBuffer = '';
// 如果正在播放,停止播放
if (_isSpeaking.value) {
// 先重置状态标志,避免处理队列循环继续等待
_isProcessingQueue = false;
// 执行停止
final result = await _flutterTts.stop();
Logger.info('TTS停止结果: $result');
// 短暂延迟等待状态更新
await Future.delayed(const Duration(milliseconds: 100));
// 强制更新状态
_isSpeaking.value = false;
// 发送取消事件
_eventController.add(TtsEvent(type: TtsEventType.canceled));
}
Logger.info('TTS停止播放完成');
return true;
} catch (e) {
Logger.error('TTS停止播放失败: ${e.toString()}');
// 即使出错也尝试强制重置状态
_isSpeaking.value = false;
_isProcessingQueue = false;
return false;
}
}
@override
Future<void> dispose() async {
try {
await stop();
_textQueue.clear();
// 关闭事件流控制器
await _eventController.close();
Logger.info('TTS资源已释放');
} catch (e) {
Logger.error('释放TTS资源失败: ${e.toString()}');
}
}
/// 处理AI返回的文本流,将其转换为语音
///
/// [textStream] 文本流
/// 返回一个Future,当流处理完成时完成
Future<bool> processAiTextStream(Stream<String> textStream) async {
try {
await for (final text in textStream) {
if (text.isEmpty) continue;
await speakStream(text);
}
// 处理完流后,刷新缓冲区中的剩余文本
await flushStream();
return true;
} catch (e) {
Logger.error('处理文本流时出错: ${e.toString()}');
return false;
}
}
}
/// 语音合成项目
class _SpeechItem {
final String text;
final double rate;
final double pitch;
final double volume;
_SpeechItem({
required this.text,
this.rate = 0.5,
this.pitch = 1.0,
this.volume = 1.0,
});
}

1
lib/modules/login/controllers/login_controller.dart

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:logger/web.dart'; import 'package:logger/web.dart';
import '../../../routes/app_routes.dart'; import '../../../routes/app_routes.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:ffi';
import 'facebookLogin.dart'; import 'facebookLogin.dart';
import 'googleLogin.dart'; import 'googleLogin.dart';
import 'weixinLogin.dart'; import 'weixinLogin.dart';

211
lib/modules/pairing/controllers/pairing_controller.dart

@ -85,10 +85,6 @@ class PairingController extends GetxController {
isLoading.value = true; isLoading.value = true;
statusDescription.value = 'checkingPermissions'.tr; statusDescription.value = 'checkingPermissions'.tr;
// 在iOS上,延迟一下再请求权限,避免多权限冲突
if (Platform.isIOS) {
await Future.delayed(Duration(milliseconds: 800));
}
// 检查并请求权限 // 检查并请求权限
if (!await _checkAndRequestPermissions()) { if (!await _checkAndRequestPermissions()) {
@ -114,10 +110,22 @@ class PairingController extends GetxController {
/// 检查蓝牙状态并刷新设备 /// 检查蓝牙状态并刷新设备
Future<void> _checkBluetoothAndRefresh() async { Future<void> _checkBluetoothAndRefresh() async {
statusDescription.value = 'checkingBluetoothStatus'.tr; statusDescription.value = 'checkingBluetoothStatus'.tr;
final isEnabled = await _bluetoothService.isBluetoothEnabled();
_bluetoothState.value = isEnabled ? 'on' : 'off';
if (!isEnabled) { // 无论是iOS还是Android,都正常检查蓝牙状态
try {
final isEnabled = await _bluetoothService.isBluetoothEnabled();
_bluetoothState.value = isEnabled ? 'on' : 'off';
if (isEnabled) {
// 如果蓝牙已开启,刷新设备连接
await _bluetoothService.refreshDeviceConnection();
}
} catch (e) {
Logger.e(_tag, '检查蓝牙状态失败: ${e.toString()}');
_bluetoothState.value = 'off';
}
if (_bluetoothState.value != 'on') {
statusDescription.value = 'bluetoothTurnedOff'.tr; statusDescription.value = 'bluetoothTurnedOff'.tr;
actionButtonText.value = '去系统设置'; actionButtonText.value = '去系统设置';
return; return;
@ -191,94 +199,41 @@ class PairingController extends GetxController {
List<Permission> permissionsToRequest = []; List<Permission> permissionsToRequest = [];
if (Platform.isAndroid) { if (Platform.isAndroid) {
// Android权限处理保持不变
permissionsToRequest.add(Permission.locationWhenInUse); permissionsToRequest.add(Permission.locationWhenInUse);
// permissionsToRequest.add(Permission.microphone);
if (await _getAndroidSdkVersion() >= 31) { if (await _getAndroidSdkVersion() >= 31) {
permissionsToRequest.add(Permission.bluetoothScan); permissionsToRequest.add(Permission.bluetoothScan);
permissionsToRequest.add(Permission.bluetoothConnect); 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原生权限请求 // 处理Android权限
bool isEnabled = await _bluetoothService.isBluetoothEnabled(); if (permissionsToRequest.isNotEmpty) {
Logger.i(_tag, 'iOS蓝牙状态检查: ${isEnabled ? "已开启" : "未开启"}'); statuses = await permissionsToRequest.request();
// 检测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上视为权限已授予 bool allGranted = true;
_hasPermissions.value = true; statuses.forEach((permission, status) {
return true; if (!status.isGranted) {
} catch (e) { allGranted = false;
Logger.e(_tag, "iOS蓝牙初始化失败: ${e.toString()}"); Logger.w(_tag, '权限 ${permission.toString()} 未被授予: $status');
}
});
// 显示自定义对话框引导用户 if (!allGranted) {
_showPermissionSettingsDialog('需要开启蓝牙权限才能配对设备'); return false;
return false; }
} }
} } else if (Platform.isIOS) {
await _bluetoothService.refreshDeviceConnection();
// 如果还有其他权限需要请求
if (permissionsToRequest.isNotEmpty) {
statuses = await permissionsToRequest.request();
bool allGranted = true; // 获取iOS蓝牙权限状态
statuses.forEach((permission, status) { final bluetoothState = await _bluetoothService.bluetoothState;
if (!status.isGranted) { final permissionStatus = await Permission.bluetooth.status;
allGranted = false;
Logger.w(_tag, '权限 ${permission.toString()} 未被授予: $status');
}
});
if (!allGranted) { Logger.i(_tag, 'iOS蓝牙状态: $bluetoothState');
return false; Logger.i(_tag, 'iOS蓝牙权限状态: $permissionStatus');
} // iOS上不需要主动请求权限,系统会在使用蓝牙时自动请求
_hasPermissions.value = true;
return true;
} }
_hasPermissions.value = true; _hasPermissions.value = true;
@ -409,6 +364,49 @@ class PairingController extends GetxController {
_updateStatusDescription(); _updateStatusDescription();
try { try {
// iOS 环境也启用完整的关联流程
if (Platform.isIOS) {
Logger.i(_tag, 'iOS: 启动完整关联流程,使用AccessorySetupKit');
// 检查是否支持Companion功能
final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) {
Logger.i(_tag, 'iOS设备不支持Companion功能,跳过关联');
_companionAssociated.value = true;
_autoNavigateToHome();
return;
}
// 检查设备名称是否符合要求
if (!_isValidDeviceName(currentDevice!.name)) {
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
_isAssociating = false;
return;
}
Logger.i(_tag, 'iOS开始Companion设备关联流程, 设备: ${currentDevice!.name}');
var success = await _bleService.startDeviceAssociation(
deviceNamePattern: currentDevice!.name,
macAddress: currentDevice!.address,
);
_isAssociating = false;
if (success) {
Logger.i(_tag, 'iOS Companion设备关联成功');
_companionAssociated.value = true;
_userRejectedAssociation.value = false;
_autoNavigateToHome();
} else {
Logger.e(_tag, 'iOS Companion设备关联失败或被用户拒绝');
_userRejectedAssociation.value = true;
_updateStatusDescription();
}
return;
}
// 下面是原有的Android关联流程
// 检查是否支持Companion功能 // 检查是否支持Companion功能
final isSupported = await _bleService.isCompanionSupported(); final isSupported = await _bleService.isCompanionSupported();
if (!isSupported) { if (!isSupported) {
@ -538,50 +536,17 @@ class PairingController extends GetxController {
isLoading.value = true; isLoading.value = true;
statusDescription.value = '正在重新初始化蓝牙...'; statusDescription.value = '正在重新初始化蓝牙...';
// iOS上尝试重置权限状态 // iOS上简化处理
if (Platform.isIOS) { if (Platform.isIOS) {
await Future.delayed(Duration(milliseconds: 500)); Logger.i(_tag, "iOS重新初始化蓝牙: 简化流程");
// 检查蓝牙权限 // 直接检查蓝牙状态并刷新连接,系统会在需要时请求权限
PermissionStatus bluetoothStatus = await Permission.bluetooth.status; await _checkBluetoothAndRefresh();
Logger.i(_tag, "iOS蓝牙权限当前状态: $bluetoothStatus"); return;
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()}");
}
} }
// 重新检查权限 // Android上的正常流程
if (await _checkAndRequestPermissions()) { if (await _checkAndRequestPermissions()) {
// 重新检查蓝牙状态
await _checkBluetoothAndRefresh(); await _checkBluetoothAndRefresh();
} }
} catch (e) { } catch (e) {

34
local_plugins/azure_speech/ios/.gitignore

@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

857
local_plugins/azure_speech/ios/Classes/AzureAsrHelper.swift

File diff suppressed because it is too large

4
local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.h

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface AzureSpeechPlugin : NSObject<FlutterPlugin>
@end

12
local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.m

@ -0,0 +1,12 @@
#import "AzureSpeechPlugin.h"
#if __has_include(<azure_speech/azure_speech-Swift.h>)
#import <azure_speech/azure_speech-Swift.h>
#else
#import "azure_speech-Swift.h"
#endif
@implementation AzureSpeechPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
[SwiftAzureSpeechPlugin registerWithRegistrar:registrar];
}
@end

530
local_plugins/azure_speech/ios/Classes/AzureSpeechPlugin.swift

@ -1,182 +1,388 @@
import Flutter import Flutter
import UIKit import UIKit
import AVFoundation
import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能
import speech
/**
* Azure Speech Plugin
*
* 基于微软Azure语音服务的Flutter插件
* 提供语音识别(ASR)和语音合成(TTS)功能
*/
public class SwiftAzureSpeechPlugin: NSObject, FlutterPlugin {
// 日志标签
private let tag = "AzureSpeechPlugin"
// ASR相关
private var asrChannel: FlutterMethodChannel?
private var asrEventChannel: FlutterEventChannel?
private var asrEventSink: FlutterEventSink?
private let azureAsrHelper = AzureAsrHelper()
// TTS相关
private var ttsChannel: FlutterMethodChannel?
private var ttsEventChannel: FlutterEventChannel?
private var ttsEventSink: FlutterEventSink?
private let azureTtsHelper = AzureTtsHelper()
// 是否已添加TTS事件监听器
private var isTtsListenerAdded = false
// 插件注册
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = SwiftAzureSpeechPlugin()
// 初始化ASR通道
let asrChannel = FlutterMethodChannel(name: "azure_speech/asr", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: asrChannel)
instance.asrChannel = asrChannel
// 初始化TTS通道
let ttsChannel = FlutterMethodChannel(name: "azure_speech/tts", binaryMessenger: registrar.messenger())
registrar.addMethodCallDelegate(instance, channel: ttsChannel)
instance.ttsChannel = ttsChannel
// 初始化ASR事件通道
let asrEventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger())
asrEventChannel.setStreamHandler(instance)
instance.asrEventChannel = asrEventChannel
// 初始化TTS事件通道
let ttsEventChannel = FlutterEventChannel(name: "azure_speech/tts_events", binaryMessenger: registrar.messenger())
ttsEventChannel.setStreamHandler(instance)
instance.ttsEventChannel = ttsEventChannel
}
// 发送ASR事件方法
private func sendAsrEvent(_ event: [String: Any]) {
if asrEventSink == nil {
print("\(tag): 无法发送ASR事件:事件通道未准备好")
return
}
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.asrEventSink?(event)
}
}
// 发送TTS事件方法
private func sendTtsEvent(_ event: [String: Any]) {
if ttsEventSink == nil {
print("\(tag): 无法发送TTS事件:事件通道未准备好")
return
}
public class AzureSpeechPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { [weak self] in
private var ttsHelper: AzureTtsHelper? guard let self = self else { return }
private var asrHelper: AzureAsrHelper? self.ttsEventSink?(event)
private var eventSink: FlutterEventSink? }
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "azure_speech", binaryMessenger: registrar.messenger())
let instance = AzureSpeechPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
// 初始化事件通道
let eventChannel = FlutterEventChannel(name: "azure_speech/asr_events", binaryMessenger: registrar.messenger())
eventChannel.setStreamHandler(AsrStreamHandler(instance: instance))
}
override init() {
super.init()
ttsHelper = AzureTtsHelper()
asrHelper = AzureAsrHelper()
// 设置ASR事件处理
asrHelper?.setEventHandler { [weak self] event in
self?.handleAsrEvent(event)
} }
}
// 设置TTS事件监听器
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { private func setupTtsEventListener() {
switch call.method { if !isTtsListenerAdded {
// TTS相关方法 azureTtsHelper.addListener(self)
case "initializeTts": isTtsListenerAdded = true
guard let args = call.arguments as? [String: Any], }
let subscriptionKey = args["subscriptionKey"] as? String, }
let serviceRegion = args["serviceRegion"] as? String else {
result(false) // 设置ASR事件监听器
return private func setupAsrEventListener() {
} azureAsrHelper.addListener(self)
}
let language = args["language"] as? String ?? "zh-CN"
let success = ttsHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, language: language) ?? false // 处理Flutter方法调用
result(success) public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
if call.method.hasPrefix("tts_") {
case "setTtsVoice": handleTtsMethodCall(call, result)
guard let args = call.arguments as? [String: Any],
let voiceName = args["voiceName"] as? String else {
result(false)
return
}
let success = ttsHelper?.setVoice(voiceName: voiceName) ?? false
result(success)
case "setTtsSpeechParams":
guard let args = call.arguments as? [String: Any],
let rate = args["rate"] as? Int,
let pitch = args["pitch"] as? Int,
let volume = args["volume"] as? Int else {
result(false)
return
}
let success = ttsHelper?.setSpeechParams(rate: rate, pitch: pitch, volume: volume) ?? false
result(success)
case "speakText":
guard let args = call.arguments as? [String: Any],
let text = args["text"] as? String else {
result(false)
return
}
ttsHelper?.speakText(text: text) { success, _ in
result(success)
}
case "stopSpeaking":
let success = ttsHelper?.stopSpeaking() ?? false
result(success)
case "isSpeaking":
let speaking = ttsHelper?.isSpeaking() ?? false
result(speaking)
case "setTtsAudioOutputType":
guard let args = call.arguments as? [String: Any],
let outputType = args["outputType"] as? String else {
result(false)
return
}
var type: AzureTtsHelper.AudioOutputType = .auto
switch outputType.uppercased() {
case "SPEAKER":
type = .speaker
case "EARPIECE":
type = .earpiece
default:
type = .auto
}
let success = ttsHelper?.setAudioOutputType(outputType: type) ?? false
result(success)
// ASR相关方法
case "initializeAsr":
guard let args = call.arguments as? [String: Any],
let subscriptionKey = args["subscriptionKey"] as? String,
let serviceRegion = args["serviceRegion"] as? String,
let supportedLanguages = args["supportedLanguages"] as? [String] else {
result(false)
return
}
let success = asrHelper?.initialize(speechSubscriptionKey: subscriptionKey, serviceRegion: serviceRegion, supportedLanguages: supportedLanguages) ?? false
result(success)
case "recognizeOnce":
asrHelper?.recognizeOnce { success, text, language, error in
var resultMap: [String: Any] = ["success": success]
if success {
resultMap["text"] = text
resultMap["language"] = language
} else { } else {
resultMap["error"] = error handleAsrMethodCall(call, result)
}
}
// MARK: - ASR 方法处理
private func handleAsrMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
switch call.method {
case "initialize":
guard let args = call.arguments as? [String: Any],
let subscriptionKey = args["subscriptionKey"] as? String,
let region = args["region"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil))
return
}
let supportedLanguages = args["supportedLanguages"] as? [String]
// 初始化ASR引擎并设置事件监听
let success = azureAsrHelper.initialize(subscriptionKey: subscriptionKey, region: region, supportedLanguages: supportedLanguages)
setupAsrEventListener()
result(success)
case "recognizeOnce":
guard let args = call.arguments as? [String: Any],
let audioBytes = args["data"] as? FlutterStandardTypedData else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil))
return
}
// 执行一次性识别
let success = azureAsrHelper.recognizeOnce(audioData: audioBytes.data) { recognizedText in
result(["text": recognizedText, "detectedLanguage": ""])
}
if !success {
result(FlutterError(code: "RECOGNITION_ERROR", message: "启动识别失败", details: nil))
}
case "startContinuousRecognition":
// 确保事件通道已准备好
if asrEventSink == nil {
result(FlutterError(code: "EVENT_CHANNEL_NOT_READY", message: "事件通道未准备好,无法开始连续识别", details: nil))
return
}
let success = azureAsrHelper.startContinuousRecognition()
result(success)
case "stopContinuousRecognition":
let success = azureAsrHelper.stopContinuousRecognition()
result(success)
case "isContinuousRecognitionActive":
result(azureAsrHelper.isRecognitionActive())
case "dispose":
azureAsrHelper.dispose()
result(true)
case "pushAudioData":
guard let args = call.arguments as? [String: Any],
let audioBytes = args["data"] as? FlutterStandardTypedData else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "音频数据不能为空", details: nil))
return
}
let success = azureAsrHelper.pushAudioData(audioData: audioBytes.data)
result(success)
default:
result(FlutterMethodNotImplemented)
}
}
// MARK: - TTS 方法处理
private func handleTtsMethodCall(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
switch call.method {
case "tts_initialize":
guard let args = call.arguments as? [String: Any],
let subscriptionKey = args["subscriptionKey"] as? String,
let region = args["region"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "必要的参数不能为空", details: nil))
return
}
// 初始化TTS引擎
let language = args["language"] as? String ?? "zh-CN"
let success = azureTtsHelper.initialize(ttsAppId: "", ttsAppToken: subscriptionKey, ttsResource: region, language: language)
// 设置TTS事件监听器
setupTtsEventListener()
result(success)
case "tts_set_voice":
guard let args = call.arguments as? [String: Any],
let voiceName = args["voiceName"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "语音名称不能为空", details: nil))
return
}
let success = azureTtsHelper.setVoice(voiceName)
result(success)
case "tts_speak_once":
guard let args = call.arguments as? [String: Any],
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = azureTtsHelper.speakOnce(text)
result(success)
case "tts_speak_stream":
guard let args = call.arguments as? [String: Any],
let text = args["text"] as? String else {
result(FlutterError(code: "INVALID_ARGUMENTS", message: "文本不能为空", details: nil))
return
}
let success = azureTtsHelper.speakStream(text)
result(success)
case "tts_flush_stream":
let success = azureTtsHelper.flushStream()
result(success)
case "tts_stop":
let success = azureTtsHelper.stop()
result(success)
case "tts_isSpeaking":
result(azureTtsHelper.isSpeaking())
case "tts_release":
azureTtsHelper.dispose()
result(true)
default:
result(FlutterMethodNotImplemented)
} }
result(resultMap) }
} }
case "startContinuousRecognition": // MARK: - 事件处理
let success = asrHelper?.startContinuousRecognition() ?? false extension SwiftAzureSpeechPlugin: FlutterStreamHandler {
result(success) public func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
// 根据通道类型设置事件接收器
if let args = arguments as? [String: Any],
let channel = args["channel"] as? String {
if channel == "asr" {
asrEventSink = events
} else if channel == "tts" {
ttsEventSink = events
setupTtsEventListener()
}
} else if arguments == nil {
// 如果没有指定通道,尝试弄清楚是哪个通道在监听
if asrEventSink == nil && ttsEventSink != nil {
asrEventSink = events
} else if asrEventSink != nil && ttsEventSink == nil {
ttsEventSink = events
setupTtsEventListener()
} else {
// 无法确定哪个通道,默认设置为ASR
asrEventSink = events
}
}
case "stopContinuousRecognition": return nil
let success = asrHelper?.stopContinuousRecognition() ?? false }
result(success)
case "isContinuousRecognitionActive": public func onCancel(withArguments arguments: Any?) -> FlutterError? {
let isActive = asrHelper?.isContinuousRecognitionActive() ?? false // 根据通道类型清除事件接收器
result(isActive) if let args = arguments as? [String: Any],
let channel = args["channel"] as? String {
case "dispose": if channel == "asr" {
ttsHelper?.dispose() asrEventSink = nil
asrHelper?.dispose() } else if channel == "tts" {
result(nil) ttsEventSink = nil
}
} else {
// 如果没有指定通道,清除所有通道
asrEventSink = nil
ttsEventSink = nil
}
default: return nil
result(FlutterMethodNotImplemented)
} }
} }
// 设置事件接收器 // MARK: - ASR 事件监听实现
func setEventSink(_ sink: FlutterEventSink?) { extension SwiftAzureSpeechPlugin: AsrEventListener {
self.eventSink = sink // 为ASR事件监听提供明确的选择器名称
} @objc(onAsrEvent:)
public func onEvent(_ event: AsrEvent) {
var eventMap: [String: Any] = [
"type": getAsrEventTypeName(event.type)
]
// 添加参数
for (key, value) in event.params {
eventMap[key] = value
}
// 处理ASR事件 sendAsrEvent(eventMap)
private func handleAsrEvent(_ event: [String: Any]) { }
self.eventSink?(event)
} private func getAsrEventTypeName(_ type: AsrEventType) -> String {
switch type {
case .sessionStarted:
return "sessionStarted"
case .sessionStopped:
return "sessionStopped"
case .recognizing:
return "recognizing"
case .recognized:
return "result"
case .canceled:
return "canceled"
case .error:
return "error"
@unknown default:
return "unknown"
}
}
} }
// ASR事件流处理器 // MARK: - TTS 事件监听实现
class AsrStreamHandler: NSObject, FlutterStreamHandler { extension SwiftAzureSpeechPlugin: TtsEventListener {
private weak var plugin: AzureSpeechPlugin? // 使用@objc特性为方法提供一个不同的Objective-C选择器名称
@objc(onTtsEvent:)
public func onEvent(_ event: TtsEvent) {
var eventMap: [String: Any] = [:]
// 根据事件类型转换
switch event.type {
case .synthesisStarted:
eventMap["type"] = "synthesis_started"
case .synthesisCompleted:
eventMap["type"] = "synthesis_completed"
case .synthesisCanceled:
eventMap["type"] = "synthesis_canceled"
if let reason = event.params["reason"] {
eventMap["reason"] = reason
}
if let errorDetails = event.params["errorDetails"] {
eventMap["errorDetails"] = errorDetails
}
case .error:
eventMap["type"] = "error"
if let errorCode = event.params["errorCode"] {
eventMap["errorCode"] = errorCode
}
if let errorMessage = event.params["errorMessage"] {
eventMap["errorMessage"] = errorMessage
}
@unknown default:
eventMap["type"] = "unknown"
for (key, value) in event.params {
eventMap[key] = value
}
}
init(instance: AzureSpeechPlugin) { sendTtsEvent(eventMap)
self.plugin = instance }
super.init() }
}
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // MARK: - 音频数据监听实现
plugin?.setEventSink(events) extension SwiftAzureSpeechPlugin: AudioDataListener {
return nil public func onAudioData(_ data: Data) {
} if ttsEventSink == nil { return }
func onCancel(withArguments arguments: Any?) -> FlutterError? { let eventMap: [String: Any] = [
plugin?.setEventSink(nil) "type": "audio_data",
return nil "data": FlutterStandardTypedData(bytes: data)
} ]
sendTtsEvent(eventMap)
}
} }

633
local_plugins/azure_speech/ios/Classes/AzureTtsHelper.swift

@ -1,315 +1,175 @@
import Foundation import Foundation
import AVFoundation import AVFoundation
import MicrosoftCognitiveServicesSpeech import MicrosoftCognitiveServicesSpeech
// 自定义语音处理组件,提供音频流处理等功能
/// Azure 语音合成辅助类 import speech
class AzureTtsHelper: NSObject {
// 常量定义 /**
* Azure TTS Helper
*
* 基于微软Azure语音服务的TTS实现
* 参考文档: https://learn.microsoft.com/en-us/azure/ai-services/speech-service/how-to-speech-synthesis
*/
class AzureTtsHelper: NSObject, ITtsService {
private let tag = "AzureTtsHelper" private let tag = "AzureTtsHelper"
private let DEFAULT_LANGUAGE = "zh-CN"
private let DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural"
// 核心组件 private static let DEFAULT_LANGUAGE = "zh-CN"
private var synthesizer: SPXSpeechSynthesizer? private static let DEFAULT_VOICE = "zh-CN-XiaoxiaoNeural"
// Azure语音服务配置
private var speechConfig: SPXSpeechConfiguration? private var speechConfig: SPXSpeechConfiguration?
private var audioConfig: SPXAudioConfiguration? private var synthesizer: SPXSpeechSynthesizer?
private var initialized = false private var isInitialized = false
private var speaking = false private var speaking = false
// 当前设置 // 当前配置
private var currentVoice = "zh-CN-XiaoxiaoNeural" private var currentVoice = DEFAULT_VOICE
private var currentRate = "0%" private var currentLanguage = DEFAULT_LANGUAGE
private var currentPitch = "0%" private var currentRate = "+0%"
private var currentPitch = "+0%"
private var currentVolume = "100%" private var currentVolume = "100%"
// 音频会话管理 // 事件监听器列表
private let audioSession = AVAudioSession.sharedInstance() private var eventListeners = NSHashTable<AnyObject>.weakObjects()
// 自定义音频输出流
private var customAudioOutputStream: SPXPushAudioOutputStream?
// 流式文本处理的缓冲区 // 流式文本处理的缓冲区
private var streamBuffer = "" private var streamBuffer = ""
private var lastSpeakTime: TimeInterval = 0 private var lastSpeakTime: TimeInterval = 0
// 应用上下文 // 音频会话
private let context: Any private let audioSession = AVAudioSession.sharedInstance()
// 全局回调
private var ttsCallback: TtsCallback?
/// TTS回调接口
protocol TtsCallback {
/// 合成开始
func onSynthesisStarted()
/// 合成中
func onSynthesizing()
/// 合成完成
func onSynthesisCompleted()
/// 合成取消
func onSynthesisCanceled()
}
/// 初始化
/// - Parameter context: 应用上下文
init(_ context: Any) {
self.context = context
super.init()
}
/// 初始化 TTS 引擎
/// - Parameters:
/// - subscriptionKey: Azure 语音服务订阅密钥
/// - region: Azure 语音服务区域
/// - callback: 可选,全局TTS回调接口
/// - Returns: 是否初始化成功
func initialize(
subscriptionKey: String,
region: String,
callback: TtsCallback? = nil
) -> Bool {
print("\(tag): 初始化 Azure 语音服务")
// 检查配置是否为空
if subscriptionKey.isEmpty || region.isEmpty {
print("\(tag): 错误: Azure 配置信息不完整")
return false
}
// 释放之前的资源
dispose()
// 保存全局回调
ttsCallback = callback
/**
* 初始化语音合成服务
*
* @param appId 服务应用ID
* @param token 服务访问令牌/订阅密钥
* @param resource 服务资源ID/区域(可选)
* @param language 语言代码,如"zh-CN"
* @return 是否初始化成功
*/
func initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String, language: String) -> Bool {
do { do {
// 创建语音配置 // 创建语音配置
speechConfig = try SPXSpeechConfiguration(subscription: subscriptionKey, region: region) if ttsResource.isEmpty {
speechConfig = try SPXSpeechConfiguration(subscription: ttsAppToken, region: "eastasia")
// 设置语音合成输出格式为高质量音频
speechConfig?.setSpeechSynthesisOutputFormat(.riff24Khz16BitMonoPcm)
// 设置默认语音
speechConfig?.setSpeechSynthesisVoiceName(currentVoice)
// 创建音频配置
if let customOutput = customAudioOutputStream {
// 使用自定义音频输出流
audioConfig = try SPXAudioConfiguration(streamOutput: customOutput)
} else { } else {
// 使用默认扬声器 speechConfig = try SPXSpeechConfiguration(subscription: ttsAppToken, region: ttsResource)
audioConfig = SPXAudioConfiguration()
} }
// 创建语音合成器 // 设置语言
synthesizer = try SPXSpeechSynthesizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!) speechConfig?.speechSynthesisLanguage = language
currentLanguage = language
// 设置事件监听
setupEventListeners()
initialized = true
print("\(tag): TTS 引擎初始化成功")
return true
} catch {
print("\(tag): TTS 引擎初始化失败: \(error.localizedDescription)")
return false
}
}
/// 设置事件监听器
private func setupEventListeners() {
guard let synthesizer = synthesizer else { return }
// 合成开始事件
synthesizer.addSynthesisStarted { [weak self] _, _ in
guard let self = self else { return }
self.ttsCallback?.onSynthesisStarted()
}
// 合成中事件
synthesizer.addSynthesizing { [weak self] _, _ in
guard let self = self else { return }
self.ttsCallback?.onSynthesizing()
}
// 合成完成事件
synthesizer.addSynthesisCompleted { [weak self] _, _ in
guard let self = self else { return }
self.speaking = false
self.ttsCallback?.onSynthesisCompleted()
}
// 合成取消事件
synthesizer.addSynthesisCanceled { [weak self] _, _ in
guard let self = self else { return }
print("\(self.tag): 语音合成取消")
self.speaking = false
self.ttsCallback?.onSynthesisCanceled()
}
}
/// 设置TTS回调
/// - Parameter callback: TTS回调接口
func setTtsCallback(_ callback: TtsCallback?) {
ttsCallback = callback
}
/// 设置自定义音频输出流 // 设置音频输出格式 - 使用16k、16位的PCM格式
/// - Parameter outputStream: 自定义音频输出流,如果为nil则使用默认音频输出 speechConfig?.setPropertyTo("Audio16Khz16BitMonoPcm",
/// - Returns: 是否设置成功 byName: "SpeechServiceConnection_SynthOutputFormat")
func setCustomAudioOutputStream(_ outputStream: SPXPushAudioOutputStream?) -> Bool {
do {
// 保存引用
customAudioOutputStream = outputStream
// 如果已初始化,需要重新创建合成器以应用新的音频输出流 // 创建合成器,使用默认音频输出(扬声器)
if initialized { synthesizer = try SPXSpeechSynthesizer(speechConfig!)
recreateSynthesizer()
}
return true // 设置事件监听
} catch { setupEventListeners()
print("\(tag): 设置自定义音频输出流失败: \(error.localizedDescription)")
return false
}
}
/// 重新创建合成器
private func recreateSynthesizer() {
do {
// 关闭现有合成器
synthesizer = nil
// 创建音频配置 // 设置默认音色 - 中文默认使用晓晓,英文默认使用Jenny
if let customOutput = customAudioOutputStream { if language.lowercased().starts(with: "zh") {
// 使用自定义音频输出流 _ = setVoice("zh-CN-XiaoxiaoNeural")
audioConfig = try SPXAudioConfiguration(streamOutput: customOutput)
} else { } else {
// 使用默认扬声器 _ = setVoice("en-US-JennyNeural")
audioConfig = SPXAudioConfiguration()
} }
// 使用新的音频配置创建合成器 // 设置初始化完成
synthesizer = try SPXSpeechSynthesizer(speechConfiguration: speechConfig!, audioConfiguration: audioConfig!) isInitialized = true
// 重新设置事件监听 return true
setupEventListeners()
} catch { } catch {
print("\(tag): 重新创建合成器失败: \(error.localizedDescription)") print("\(tag): 语音合成服务初始化失败: \(error.localizedDescription)")
return false
} }
} }
/// 设置语音 /**
/// - Parameter voiceName: 语音名称,例如 "zh-CN-XiaoxiaoNeural" * 设置语音角色
/// - Returns: 是否设置成功 *
func setVoice(voiceName: String) -> Bool { * @param voiceName 语音角色名称(不同服务的语音角色命名可能不同)
if !initialized { * @return 是否设置成功
print("\(tag): TTS 引擎尚未初始化") */
return false func setVoice(_ voiceName: String) -> Bool {
} if !isInitialized { return false }
if voiceName == currentVoice {
print("\(tag): 已设置语音: \(voiceName)")
return true
}
do { do {
currentVoice = voiceName currentVoice = voiceName
speechConfig?.setSpeechSynthesisVoiceName(voiceName)
// 更新语音名称
if let config = speechConfig {
config.speechSynthesisVoiceName = voiceName
}
// 重新创建合成器 // 重新创建合成器
recreateSynthesizer() recreateSynthesizer()
print("\(tag): 已设置语音: \(voiceName)")
return true return true
} catch { } catch {
print("\(tag): 设置语音失败: \(error.localizedDescription)") print("\(tag): 设置语音失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "VOICE_SET_FAILED",
"errorMessage": "设置语音失败: \(error.localizedDescription)"
])
return false return false
} }
} }
/// 设置语音合成参数 /**
/// - Parameters: * 单次合成并播放
/// - rate: 语速,范围 -100 到 100,默认为 0 *
/// - pitch: 音调,范围 -100 到 100,默认为 0 * @param text 要合成的文本
/// - volume: 音量,范围 0 到 100,默认为 100 * @return 是否成功开始合成
/// - Returns: 是否设置成功 */
func setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100) -> Bool { func speakOnce(_ text: String) -> Bool {
if !initialized { if !isInitialized {
print("\(tag): TTS 引擎尚未初始化") print("\(tag): 语音合成未初始化")
return false return false
} }
do { // 已经在播放,先停止
currentRate = formatPercentage(rate) if speaking {
currentPitch = formatPercentage(pitch) _ = stop()
currentVolume = "\(min(max(volume, 0), 100))%"
print("\(tag): 已设置语音参数: 语速=\(rate), 音调=\(pitch), 音量=\(volume)")
return true
} catch {
print("\(tag): 设置语音参数失败: \(error.localizedDescription)")
return false
} }
}
/// 格式化百分比值
private func formatPercentage(_ value: Int) -> String {
return value >= 0 ? "+\(value)%" : "\(value)%"
}
/// 生成 SSML 文本 // 重置状态
/// - Parameter text: 要转换的文本 speaking = true
/// - Returns: SSML 格式的文本
private func generateSsml(text: String) -> String {
return """
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts" xml:lang="zh-CN">
<voice name="\(currentVoice)">
<prosody rate="\(currentRate)" pitch="\(currentPitch)" volume="\(currentVolume)">
\(text)
</prosody>
</voice>
</speak>
"""
}
/// 合成文本为语音并播放 // 生成SSML
/// - Parameter text: 要合成的文本 let ssml = generateSsml(text)
/// - Returns: 是否成功开始合成
func speakText(_ text: String) -> Bool {
if !initialized {
print("\(tag): TTS 引擎尚未初始化")
return false
}
do { do {
print("\(tag): 开始合成文本: \(text)") // 创建合成任务
try synthesizer?.startSpeakingSsml(ssml)
// 生成 SSML
let ssml = generateSsml(text: text)
// 标记为正在播放
speaking = true
// 异步合成语音
try synthesizer?.speakSsmlAsync(ssml)
return true return true
} catch { } catch {
print("\(tag): 语音合成异常: \(error.localizedDescription)") print("\(tag): 语音合成失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "SYNTHESIS_FAILED",
"errorMessage": error.localizedDescription
])
speaking = false speaking = false
return false return false
} }
} }
/// 处理流式文本并在遇到标点符号时播放 /**
/// - Parameter text: 收到的文本流片段 * 流式合成文本
/// - Returns: 是否成功处理 *
* @param text 要合成的文本片段
* @return 是否成功处理
*/
func speakStream(_ text: String) -> Bool { func speakStream(_ text: String) -> Bool {
if !initialized || text.isEmpty { if !isInitialized || text.isEmpty {
if !isInitialized {
notifyEvent(eventType: .error, params: [
"errorCode": "NOT_INITIALIZED",
"errorMessage": "TTS引擎未初始化"
])
}
return false return false
} }
@ -319,7 +179,7 @@ class AzureTtsHelper: NSObject {
// 增加500ms防抖逻辑 // 增加500ms防抖逻辑
let currentTime = Date().timeIntervalSince1970 let currentTime = Date().timeIntervalSince1970
if currentTime - lastSpeakTime < 0.5 { if currentTime - lastSpeakTime < 0.6 {
return true return true
} }
lastSpeakTime = currentTime lastSpeakTime = currentTime
@ -327,13 +187,13 @@ class AzureTtsHelper: NSObject {
let currentText = streamBuffer let currentText = streamBuffer
// 定义标点符号列表 // 定义标点符号列表
let punctuationMarks: [Character] = [".", "。", "!", "!", "?", "?", ";", ";", ",", ",", ":", ":"] let punctuationMarks: [Character] = [".", "。", "!", "!", "?", "?", ";", ";", ",", ",", ":", ":", "\n"]
// 查找最后一个标点符号的位置 // 查找最后一个标点符号的位置
var lastPunctuationIndex = -1 var lastPunctuationIndex = -1
for i in currentText.indices.reversed() { for (i, char) in currentText.enumerated().reversed() {
if punctuationMarks.contains(currentText[i]) { if punctuationMarks.contains(char) {
lastPunctuationIndex = currentText.distance(from: currentText.startIndex, to: i) lastPunctuationIndex = i
break break
} }
} }
@ -344,24 +204,36 @@ class AzureTtsHelper: NSObject {
let textToSpeak = String(currentText.prefix(lastPunctuationIndex + 1)) let textToSpeak = String(currentText.prefix(lastPunctuationIndex + 1))
// 剩余的文本保存在缓冲区中 // 剩余的文本保存在缓冲区中
streamBuffer = String(currentText.dropFirst(lastPunctuationIndex + 1)) let startIndex = currentText.index(currentText.startIndex, offsetBy: lastPunctuationIndex + 1)
streamBuffer = String(currentText[startIndex...])
// 播放提取的文本 // 播放提取的文本
return speakText(textToSpeak) return speakOnce(textToSpeak)
} }
// 如果没有找到标点符号,则等待更多文本 // 如果没有找到标点符号,则等待更多文本
return true return true
} catch { } catch {
print("\(tag): 流式语音合成失败: \(error.localizedDescription)") print("\(tag): 流式语音合成失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "STREAM_FAILED",
"errorMessage": "流式语音合成失败: \(error.localizedDescription)"
])
return false return false
} }
} }
/// 播放剩余的流式文本 /**
/// - Returns: 是否成功播放剩余文本 * 刷新并播放流式文本缓冲区中的剩余内容
*
* @return 是否成功处理
*/
func flushStream() -> Bool { func flushStream() -> Bool {
if !initialized { if !isInitialized {
notifyEvent(eventType: .error, params: [
"errorCode": "NOT_INITIALIZED",
"errorMessage": "TTS引擎未初始化"
])
return false return false
} }
@ -378,69 +250,248 @@ class AzureTtsHelper: NSObject {
} }
// 播放剩余文本 // 播放剩余文本
return speakText(remainingText) return speakOnce(remainingText)
} catch { } catch {
print("\(tag): 刷新流式文本失败: \(error.localizedDescription)") print("\(tag): 刷新流式文本失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "FLUSH_FAILED",
"errorMessage": "刷新流式文本失败: \(error.localizedDescription)"
])
return false return false
} }
} }
/// 停止当前语音合成 /**
/// - Returns: 是否停止成功 * 停止语音合成和播放
func stopSpeaking() -> Bool { *
if !initialized { * @return 是否成功停止
print("\(tag): TTS 引擎尚未初始化") */
return false func stop() -> Bool {
if !speaking || synthesizer == nil {
return true
} }
do { do {
// 清空流缓冲区 // 停止合成
streamBuffer = "" try synthesizer?.stopSpeaking()
try synthesizer?.stopSpeakingAsync()
speaking = false speaking = false
print("\(tag): 已停止语音合成")
return true return true
} catch { } catch {
print("\(tag): 停止语音合成失败: \(error.localizedDescription)") print("\(tag): 停止语音合成失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "STOP_FAILED",
"errorMessage": error.localizedDescription
])
return false return false
} }
} }
/// 释放资源 /**
* 释放资源
* 在不再需要服务时调用,释放底层资源
*/
func dispose() { func dispose() {
do { do {
stopSpeaking() // 停止播放
_ = stop()
// 恢复音频会话 // 释放合成器
try audioSession.setActive(false, options: .notifyOthersOnDeactivation) synthesizer = nil
// 关闭自定义音频输出流 // 释放配置
customAudioOutputStream = nil speechConfig = nil
// 清空流缓冲区 // 清空流缓冲区
streamBuffer = "" streamBuffer = ""
// 清理资源 // 重置状态
synthesizer = nil isInitialized = false
speechConfig = nil
audioConfig = nil
ttsCallback = nil
initialized = false
speaking = false speaking = false
print("\(tag): TTS 引擎已释放")
} catch { } catch {
print("\(tag): 释放 TTS 引擎失败: \(error.localizedDescription)") // 即使发生异常,也确保重置状态
// 确保重置状态 isInitialized = false
initialized = false
speaking = false speaking = false
} }
} }
/// 检查当前是否正在播放语音 /**
/// - Returns: 是否正在播放语音 * 添加TTS事件监听器
*
* @param listener 事件监听器
*/
func addListener(_ listener: TtsEventListener) {
eventListeners.add(listener as AnyObject)
}
/**
* 移除TTS事件监听器
*
* @param listener 要移除的事件监听器
*/
func removeListener(_ listener: TtsEventListener) {
eventListeners.remove(listener as AnyObject)
}
/**
* 添加音频数据监听器
* 由于不再支持自定义音频流,此方法实际上不再有效
*
* @param listener 音频数据监听器
*/
func addAudioDataListener(_ listener: AudioDataListener) {
print("\(tag): 警告:不支持音频数据监听器功能")
}
/**
* 移除音频数据监听器
* 由于不再支持自定义音频流,此方法实际上不再有效
*
* @param listener 要移除的音频数据监听器
*/
func removeAudioDataListener(_ listener: AudioDataListener) {
// 不做任何操作
}
/**
* 当前是否正在播放/合成
*/
func isSpeaking() -> Bool { func isSpeaking() -> Bool {
return speaking return speaking
} }
// MARK: - 辅助方法
/**
* 设置事件监听器
*/
private func setupEventListeners() {
guard let synthesizer = synthesizer else { return }
// 添加合成开始事件处理器
synthesizer.addSynthesisStartedEventHandler { [weak self] _, _ in
guard let self = self else { return }
self.notifyEvent(eventType: .synthesisStarted)
}
// 添加合成中事件处理器
synthesizer.addSynthesizingEventHandler { [weak self] _, _ in
// 可以在这里处理合成中的事件,目前没有特别操作
}
// 添加合成完成事件处理器
synthesizer.addSynthesisCompletedEventHandler { [weak self] _, e in
guard let self = self else { return }
self.speaking = false
self.notifyEvent(eventType: .synthesisCompleted)
}
// 添加合成取消事件处理器
synthesizer.addSynthesisCanceledEventHandler { [weak self] _, e in
guard let self = self else { return }
print("\(self.tag): 语音合成取消")
self.speaking = false
var params: [String: Any] = [:]
do {
let cancellationDetails = try SPXSpeechSynthesisCancellationDetails(fromCanceledSynthesisResult: e.result)
if cancellationDetails.reason == SPXCancellationReason.error {
params["reason"] = String(describing: cancellationDetails.reason.rawValue)
params["errorDetails"] = cancellationDetails.errorDetails ?? "未知错误"
}
} catch {
params["errorDetails"] = "获取取消详情失败: \(error.localizedDescription)"
}
self.notifyEvent(eventType: .synthesisCanceled, params: params)
}
}
/**
* 触发事件通知
*/
private func notifyEvent(eventType: TtsEventType, params: [String: Any] = [:]) {
let event = TtsEvent(type: eventType, params: params)
// 在主线程通知事件
DispatchQueue.main.async {
for case let listener as TtsEventListener in self.eventListeners.allObjects {
listener.onEvent(event)
}
}
}
/**
* 重新创建合成器
*/
private func recreateSynthesizer() {
do {
// 使用默认音频输出配置创建合成器
synthesizer = try SPXSpeechSynthesizer(speechConfig!)
// 设置事件监听
setupEventListeners()
} catch {
print("\(tag): 重新创建合成器失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "RECREATE_FAILED",
"errorMessage": "重新创建合成器失败: \(error.localizedDescription)"
])
}
}
/**
* 设置语音参数
*/
private func setSpeechParams(rate: Int = 0, pitch: Int = 0, volume: Int = 100) -> Bool {
if !isInitialized { return false }
do {
currentRate = formatPercentage(rate)
currentPitch = formatPercentage(pitch)
currentVolume = "\(min(max(volume, 0), 100))%"
return true
} catch {
print("\(tag): 设置语音参数失败: \(error.localizedDescription)")
notifyEvent(eventType: .error, params: [
"errorCode": "PARAMS_SET_FAILED",
"errorMessage": "设置语音参数失败: \(error.localizedDescription)"
])
return false
}
}
/**
* 格式化百分比值
*/
private func formatPercentage(_ value: Int) -> String {
return value >= 0 ? "+\(value)%" : "\(value)%"
}
/**
* 生成SSML
*/
private func generateSsml(_ rawText: String) -> String {
// 转义 XML 保留字符
var escapedText = rawText
.replacingOccurrences(of: "&", with: "&amp;")
.replacingOccurrences(of: "<", with: "&lt;")
.replacingOccurrences(of: ">", with: "&gt;")
// 构造简化的SSML文档
let ssml = """
<speak version="1.0"
xmlns="http://www.w3.org/2001/10/synthesis"
xmlns:mstts="https://www.w3.org/2001/mstts"
xml:lang="zh-CN">
<voice name="\(currentVoice)">
<prosody rate="\(currentRate)" pitch="\(currentPitch)" volume="\(currentVolume)">
\(escapedText)
</prosody>
</voice>
</speak>
"""
return ssml
}
} }

6
local_plugins/azure_speech/ios/Classes/azure_speech-Bridging-Header.h

@ -0,0 +1,6 @@
#ifndef azure_speech_Bridging_Header_h
#define azure_speech_Bridging_Header_h
#import <Foundation/Foundation.h>
#endif /* azure_speech_Bridging_Header_h */

5
local_plugins/azure_speech/ios/Classes/azure_speech.h

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
#import "AzureSpeechPlugin.h"
// 添加其他公共头文件导出

26
local_plugins/azure_speech/ios/Flutter/AppFrameworkInfo.plist

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>

1
local_plugins/azure_speech/ios/Flutter/Debug.xcconfig

@ -1 +0,0 @@
#include "Generated.xcconfig"

1
local_plugins/azure_speech/ios/Flutter/Release.xcconfig

@ -1 +0,0 @@
#include "Generated.xcconfig"

619
local_plugins/azure_speech/ios/Runner.xcodeproj/project.pbxproj

@ -1,619 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
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 = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PNNU77KTCP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PNNU77KTCP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = PNNU77KTCP;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.azureSpeech;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

7
local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

8
local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

8
local_plugins/azure_speech/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

99
local_plugins/azure_speech/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

7
local_plugins/azure_speech/ios/Runner.xcworkspace/contents.xcworkspacedata

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

8
local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

8
local_plugins/azure_speech/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

13
local_plugins/azure_speech/ios/Runner/AppDelegate.swift

@ -1,13 +0,0 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

122
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json

@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

23
local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json

@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

BIN
local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

5
local_plugins/azure_speech/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md

@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

37
local_plugins/azure_speech/ios/Runner/Base.lproj/LaunchScreen.storyboard

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

26
local_plugins/azure_speech/ios/Runner/Base.lproj/Main.storyboard

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

49
local_plugins/azure_speech/ios/Runner/Info.plist

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Azure Speech</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>azure_speech</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

1
local_plugins/azure_speech/ios/Runner/Runner-Bridging-Header.h

@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"

12
local_plugins/azure_speech/ios/RunnerTests/RunnerTests.swift

@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

11
local_plugins/azure_speech/ios/azure_speech.podspec

@ -5,18 +5,19 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = 'azure_speech' s.name = 'azure_speech'
s.version = '0.0.1' s.version = '0.0.1'
s.summary = 'Azure Speech plugin' s.summary = 'Azure Speech SDK Flutter plugin'
s.description = <<-DESC s.description = <<-DESC
A Flutter plugin for Azure Speech services. A Flutter plugin for Azure Speech Services providing speech recognition and text-to-speech capabilities.
DESC DESC
s.homepage = 'http://example.com' s.homepage = 'https://github.com/yunqiinnovation/azure_speech'
s.license = { :file => '../LICENSE' } s.license = { :file => '../LICENSE' }
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.dependency 'Flutter' s.dependency 'Flutter'
s.dependency 'MicrosoftCognitiveServicesSpeech-iOS' s.dependency 'speech'
s.platform = :ios, '13.0' s.dependency 'MicrosoftCognitiveServicesSpeech-iOS', '~> 1.30.0'
s.platform = :ios, '11.0'
# Flutter.framework does not contain a i386 slice. # Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }

20
local_plugins/azure_speech/lib/main.dart

@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
);
}
}

109
local_plugins/ble_service/ios/Classes/BleAccessorySetup.swift

@ -22,8 +22,13 @@ class BleAccessorySetup: NSObject {
class AccessorySessionWrapper { class AccessorySessionWrapper {
var session: ASAccessorySession var session: ASAccessorySession
init() { init() throws {
self.session = ASAccessorySession() do {
self.session = try ASAccessorySession()
} catch {
print("ASAccessorySession 初始化错误: \(error.localizedDescription)")
throw error
}
} }
} }
@ -57,58 +62,66 @@ class BleAccessorySetup: NSObject {
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
os_log("初始化iOS 18 AccessorySetupKit会话", log: logger, type: .info) os_log("初始化iOS 18 AccessorySetupKit会话", log: logger, type: .info)
// 创建会话 // 检查Info.plist配置
accessorySessionWrapper = AccessorySessionWrapper() let plistCheck = Bundle.main.object(forInfoDictionaryKey: "NSAccessorySetupKitSupports")
os_log("Info.plist NSAccessorySetupKitSupports: %{public}@", log: logger, type: .info, String(describing: plistCheck))
// 激活会话并设置事件处理
if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper { // 创建会话 (使用try-catch)
wrapper.session.activate(on: .main) { [weak self] event in do {
guard let self = self else { return } accessorySessionWrapper = try AccessorySessionWrapper()
switch event.eventType { // 激活会话并设置事件处理
case .activated: if let wrapper = accessorySessionWrapper as? AccessorySessionWrapper {
os_log("AccessorySetupKit会话已激活", log: self.logger, type: .info) wrapper.session.activate(on: .main) { [weak self] event in
guard let self = self else { return }
case .accessoryAdded:
if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier { switch event.eventType {
os_log("发现新设备: %{public}@", log: self.logger, type: .info, accessory.displayName) case .activated:
self.onDeviceDiscovered?(uuid) os_log("AccessorySetupKit会话已激活", log: self.logger, type: .info)
}
case .accessoryAdded:
case .accessoryRemoved: if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier {
if let accessory = event.accessory { os_log("发现新设备: %{public}@", log: self.logger, type: .info, accessory.displayName)
os_log("设备已移除: %{public}@", log: self.logger, type: .info, accessory.displayName) self.onDeviceDiscovered?(uuid)
}
case .accessoryRemoved:
if let accessory = event.accessory {
os_log("设备已移除: %{public}@", log: self.logger, type: .info, accessory.displayName)
}
// 根据WWDC24视频,正确的事件类型为accessoryChanged
case .accessoryChanged:
if let accessory = event.accessory {
os_log("设备已更新: %{public}@", log: self.logger, type: .info, accessory.displayName)
}
// 根据WWDC24视频,失败事件类型已变更
case .pickerSetupFailed:
if let error = event.error {
os_log("会话出错: %{public}@", log: self.logger, type: .error, error.localizedDescription)
}
default:
os_log("收到其他会话事件: %{public}d", log: self.logger, type: .debug, event.eventType.rawValue)
} }
// 根据WWDC24视频,正确的事件类型为accessoryChanged
case .accessoryChanged:
if let accessory = event.accessory {
os_log("设备已更新: %{public}@", log: self.logger, type: .info, accessory.displayName)
}
// 根据WWDC24视频,失败事件类型已变更
case .pickerSetupFailed:
if let error = event.error {
os_log("会话出错: %{public}@", log: self.logger, type: .error, error.localizedDescription)
}
default:
os_log("收到其他会话事件: %{public}d", log: self.logger, type: .debug, event.eventType.rawValue)
} }
}
// 检查现有授权设备 // 检查现有授权设备
if !wrapper.session.accessories.isEmpty { if !wrapper.session.accessories.isEmpty {
os_log("发现%{public}d个已授权设备", log: logger, type: .info, wrapper.session.accessories.count) os_log("发现%{public}d个已授权设备", log: logger, type: .info, wrapper.session.accessories.count)
// 如果有现有设备,触发回调 // 如果有现有设备,触发回调
for accessory in wrapper.session.accessories { for accessory in wrapper.session.accessories {
if let uuid = accessory.bluetoothIdentifier { if let uuid = accessory.bluetoothIdentifier {
os_log("处理已授权设备: %{public}@", log: logger, type: .info, accessory.displayName) os_log("处理已授权设备: %{public}@", log: logger, type: .info, accessory.displayName)
self.onDeviceDiscovered?(uuid) self.onDeviceDiscovered?(uuid)
}
} }
} }
} }
} catch {
os_log("AccessorySetupKit初始化错误: %{public}@", log: logger, type: .error, error.localizedDescription)
} }
} else { } else {
os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info) os_log("iOS 18 AccessorySetupKit不可用", log: logger, type: .info)
@ -132,7 +145,7 @@ class BleAccessorySetup: NSObject {
let descriptor = ASDiscoveryDescriptor() let descriptor = ASDiscoveryDescriptor()
// 设置过滤条件 // 设置过滤条件
// 使用ASBluetoothCompanyIdentifier类型 // 使用UInt16格式的公司标识符
descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(UInt16(0xABEF)) // BleConst.MANUFACTURER_ID descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(UInt16(0xABEF)) // BleConst.MANUFACTURER_ID
descriptor.bluetoothServiceUUID = CBUUID(string: "0000abc0-0000-1111-2222-123456789abc") // BleConst.PRIMARY_SERVICE_UUID descriptor.bluetoothServiceUUID = CBUUID(string: "0000abc0-0000-1111-2222-123456789abc") // BleConst.PRIMARY_SERVICE_UUID

95
local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift

@ -18,7 +18,7 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
// 设置BLE服务回调 // 设置BLE服务回调
BleService.shared.setDelegate(self) BleService.shared.setDelegate(self)
// 初始化AccessorySetup // 初始化AccessorySetup - iOS 18相关代码
BleAccessorySetup.shared.setBleService(BleService.shared) BleAccessorySetup.shared.setBleService(BleService.shared)
BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in BleAccessorySetup.shared.setDeviceDiscoveredCallback { [weak self] uuid in
self?.handleDiscoveredDevice(uuid: uuid) self?.handleDiscoveredDevice(uuid: uuid)
@ -56,90 +56,32 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
} }
case "isCompanionSupported": case "isCompanionSupported":
// 检查设备是否支持iOS 18的AccessorySetupKit功能 // iOS 18+ 支持 AccessorySetupKit
#if canImport(AccessorySetupKit) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
// 设备运行iOS 18+,支持AccessorySetupKit
result(true) result(true)
} else { } else {
// 设备运行iOS 17或更低版本
result(false) result(false)
} }
#else #else
// 编译时没有AccessorySetupKit框架
result(false) result(false)
#endif #endif
case "getPairedMacAddress": case "getPairedMacAddress":
// 获取已配对设备的标识符 // iOS上禁用AccessorySetupKit,始终返回无配对设备
#if canImport(AccessorySetupKit)
if #available(iOS 18.0, *) {
if let wrapper = BleAccessorySetup.shared.accessorySessionWrapper as? BleAccessorySetup.AccessorySessionWrapper {
// 检查是否有配对设备
if let firstAccessory = wrapper.session.accessories.first,
let btId = firstAccessory.bluetoothIdentifier {
// 在iOS中,我们返回UUID字符串作为"MAC地址"的等价物
result(btId.uuidString)
} else {
// 无配对设备
result(nil)
}
} else {
// 会话未初始化
result(nil)
}
} else {
// iOS版本太低
result(nil)
}
#else
// 编译环境不支持
result(nil) result(nil)
#endif
case "registerBackgroundScan": case "registerBackgroundScan":
// iOS通过AccessorySetupKit自动处理后台扫描,不需要单独注册 // iOS上禁用,直接返回成功以避免Flutter端报错
#if canImport(AccessorySetupKit) result(true)
if #available(iOS 18.0, *) {
// 在iOS 18+上,不需要额外操作
result(true)
} else {
// iOS版本太低
result(false)
}
#else
result(false)
#endif
case "unregisterBackgroundScan": case "unregisterBackgroundScan":
// iOS通过AccessorySetupKit自动处理后台扫描,不需要单独注销 // iOS上禁用,直接返回成功以避免Flutter端报错
result(true) result(true)
case "clearAssociations": case "clearAssociations":
// 清除设备关联 // iOS上禁用,直接返回成功以避免Flutter端报错
#if canImport(AccessorySetupKit) result(true)
if #available(iOS 18.0, *) {
if let wrapper = BleAccessorySetup.shared.accessorySessionWrapper as? BleAccessorySetup.AccessorySessionWrapper {
// 遍历所有配对设备
for accessory in wrapper.session.accessories {
if let btId = accessory.bluetoothIdentifier {
// 移除设备的系统处理已经集成在设置中,这里什么都不做
os_log("设备需要在系统设置中手动移除: %{public}@",
log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"),
type: .info,
btId.uuidString)
}
}
}
// 返回成功
result(true)
} else {
// iOS版本太低
result(false)
}
#else
result(false)
#endif
case "getDeviceVersion": case "getDeviceVersion":
result(BleService.shared.getDeviceVersion()) result(BleService.shared.getDeviceVersion())
@ -167,19 +109,22 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
// iOS 18+ AccessorySetupKit相关方法 // iOS 18+ AccessorySetupKit相关方法
case "startDeviceAssociation": case "startDeviceAssociation":
#if canImport(AccessorySetupKit) #if canImport(AccessorySetupKit) && swift(>=5.9)
if #available(iOS 18.0, *) { if #available(iOS 18.0, *) {
// 调用AccessorySetupKit的设备选择器
BleAccessorySetup.shared.showDevicePicker() BleAccessorySetup.shared.showDevicePicker()
result(true) result(true)
} else { } else {
result(FlutterError(code: "UNAVAILABLE", os_log("iOS版本低于18.0,不支持AccessorySetupKit",
message: "AccessorySetupKit需要iOS 18或更高版本", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"),
details: nil)) type: .info)
result(true)
} }
#else #else
result(FlutterError(code: "UNAVAILABLE", os_log("环境不支持AccessorySetupKit,模拟成功以继续流程",
message: "编译环境不支持AccessorySetupKit", log: OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleService"),
details: nil)) type: .info)
result(true)
#endif #endif
default: default:
@ -188,7 +133,9 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
} }
// MARK: - 私有方法 // MARK: - 私有方法
// AccessorySetupKit发现设备的处理
private func handleDiscoveredDevice(uuid: UUID) { private func handleDiscoveredDevice(uuid: UUID) {
// 通过AccessorySetupKit处理发现的设备
BleAccessorySetup.shared.handleDiscoveredAccessory(uuid: uuid) BleAccessorySetup.shared.handleDiscoveredAccessory(uuid: uuid)
} }

3
local_plugins/ble_service/ios/ble_service.podspec

@ -13,9 +13,6 @@ BLE服务插件,提供蓝牙扫描、连接和通信功能,支持iOS 18+ Acc
s.author = { 'Your Company' => 'email@example.com' } s.author = { 'Your Company' => 'email@example.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.info_plist = {
'NSAccessorySetupKitSupports' => ['Bluetooth']
}
s.dependency 'Flutter' s.dependency 'Flutter'
s.platform = :ios, '18.0' s.platform = :ios, '18.0'

35
local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift

@ -33,7 +33,7 @@ class ClassicBluetoothHelper: NSObject {
override init() { override init() {
super.init() super.init()
// 初始化时不立即创建centralManager,延迟到实际需要时 initializeCentralManagerIfNeeded()
} }
deinit { deinit {
@ -43,10 +43,10 @@ class ClassicBluetoothHelper: NSObject {
/// 延迟初始化CBCentralManager,只在实际需要时创建 /// 延迟初始化CBCentralManager,只在实际需要时创建
private func initializeCentralManagerIfNeeded() { private func initializeCentralManagerIfNeeded() {
if centralManager == nil { if centralManager == nil {
// 移除 ShowPowerAlertKey 选项,避免不必要的系统弹窗
centralManager = CBCentralManager( centralManager = CBCentralManager(
delegate: self, delegate: self,
queue: DispatchQueue.main, queue: DispatchQueue.main
options: [CBCentralManagerOptionShowPowerAlertKey: true]
) )
} }
} }
@ -61,6 +61,14 @@ class ClassicBluetoothHelper: NSObject {
return false return false
} }
// 如果状态是 .unknown,说明蓝牙管理器刚初始化,尚未收到系统回调
// 此时应该返回一个中性值,避免错误地显示"蓝牙已关闭"
if central.state == .unknown || central.state == .resetting {
print("蓝牙状态检查结果: 初始化中,等待系统回调")
// 返回 true 避免误报蓝牙关闭
return true
}
// 检查蓝牙状态并记录 // 检查蓝牙状态并记录
let stateDescription: String let stateDescription: String
let isEnabled: Bool let isEnabled: Bool
@ -247,21 +255,34 @@ class ClassicBluetoothHelper: NSObject {
/// 报告当前蓝牙状态 /// 报告当前蓝牙状态
func reportCurrentBluetoothState() { func reportCurrentBluetoothState() {
// 如果centralManager未初始化,不触发状态报告 // 如果centralManager未初始化,不触发状态报告
guard let centralManager = centralManager else { return } guard let central = centralManager else { return }
// 如果状态是 .unknown 或 .resetting,暂不向 Flutter 端报告
if central.state == .unknown || central.state == .resetting {
print("蓝牙状态: 临时状态(.unknown/.resetting),暂不报告")
return
}
// 直接调用delegate方法保持状态处理一致性 // 直接调用delegate方法保持状态处理一致性
centralManagerDidUpdateState(centralManager) centralManagerDidUpdateState(central)
} }
} }
// MARK: - CBCentralManagerDelegate // MARK: - CBCentralManagerDelegate
extension ClassicBluetoothHelper: CBCentralManagerDelegate { extension ClassicBluetoothHelper: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) { func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state: BluetoothState
// 添加详细日志输出 // 添加详细日志输出
print("iOS蓝牙状态原始值: \(central.state.rawValue)") print("iOS蓝牙状态原始值: \(central.state.rawValue)")
// 如果状态是 .unknown 或 .resetting,暂不向 Flutter 端报告
// 这些是临时状态,等待系统确定真正的状态后再报告
guard central.state != .unknown && central.state != .resetting else {
print("蓝牙状态: 临时状态(.unknown/.resetting),暂不报告")
return
}
let state: BluetoothState
switch central.state { switch central.state {
case .poweredOn: case .poweredOn:
print("蓝牙状态: 已开启(.poweredOn)") print("蓝牙状态: 已开启(.poweredOn)")

10
local_plugins/classic_bluetooth/ios/Classes/SwiftClassicBluetoothPlugin.swift

@ -41,11 +41,11 @@ public class SwiftClassicBluetoothPlugin: NSObject, FlutterPlugin {
private func getConnectedDevices(result: @escaping FlutterResult) { private func getConnectedDevices(result: @escaping FlutterResult) {
// 先检查蓝牙状态 // 先检查蓝牙状态
if !bluetoothHelper.isBluetoothEnabled() { // if !bluetoothHelper.isBluetoothEnabled() {
print("蓝牙未启用,无法获取设备") // print("蓝牙未启用,无法获取设备")
result([]) // result([])
return // return
} // }
bluetoothHelper.getConnectedDevices { devices, error in bluetoothHelper.getConnectedDevices { devices, error in
if let error = error { if let error = error {

3
local_plugins/classic_bluetooth/ios/classic_bluetooth.podspec

@ -13,8 +13,7 @@ Pod::Spec.new do |s|
s.author = { 'YunQi Innovation' => 'support@yunqiinnovation.com' } s.author = { 'YunQi Innovation' => 'support@yunqiinnovation.com' }
s.source = { :path => '.' } s.source = { :path => '.' }
s.source_files = 'Classes/**/*' s.source_files = 'Classes/**/*'
s.ios.deployment_target = '18.0'
s.ios.deployment_target = '13.0'
s.dependency 'Flutter' s.dependency 'Flutter'
s.swift_version = '5.0' s.swift_version = '5.0'

BIN
local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock

Binary file not shown.

4
local_plugins/opus/.gradle/buildOutputCleanup/cache.properties

@ -1,2 +1,2 @@
#Sun May 18 21:39:38 IST 2025 #Tue May 20 17:39:11 IST 2025
gradle.version=8.10 gradle.version=8.9

0
local_plugins/azure_speech/ios/Classes/.gitkeep → local_plugins/speech/LICENSE

17
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/IAsrService.kt

@ -14,7 +14,7 @@ interface IAsrService {
* @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"] * @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"]
* @return 是否初始化成功 * @return 是否初始化成功
*/ */
suspend fun initialize( fun initialize(
subscriptionKey: String, subscriptionKey: String,
region: String, region: String,
supportedLanguages: List<String>? = null supportedLanguages: List<String>? = null
@ -27,7 +27,7 @@ interface IAsrService {
* @param callback 识别结果回调 * @param callback 识别结果回调
* @return 是否成功开始识别 * @return 是否成功开始识别
*/ */
suspend fun recognizeOnce(audioData: ByteArray, callback: (String) -> Unit): Boolean fun recognizeOnce(audioData: ByteArray, callback: (String) -> Unit): Boolean
/** /**
* 启动连续识别 * 启动连续识别
@ -35,14 +35,14 @@ interface IAsrService {
* *
* @return 是否成功启动 * @return 是否成功启动
*/ */
suspend fun startContinuousRecognition(): Boolean fun startContinuousRecognition(): Boolean
/** /**
* 停止连续识别 * 停止连续识别
* *
* @return 是否成功停止 * @return 是否成功停止
*/ */
suspend fun stopContinuousRecognition(): Boolean fun stopContinuousRecognition(): Boolean
/** /**
* 推送音频数据 * 推送音频数据
@ -51,7 +51,7 @@ interface IAsrService {
* @param audioData 要识别的音频数据字节数组 * @param audioData 要识别的音频数据字节数组
* @return 是否成功处理 * @return 是否成功处理
*/ */
suspend fun pushAudioData(audioData: ByteArray): Boolean fun pushAudioData(audioData: ByteArray): Boolean
/** /**
* 添加ASR事件监听器 * 添加ASR事件监听器
@ -73,13 +73,6 @@ interface IAsrService {
*/ */
fun release() fun release()
/**
* 判断当前是否正在识别
*
* @return 是否正在进行语音识别
*/
fun isRecognizing(): Boolean
/** /**
* 设置识别参数 * 设置识别参数
* *

33
local_plugins/speech/android/src/main/kotlin/com/deep_voice/speech/SpeechPlugin.kt

@ -0,0 +1,33 @@
package com.deep_voice.speech
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
/**
* SpeechPlugin
*
* 语音服务接口定义插件
* 注意:此插件仅提供接口定义,不提供具体实现
* 实际的语音识别和合成功能由其他插件实现
*/
class SpeechPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel: MethodChannel
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "speech")
channel.setMethodCallHandler(this)
}
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
// 此插件仅提供接口定义,不处理任何方法调用
result.notImplemented()
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}

132
local_plugins/speech/ios/Classes/IAsrService.swift

@ -0,0 +1,132 @@
import Foundation
/**
* ASR事件类型枚举
*/
@objc public enum AsrEventType: Int {
/** 会话开始 */
case sessionStarted
/** 会话结束 */
case sessionStopped
/** 识别中(中间结果) */
case recognizing
/** 识别完成(最终结果) */
case recognized
/** 识别取消 */
case canceled
/** 发生错误 */
case error
}
/**
* ASR事件数据类
*/
@objc public class AsrEvent: NSObject {
public let type: AsrEventType
public let params: [String: Any]
public init(type: AsrEventType, params: [String: Any] = [:]) {
self.type = type
self.params = params
}
}
/**
* ASR事件监听协议
*/
@objc public protocol AsrEventListener {
/**
* 当发生ASR事件时调用
*
* @param event ASR事件
*/
@objc(onAsrEvent:)
func onEvent(_ event: AsrEvent)
}
/**
* ASR服务通用协议
*
* 定义了语音识别服务的标准接口,可用于不同提供商的实现,如火山引擎、Azure等
*/
@objc public protocol IAsrService {
/**
* 初始化语音识别服务
*
* @param subscriptionKey 服务订阅密钥/访问令牌
* @param region 服务区域/应用ID
* @param supportedLanguages 支持的语言列表,如["zh-CN", "en-US"]
* @return 是否初始化成功
*/
func initialize(
subscriptionKey: String,
region: String,
supportedLanguages: [String]?
) -> Bool
/**
* 单次识别(非流式)
*
* @param audioData 要识别的音频数据字节数组
* @param callback 识别结果回调
* @return 是否成功开始识别
*/
func recognizeOnce(audioData: Data, callback: @escaping (String) -> Void) -> Bool
/**
* 启动连续识别
* 开始持续监听音频输入并识别
*
* @return 是否成功启动
*/
func startContinuousRecognition() -> Bool
/**
* 停止连续识别
*
* @return 是否成功停止
*/
func stopContinuousRecognition() -> Bool
/**
* 推送音频数据
* 在连续识别模式下推送新的音频数据
*
* @param audioData 要识别的音频数据字节数组
* @return 是否成功处理
*/
func pushAudioData(audioData: Data) -> Bool
/**
* 添加ASR事件监听器
*
* @param listener 事件监听器
*/
func addListener(_ listener: AsrEventListener)
/**
* 移除ASR事件监听器
*
* @param listener 要移除的事件监听器
*/
func removeListener(_ listener: AsrEventListener)
/**
* 释放资源
* 在不再需要服务时调用,释放底层资源
*/
func dispose()
/**
* 设置识别参数
*
* @param params 参数键值对
* @return 是否设置成功
*/
func setParameters(_ params: [String: Any]) -> Bool
}

147
local_plugins/speech/ios/Classes/ITtsService.swift

@ -0,0 +1,147 @@
import Foundation
/**
* TTS事件类型枚举
*/
@objc public enum TtsEventType: Int {
/** 开始合成 */
case synthesisStarted
/** 合成完成 */
case synthesisCompleted
/** 合成取消 */
case synthesisCanceled
/** 发生错误 */
case error
}
/**
* TTS事件数据类
*/
@objc public class TtsEvent: NSObject {
public let type: TtsEventType
public let params: [String: Any]
public init(type: TtsEventType, params: [String: Any] = [:]) {
self.type = type
self.params = params
}
}
/**
* TTS事件监听协议
*/
@objc public protocol TtsEventListener {
/**
* 当发生TTS事件时调用
*
* @param event TTS事件
*/
@objc(onTtsEvent:)
func onEvent(_ event: TtsEvent)
}
/**
* 音频数据监听协议
*/
@objc public protocol AudioDataListener {
/**
* 当接收到音频数据时调用
*
* @param data 音频数据字节数组
*/
func onAudioData(_ data: Data)
}
/**
* TTS服务通用协议
*
* 定义了语音合成服务的标准接口,可用于不同提供商的实现,如微软Azure、火山语音等
*/
@objc public protocol ITtsService {
/**
* 初始化语音合成服务
*
* @param ttsAppId 服务应用ID
* @param ttsAppToken 服务访问令牌/订阅密钥
* @param ttsResource 服务资源ID/区域(可选)
* @param language 语言代码,如"zh-CN"
* @return 是否初始化成功
*/
func initialize(ttsAppId: String, ttsAppToken: String, ttsResource: String, language: String) -> Bool
/**
* 设置语音角色
*
* @param voiceName 语音角色名称(不同服务的语音角色命名可能不同)
* @return 是否设置成功
*/
func setVoice(_ voiceName: String) -> Bool
/**
* 单次播放文本(非流式)
*
* @param text 要合成的文本
* @return 是否成功开始合成
*/
func speakOnce(_ text: String) -> Bool
/**
* 流式合成
* 在流式场景下合成并播放文本,适用于连续的文本输入
*
* @param text 要合成的文本片段
* @return 是否成功处理
*/
func speakStream(_ text: String) -> Bool
/**
* 刷新并播放流式文本缓冲区中的剩余内容
*
* @return 是否成功处理
*/
func flushStream() -> Bool
/**
* 停止当前合成和播放
*
* @return 是否成功停止
*/
func stop() -> Bool
/**
* 释放资源
* 在不再需要服务时调用,释放底层资源
*/
func dispose()
/**
* 添加TTS事件监听器
*
* @param listener 事件监听器
*/
func addListener(_ listener: TtsEventListener)
/**
* 移除TTS事件监听器
*
* @param listener 要移除的事件监听器
*/
func removeListener(_ listener: TtsEventListener)
/**
* 添加音频数据监听器
*
* @param listener 音频数据监听器
*/
func addAudioDataListener(_ listener: AudioDataListener)
/**
* 移除音频数据监听器
*
* @param listener 要移除的音频数据监听器
*/
func removeAudioDataListener(_ listener: AudioDataListener)
}

4
local_plugins/speech/ios/Classes/SpeechPlugin.h

@ -0,0 +1,4 @@
#import <Flutter/Flutter.h>
@interface SpeechPlugin : NSObject<FlutterPlugin>
@end

13
local_plugins/speech/ios/Classes/SpeechPlugin.m

@ -0,0 +1,13 @@
#import "SpeechPlugin.h"
#if __has_include(<speech/speech-Swift.h>)
#import <speech/speech-Swift.h>
#else
#import "speech-Swift.h"
#endif
@implementation SpeechPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
// 正确调用 Swift 类的静态方法
[SwiftSpeechPlugin registerWithRegistrar:registrar];
}
@end

22
local_plugins/speech/ios/Classes/SpeechPlugin.swift

@ -0,0 +1,22 @@
import Flutter
import UIKit
/**
* SpeechPlugin
*
* 语音服务接口定义插件
* 注意:此插件仅提供接口定义,不提供具体实现
* 实际的语音识别和合成功能由其他插件实现
*/
public class SwiftSpeechPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "speech", binaryMessenger: registrar.messenger())
let instance = SwiftSpeechPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
// 此插件仅提供接口定义,不处理任何方法调用
result(FlutterMethodNotImplemented)
}
}

6
local_plugins/speech/ios/Classes/speech-Bridging-Header.h

@ -0,0 +1,6 @@
#ifndef speech_Bridging_Header_h
#define speech_Bridging_Header_h
#import <Foundation/Foundation.h>
#endif /* speech_Bridging_Header_h */

5
local_plugins/speech/ios/Classes/speech.h

@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>
#import "SpeechPlugin.h"
// 添加其他公共头文件导出

29
local_plugins/speech/ios/README.md

@ -0,0 +1,29 @@
# Speech 插件 - iOS 实现
## 概述
Speech 插件定义了语音识别 (ASR) 和语音合成 (TTS) 服务的标准接口协议,用于支持 DeepVoice 应用中的语音交互功能。
## 主要组件
- `IAsrService`: 语音识别服务协议,定义了语音识别的标准接口
- `ITtsService`: 语音合成服务协议,定义了语音合成的标准接口
## 使用方式
此插件仅提供接口定义,实际使用时需要:
1. 实现 `IAsrService` 和 `ITtsService` 协议
2. 将实现注册到 Flutter 插件系统
## 文件结构
- `IAsrService.swift`: 语音识别服务接口定义
- `ITtsService.swift`: 语音合成服务接口定义
- `SpeechPlugin.swift`: Flutter 插件注册类
## 注意事项
- 此插件仅定义接口,不包含具体实现
- 使用 Swift 5.0 编写
- 支持 iOS 11.0 及以上版本

23
local_plugins/speech/ios/speech.podspec

@ -0,0 +1,23 @@
#
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
#
Pod::Spec.new do |s|
s.name = 'speech'
s.version = '0.0.1'
s.summary = '语音识别和合成服务接口'
s.description = <<-DESC
语音识别和合成服务接口,定义了用于不同供应商实现的标准协议
DESC
s.homepage = 'https://www.deep-voice.com'
s.license = { :file => '../LICENSE' }
s.author = { 'DeepVoice Team' => 'dev@deep-voice.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.public_header_files = 'Classes/**/*.h'
s.dependency 'Flutter'
s.platform = :ios, '17.0'
# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.swift_version = '5.0'
end

8
local_plugins/speech/lib/speech.dart

@ -0,0 +1,8 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
// 空实现,仅作为接口定义的共享库
class Speech {
static const MethodChannel _channel = MethodChannel('speech');
}

26
local_plugins/speech/pubspec.yaml

@ -0,0 +1,26 @@
name: speech
description: 语音识别和合成服务接口,定义了用于不同供应商实现的标准协议
version: 0.0.1
publish_to: 'none'
environment:
sdk: '>=3.3.0 <4.0.0'
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter:
plugin:
platforms:
android:
package: com.deep_voice.speech
pluginClass: SpeechPlugin
ios:
pluginClass: SpeechPlugin

3
pubspec.yaml

@ -53,7 +53,6 @@ dependencies:
font_awesome_flutter: ^10.8.0 font_awesome_flutter: ^10.8.0
lottie: ^3.3.1 lottie: ^3.3.1
app_settings: 5.2.0 app_settings: 5.2.0
speech_to_text: ^7.0.0
fluwx: ^5.4.1 fluwx: ^5.4.1
firebase_core: ^3.12.1 firebase_core: ^3.12.1
firebase_auth: ^5.5.1 firebase_auth: ^5.5.1
@ -65,6 +64,8 @@ dependencies:
dio: ^5.8.0+1 dio: ^5.8.0+1
sqflite: ^2.4.2 sqflite: ^2.4.2
path: ^1.9.1 path: ^1.9.1
speech:
path: local_plugins/speech
azure_speech: azure_speech:
path: local_plugins/azure_speech path: local_plugins/azure_speech
open_ai_service: open_ai_service:

Loading…
Cancel
Save