富瑞坤鼠标调通鉴权
authorliangzhenying <867174124@qq.com>
Wed, 13 Nov 2024 09:31:19 +0000 (17:31 +0800)
committerliangzhenying <867174124@qq.com>
Wed, 13 Nov 2024 09:31:19 +0000 (17:31 +0800)
27 files changed:
MixSdk/Example/Example.xcodeproj/project.pbxproj
MixSdk/Example/Example/AppDelegate.swift
MixSdk/Example/Example/BleChatVC.swift [new file with mode: 0644]
MixSdk/Example/Example/BluetoothManager.swift [new file with mode: 0644]
MixSdk/Example/Example/Config.swift
MixSdk/Example/Example/PhoneChatVC.swift [moved from MixSdk/Example/Example/VoiceChatViewController.swift with 73% similarity]
MixSdk/Example/Example/Record/Example-Bridging-Header.h
MixSdk/Example/Example/Resource/1.pcm [deleted file]
MixSdk/Example/Example/Resource/2.pcm [deleted file]
MixSdk/Example/Example/Resource/AIAudio.sbc [deleted file]
MixSdk/Example/Example/Resource/AIAudio_2.sbc [deleted file]
MixSdk/Example/Example/Resource/cat.png [deleted file]
MixSdk/Example/Example/Resource/tts.mp3 [deleted file]
MixSdk/Example/Example/Tools/Date.swift
MixSdk/Example/Example/Tools/PcmPlayer.swift
MixSdk/Example/Example/Tools/PlayerManager.swift
MixSdk/Example/Example/ViewController.swift
MixSdk/MixSdk/MixSdk.xcodeproj/project.pbxproj
MixSdk/MixSdk/MixSdk/MSAuthModel.swift
MixSdk/MixSdk/MixSdk/MSBle.swift [new file with mode: 0644]
MixSdk/MixSdk/MixSdk/MSBleInfo.swift [new file with mode: 0644]
MixSdk/MixSdk/MixSdk/MSBleSender.swift [new file with mode: 0644]
MixSdk/MixSdk/MixSdk/MSVoiceChat.swift
MixSdk/MixSdk/MixSdk/MixSdk.swift
MixSdk/Podfile.lock
MixSdk/Pods/Manifest.lock
MixSdk/podfile

index a3a295d..b31a3b9 100644 (file)
@@ -9,33 +9,29 @@
 /* Begin PBXBuildFile section */
                180255BA2C3BE6BD005AFC5F /* SendRecordTooBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 180255B92C3BE6BD005AFC5F /* SendRecordTooBar.swift */; };
                18068D022CDCC57C006B6EF5 /* PcmPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18068D012CDCC57C006B6EF5 /* PcmPlayer.swift */; };
-               1811885B2C1BE80B00C23FAB /* 1.pcm in Resources */ = {isa = PBXBuildFile; fileRef = 181188592C1BE80B00C23FAB /* 1.pcm */; };
                18132AF82C1C4D15002F6B52 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18132AF62C1C4D15002F6B52 /* libz.tbd */; };
-               181B3B372C5B1C54000AED7D /* AIAudio_2.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 181B3B352C5B1C54000AED7D /* AIAudio_2.sbc */; };
                18204BD42C193F9A00F94E69 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204BD32C193F9A00F94E69 /* AppDelegate.swift */; };
                18204BD62C193F9A00F94E69 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204BD52C193F9A00F94E69 /* SceneDelegate.swift */; };
                18204BD82C193F9A00F94E69 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18204BD72C193F9A00F94E69 /* ViewController.swift */; };
                18204BDB2C193F9A00F94E69 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18204BD92C193F9A00F94E69 /* Main.storyboard */; };
                18204BDD2C193F9D00F94E69 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18204BDC2C193F9D00F94E69 /* Assets.xcassets */; };
                18204BE02C193F9D00F94E69 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 18204BDE2C193F9D00F94E69 /* LaunchScreen.storyboard */; };
-               18204BEE2C19428100F94E69 /* MixSdk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18204BEC2C19417000F94E69 /* MixSdk.framework */; };
                1843A7962C4E060D00FC010F /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1843A7952C4E060D00FC010F /* LogViewController.swift */; };
                184FFC492C218FC4006F664D /* PlayerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184FFC482C218FC4006F664D /* PlayerManager.swift */; };
                184FFC4D2C2199E5006F664D /* SendToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184FFC4C2C2199E5006F664D /* SendToolBar.swift */; };
-               186BFD4B2C57952600673D8C /* VoiceChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186BFD4A2C57952600673D8C /* VoiceChatViewController.swift */; };
                188695A62C5A3CAD00B9B89F /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 188695A52C5A3CAD00B9B89F /* Date.swift */; };
-               18A2DB6C2C40C5CC00A8731B /* AIAudio.sbc in Resources */ = {isa = PBXBuildFile; fileRef = 18A2DB6B2C40C5CC00A8731B /* AIAudio.sbc */; };
-               18ABF57A2C3F884600C60E9B /* 2.pcm in Resources */ = {isa = PBXBuildFile; fileRef = 18ABF5792C3F884600C60E9B /* 2.pcm */; };
+               18B1A36E2CE1D44A00DD4A7C /* MixSdk.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18204BEC2C19417000F94E69 /* MixSdk.framework */; };
+               18B1A3702CE1E12800DD4A7C /* BluetoothManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B1A36F2CE1E12800DD4A7C /* BluetoothManager.swift */; };
+               18B1A3732CE1EF4900DD4A7C /* BleChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B1A3722CE1EF4900DD4A7C /* BleChatVC.swift */; };
+               18B1A3752CE1EF6200DD4A7C /* PhoneChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B1A3742CE1EF6200DD4A7C /* PhoneChatVC.swift */; };
                18C42C802C242AAC000D8860 /* liblzma.5.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18C42C7E2C242AAC000D8860 /* liblzma.5.tbd */; };
                18C42C822C242AD3000D8860 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 18C42C812C242AD3000D8860 /* CoreAudio.framework */; };
                18C42C842C242B01000D8860 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18C42C832C242B01000D8860 /* libiconv.tbd */; };
                18C42C862C242B0B000D8860 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 18C42C852C242B0A000D8860 /* libbz2.tbd */; };
-               18C856202C3B81B900EAE874 /* tts.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 18C8561E2C3B81B800EAE874 /* tts.mp3 */; };
                18DB20482C213D730070D571 /* AiMouseAudioRecord.m in Sources */ = {isa = PBXBuildFile; fileRef = 18DB20462C213D730070D571 /* AiMouseAudioRecord.m */; };
                18DB20492C213D730070D571 /* AiMouseRecordManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 18DB20472C213D730070D571 /* AiMouseRecordManager.m */; };
                18EBBA152CDE1791006F3B80 /* VoiceChatCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EBBA142CDE1791006F3B80 /* VoiceChatCell.swift */; };
                18EC39D12C4F421C00964461 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18EC39D02C4F421C00964461 /* Config.swift */; };
-               18FF930C2C39263A0006452C /* cat.png in Resources */ = {isa = PBXBuildFile; fileRef = 18FF930A2C39263A0006452C /* cat.png */; };
                F59940004E1C16053FBD42F5 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B555BC40ACD8F31DBCDD1A2 /* Pods_Example.framework */; };
 /* End PBXBuildFile section */
 
@@ -52,9 +48,7 @@
 /* Begin PBXFileReference section */
                180255B92C3BE6BD005AFC5F /* SendRecordTooBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendRecordTooBar.swift; sourceTree = "<group>"; };
                18068D012CDCC57C006B6EF5 /* PcmPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PcmPlayer.swift; sourceTree = "<group>"; };
-               181188592C1BE80B00C23FAB /* 1.pcm */ = {isa = PBXFileReference; lastKnownFileType = file; path = 1.pcm; sourceTree = "<group>"; };
                18132AF62C1C4D15002F6B52 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
-               181B3B352C5B1C54000AED7D /* AIAudio_2.sbc */ = {isa = PBXFileReference; lastKnownFileType = file; path = AIAudio_2.sbc; sourceTree = "<group>"; };
                18204BD02C193F9A00F94E69 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
                18204BD32C193F9A00F94E69 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
                18204BD52C193F9A00F94E69 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
                1843A7952C4E060D00FC010F /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
                184FFC482C218FC4006F664D /* PlayerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerManager.swift; sourceTree = "<group>"; };
                184FFC4C2C2199E5006F664D /* SendToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendToolBar.swift; sourceTree = "<group>"; };
-               186BFD4A2C57952600673D8C /* VoiceChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceChatViewController.swift; sourceTree = "<group>"; };
                188695A52C5A3CAD00B9B89F /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
-               18A2DB6B2C40C5CC00A8731B /* AIAudio.sbc */ = {isa = PBXFileReference; lastKnownFileType = file; path = AIAudio.sbc; sourceTree = "<group>"; };
-               18ABF5792C3F884600C60E9B /* 2.pcm */ = {isa = PBXFileReference; lastKnownFileType = file; path = 2.pcm; sourceTree = "<group>"; };
+               18B1A36F2CE1E12800DD4A7C /* BluetoothManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BluetoothManager.swift; sourceTree = "<group>"; };
+               18B1A3722CE1EF4900DD4A7C /* BleChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BleChatVC.swift; sourceTree = "<group>"; };
+               18B1A3742CE1EF6200DD4A7C /* PhoneChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneChatVC.swift; sourceTree = "<group>"; };
                18C42C7E2C242AAC000D8860 /* liblzma.5.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.5.tbd; path = usr/lib/liblzma.5.tbd; sourceTree = SDKROOT; };
                18C42C812C242AD3000D8860 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
                18C42C832C242B01000D8860 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
                18C42C852C242B0A000D8860 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; };
-               18C8561E2C3B81B800EAE874 /* tts.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = tts.mp3; sourceTree = "<group>"; };
                18DB20432C213D720070D571 /* Example-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Example-Bridging-Header.h"; sourceTree = "<group>"; };
                18DB20442C213D730070D571 /* AiMouseRecordManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiMouseRecordManager.h; sourceTree = "<group>"; };
                18DB20452C213D730070D571 /* AiMouseAudioRecord.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AiMouseAudioRecord.h; sourceTree = "<group>"; };
@@ -83,7 +76,6 @@
                18DB20472C213D730070D571 /* AiMouseRecordManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AiMouseRecordManager.m; sourceTree = "<group>"; };
                18EBBA142CDE1791006F3B80 /* VoiceChatCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceChatCell.swift; sourceTree = "<group>"; };
                18EC39D02C4F421C00964461 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = "<group>"; };
-               18FF930A2C39263A0006452C /* cat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = cat.png; sourceTree = "<group>"; };
                4B555BC40ACD8F31DBCDD1A2 /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
                759E170040ACED4895C8A49D /* Pods-Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.release.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.release.xcconfig"; sourceTree = "<group>"; };
                C85BC820E1AA39AC970C9E79 /* Pods-Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Example.debug.xcconfig"; path = "Target Support Files/Pods-Example/Pods-Example.debug.xcconfig"; sourceTree = "<group>"; };
                        isa = PBXFrameworksBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
+                               18B1A36E2CE1D44A00DD4A7C /* MixSdk.framework in Frameworks */,
                                18C42C862C242B0B000D8860 /* libbz2.tbd in Frameworks */,
                                18C42C842C242B01000D8860 /* libiconv.tbd in Frameworks */,
                                18C42C822C242AD3000D8860 /* CoreAudio.framework in Frameworks */,
                                18C42C802C242AAC000D8860 /* liblzma.5.tbd in Frameworks */,
                                18132AF82C1C4D15002F6B52 /* libz.tbd in Frameworks */,
-                               18204BEE2C19428100F94E69 /* MixSdk.framework in Frameworks */,
                                F59940004E1C16053FBD42F5 /* Pods_Example.framework in Frameworks */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                181188582C1BE80B00C23FAB /* Resource */ = {
                        isa = PBXGroup;
                        children = (
-                               18FF930A2C39263A0006452C /* cat.png */,
-                               18A2DB6B2C40C5CC00A8731B /* AIAudio.sbc */,
-                               181B3B352C5B1C54000AED7D /* AIAudio_2.sbc */,
-                               181188592C1BE80B00C23FAB /* 1.pcm */,
-                               18ABF5792C3F884600C60E9B /* 2.pcm */,
-                               18C8561E2C3B81B800EAE874 /* tts.mp3 */,
                        );
                        path = Resource;
                        sourceTree = "<group>";
                18204BD22C193F9A00F94E69 /* Example */ = {
                        isa = PBXGroup;
                        children = (
+                               181188582C1BE80B00C23FAB /* Resource */,
                                186E75102C3E33EB0040671B /* Tools */,
                                18DB20412C213D600070D571 /* Record */,
-                               181188582C1BE80B00C23FAB /* Resource */,
                                18204BD32C193F9A00F94E69 /* AppDelegate.swift */,
                                18204BD52C193F9A00F94E69 /* SceneDelegate.swift */,
-                               18204BD72C193F9A00F94E69 /* ViewController.swift */,
-                               186BFD4A2C57952600673D8C /* VoiceChatViewController.swift */,
                                1843A7952C4E060D00FC010F /* LogViewController.swift */,
+                               18204BD72C193F9A00F94E69 /* ViewController.swift */,
+                               18B1A36F2CE1E12800DD4A7C /* BluetoothManager.swift */,
+                               18B1A3742CE1EF6200DD4A7C /* PhoneChatVC.swift */,
+                               18B1A3722CE1EF4900DD4A7C /* BleChatVC.swift */,
                                18EBBA142CDE1791006F3B80 /* VoiceChatCell.swift */,
                                18EC39D02C4F421C00964461 /* Config.swift */,
                                18204BD92C193F9A00F94E69 /* Main.storyboard */,
                        isa = PBXResourcesBuildPhase;
                        buildActionMask = 2147483647;
                        files = (
-                               181B3B372C5B1C54000AED7D /* AIAudio_2.sbc in Resources */,
-                               18FF930C2C39263A0006452C /* cat.png in Resources */,
-                               1811885B2C1BE80B00C23FAB /* 1.pcm in Resources */,
                                18204BE02C193F9D00F94E69 /* LaunchScreen.storyboard in Resources */,
-                               18C856202C3B81B900EAE874 /* tts.mp3 in Resources */,
                                18204BDD2C193F9D00F94E69 /* Assets.xcassets in Resources */,
-                               18A2DB6C2C40C5CC00A8731B /* AIAudio.sbc in Resources */,
-                               18ABF57A2C3F884600C60E9B /* 2.pcm in Resources */,
                                18204BDB2C193F9A00F94E69 /* Main.storyboard in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                        files = (
                                18DB20482C213D730070D571 /* AiMouseAudioRecord.m in Sources */,
                                18204BD82C193F9A00F94E69 /* ViewController.swift in Sources */,
-                               186BFD4B2C57952600673D8C /* VoiceChatViewController.swift in Sources */,
                                1843A7962C4E060D00FC010F /* LogViewController.swift in Sources */,
                                18EBBA152CDE1791006F3B80 /* VoiceChatCell.swift in Sources */,
                                18068D022CDCC57C006B6EF5 /* PcmPlayer.swift in Sources */,
+                               18B1A3732CE1EF4900DD4A7C /* BleChatVC.swift in Sources */,
                                18204BD42C193F9A00F94E69 /* AppDelegate.swift in Sources */,
+                               18B1A3752CE1EF6200DD4A7C /* PhoneChatVC.swift in Sources */,
+                               18B1A3702CE1E12800DD4A7C /* BluetoothManager.swift in Sources */,
                                184FFC492C218FC4006F664D /* PlayerManager.swift in Sources */,
                                18DB20492C213D730070D571 /* AiMouseRecordManager.m in Sources */,
                                18204BD62C193F9A00F94E69 /* SceneDelegate.swift in Sources */,
                                );
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = Example/Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = MixSdk;
+                               INFOPLIST_KEY_CFBundleDisplayName = VoiceChat;
+                               INFOPLIST_KEY_LSApplicationCategoryType = "";
+                               INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "需要开启蓝牙功能";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "要使用麦克风录音哦";
                                INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
                                INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 1.0;
-                               PRODUCT_BUNDLE_IDENTIFIER = ZhiHui.Example;
+                               PRODUCT_BUNDLE_IDENTIFIER = voicechat.Example.ZhiHui;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SWIFT_EMIT_LOC_STRINGS = YES;
                                SWIFT_OBJC_BRIDGING_HEADER = "Example/Record/Example-Bridging-Header.h";
                                );
                                GENERATE_INFOPLIST_FILE = YES;
                                INFOPLIST_FILE = Example/Info.plist;
-                               INFOPLIST_KEY_CFBundleDisplayName = MixSdk;
+                               INFOPLIST_KEY_CFBundleDisplayName = VoiceChat;
+                               INFOPLIST_KEY_LSApplicationCategoryType = "";
+                               INFOPLIST_KEY_NSBluetoothAlwaysUsageDescription = "需要开启蓝牙功能";
                                INFOPLIST_KEY_NSMicrophoneUsageDescription = "要使用麦克风录音哦";
                                INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
                                INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
                                        "@executable_path/Frameworks",
                                );
                                MARKETING_VERSION = 1.0;
-                               PRODUCT_BUNDLE_IDENTIFIER = ZhiHui.Example;
+                               PRODUCT_BUNDLE_IDENTIFIER = voicechat.Example.ZhiHui;
                                PRODUCT_NAME = "$(TARGET_NAME)";
                                SWIFT_EMIT_LOC_STRINGS = YES;
                                SWIFT_OBJC_BRIDGING_HEADER = "Example/Record/Example-Bridging-Header.h";
index 91ddc4f..b0cf395 100644 (file)
@@ -6,7 +6,6 @@
 //
 
 import UIKit
-import MixSdk
 import IQKeyboardManagerSwift
 @main
 class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -15,9 +14,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         // Override point for customization after application launch.
-        MixSdk.shared.initSDK()
-        MixSdk.shared.isDebug = true
-        
         
         IQKeyboardManager.shared.enable = true
         IQKeyboardManager.shared.resignOnTouchOutside = true
diff --git a/MixSdk/Example/Example/BleChatVC.swift b/MixSdk/Example/Example/BleChatVC.swift
new file mode 100644 (file)
index 0000000..8b1fe4f
--- /dev/null
@@ -0,0 +1,150 @@
+//
+//  VoiceChatViewController.swift
+//  Example
+//
+//  Created by incar on 2024/7/29.
+//
+
+import UIKit
+import MixSdk
+import SnapKit
+import Toast_Swift
+
+
+
+class BleChatVC: UIViewController{
+    var voiceData:NSMutableData = NSMutableData()
+    
+    let tableView = UITableView(frame:.zero, style: .plain)
+    let stateLabel = UIButton(type: .custom)
+    var dataArray = [CellModel]()
+    
+    deinit {
+        print("销毁")
+        PlayerManager.shared.stop()
+        MixSdk.shared.voiceChat.finish()
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Do any additional setup after loading the view.
+        
+        setupViews()
+        setupVoiceChat()
+    }
+    
+    func setupViews(){
+        self.title = "ble chat"
+        self.view.backgroundColor = .white
+        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "结束说话", style: .done, target: self, action: #selector(endSpeakAction))
+        
+        tableView.delegate = self
+        tableView.dataSource = self
+        tableView.estimatedRowHeight = 60
+        tableView.register(VoiceChatCell.self, forCellReuseIdentifier: "VoiceChatCell")
+        view.addSubview(tableView)
+        tableView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview().inset(80)
+        }
+        
+        stateLabel.backgroundColor = .blue
+        stateLabel.setTitle("", for: .normal)
+        stateLabel.addTarget(self, action: #selector(requestToSpeak), for: .touchUpInside)
+        view.addSubview(stateLabel)
+        stateLabel.snp.makeConstraints { make in
+            make.height.equalTo(80)
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview()
+        }
+    }
+
+    func setupVoiceChat(){
+        MixSdk.shared.ble.authCallBack = {[unowned self] in
+            self.view.makeToast("鉴权成功!" )
+        }
+        MixSdk.shared.voiceChat.start()
+        MixSdk.shared.voiceChat.eventCallBack = { [unowned self] model in
+
+            if model.event == "RespondingEnded"{
+                self.saveToFile()
+            }
+            if model.payload?.state == "Listening"{
+                
+                self.stateLabel.setTitle("正在聆听...", for: .normal)
+            }
+            if model.payload?.state == "Thinking"{
+                self.stateLabel.setTitle("正在思考...", for: .normal)
+            }
+            if model.payload?.state == "Responding"{
+                self.stateLabel.setTitle("正在回复...", for: .normal)
+                self.voiceData = NSMutableData()
+            }
+            
+            if  model.event == "SpeechContent",let payload = model.payload,let text = payload.text,let isFinished = payload.finished,isFinished == true{
+                let cellModel = CellModel()
+                cellModel.text = text
+                cellModel.isSend = true
+                dataArray.append(cellModel)
+                tableView.reloadData()
+                tableView.scrollToRow(at: IndexPath(row: dataArray.count - 1, section: 0), at: .none, animated: true)
+            }
+            if  model.event == "RespondingContent",let payload = model.payload,let text = payload.text,let isFinished = payload.finished,isFinished == true{
+                let cellModel = CellModel()
+                cellModel.text = text
+                cellModel.isSend = false
+                dataArray.append(cellModel)
+                tableView.reloadData()
+                tableView.scrollToRow(at: IndexPath(row: dataArray.count - 1, section: 0), at: .none, animated: true)
+            }
+            MixSdk.shared.voiceChat.replyCallBack = {[unowned self] data in
+                self.voiceData.append(data)
+            }
+        }
+    }
+    
+    @objc func requestToSpeak(){
+        PlayerManager.shared.stop()
+        MixSdk.shared.voiceChat.requestToSpeak()
+    }
+    func saveToFile(){
+        let mp3Path = NSHomeDirectory() + "/Documents/1.mp3"
+        
+        let url = URL(fileURLWithPath: mp3Path)
+        do {
+            try self.voiceData.write(to: url, options: .atomic)
+            print(mp3Path)
+            self.stateLabel.setTitle("正在播放...", for: .normal)
+            MixSdk.shared.voiceChat.startPlay()
+            PlayerManager.shared.play(filePath: mp3Path) {[unowned self] success in
+                self.stateLabel.setTitle("结束播放...", for: .normal)
+                MixSdk.shared.voiceChat.endPlay()
+            }
+        }
+        catch{
+            print(error)
+        }
+    }
+    @objc func endSpeakAction(){
+        MixSdk.shared.voiceChat.endSpeedch()
+    }
+}
+extension BleChatVC:UITableViewDelegate,UITableViewDataSource {
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return dataArray.count
+    }
+    
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        var cell = tableView.dequeueReusableCell(withIdentifier: "VoiceChatCell", for: indexPath) as? VoiceChatCell
+        
+        if cell == nil{
+            cell = VoiceChatCell(style: .default, reuseIdentifier: "VoiceChatCell")
+        }
+        cell!.model = dataArray[indexPath.row]
+        return cell!
+    }
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+    }
+}
diff --git a/MixSdk/Example/Example/BluetoothManager.swift b/MixSdk/Example/Example/BluetoothManager.swift
new file mode 100644 (file)
index 0000000..b01d12c
--- /dev/null
@@ -0,0 +1,181 @@
+//
+//  IBluetooth.swift
+//  IOTBluetooth
+//
+//  Created by yaozuopan on 2024/2/4.
+//
+
+import Foundation
+import CoreBluetooth
+import MixSdk
+
+/// 主服务
+let kserviceUUID: String = "FFF1"
+let kVoiceUUID: String = "0783B03E-8535-B5A0-7140-A304D2194CB8"
+let kDeviceInfoUUID: String = "0783B63E-8535-B5A0-7140-A304D2495CB6"
+
+public typealias IBDiscoverHandler = (_ peripheral: CBPeripheral) -> Void
+public typealias IBDidConectedHandler = (_ peripheral: CBPeripheral) -> Void
+public typealias IBDisConectedHandler = (_ peripheral: CBPeripheral) -> Void
+
+class BluetoothManager: NSObject {
+    static let shared = BluetoothManager()
+
+    /// 2AE6
+    private var writeFileCharacteristic: CBCharacteristic!
+    /// 中央管理类,管理蓝牙的扫描连接
+    var centralManager: CBCentralManager!
+    /// 当前连接设备
+    var currentPeripheral: CBPeripheral?
+    
+    /// 扫描到设备
+    var discoverCallBack:IBDiscoverHandler?
+    /// 连上设备
+    var didConnectedCallBack:IBDidConectedHandler?
+    /// 设备断开连接
+    var disConnectedCallBack:IBDisConectedHandler?
+    
+    private override init() {
+        super.init()
+        centralManager = CBCentralManager(delegate: self, queue: nil)
+        setupFileSdk()
+    }
+
+    func setupFileSdk(){
+        //初始化
+        MixSdk.shared.initSDK()
+        //isDebug为true则打印日志,false则不打印日志
+        MixSdk.shared.isDebug = true
+        //设置sdk数据回调
+        MixSdk.shared.ble.sdkToDeviceCallback = {[unowned self] data in
+            //这里将回调的数据发给蓝牙设备
+            self.send(data)
+        }
+    }
+}
+
+extension BluetoothManager{
+    /// 开始扫描
+    func startScan() {
+        print("开始扫描...")
+        centralManager.scanForPeripherals(withServices: nil)
+//        centralManager.scanForPeripherals(withServices: [CBUUID(string: kserviceUUID)], options: nil)
+        if let peripheral = centralManager.retrieveConnectedPeripherals(withServices: [CBUUID(string: kserviceUUID)]).first{
+            discoverCallBack?(peripheral)
+        }
+    }
+    
+    /// 停止扫描
+    func stopScan(){
+        print("停止扫描蓝牙设备...")
+        centralManager.stopScan()
+    }
+    
+    /// 连接蓝牙设备
+    func connect(_ peripheral: CBPeripheral) {
+        print("正在连接。。。", peripheral.name ?? "")
+        centralManager.connect(peripheral)
+    }
+    
+    /// 断开与蓝牙设备的连接
+    func cancel() {
+        guard let peripheral = currentPeripheral else { return  }
+        centralManager.cancelPeripheralConnection(peripheral)
+    }
+    
+    func send(_ data: Data) {
+        guard let peripheral = currentPeripheral else{
+            print("手机未连上蓝牙设备!")
+            return
+        }
+        peripheral.writeValue(data, for: writeFileCharacteristic, type: .withoutResponse)
+    }
+}
+
+extension BluetoothManager: CBCentralManagerDelegate {
+    func centralManagerDidUpdateState(_ central: CBCentralManager) {
+        switch central.state {
+        case .unknown:
+            print("unknown")
+        case .resetting:
+            print("resetting")
+        case .unsupported:
+            print("unsupported")
+        case .unauthorized:
+            print("unauthorized")
+        case .poweredOff:
+            print("poweredOff")
+        case .poweredOn:
+            startScan()
+            print("poweredOn")
+        @unknown default:
+            break
+        }
+    }
+
+    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
+//        IBManager.discoverHandler?(device)
+        discoverCallBack?(peripheral)
+    }
+    
+    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
+        currentPeripheral = peripheral
+        peripheral.delegate = self
+        peripheral.discoverServices([CBUUID(string: kserviceUUID)])
+        print("连接成功", peripheral.name ?? "")
+        didConnectedCallBack?(peripheral)
+    }
+    
+    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
+        print("连接失败",error?.localizedDescription ?? "" )
+        currentPeripheral = nil
+    }
+    
+    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
+        print("已断开连接")
+        disConnectedCallBack?(peripheral)
+    }
+}
+
+extension BluetoothManager: CBPeripheralDelegate {
+    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
+       peripheral
+            .services?
+            .forEach {
+                peripheral.discoverCharacteristics(nil, for: $0)
+            }
+    }
+    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
+        service
+            .characteristics?
+            .forEach {
+                if $0.uuid.uuidString == kVoiceUUID {
+                    peripheral.setNotifyValue(true, for: $0)
+                    return
+                }
+                if $0.uuid.uuidString == kDeviceInfoUUID {
+                    writeFileCharacteristic = $0
+                    peripheral.setNotifyValue(true, for: $0)
+                    return
+                }
+            }
+    }
+    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
+        peripheral.readValue(for: characteristic)
+    }
+    
+    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
+        // 收到蓝牙设备的数据,转发给sdk处理
+        if characteristic.uuid.uuidString == kVoiceUUID {
+            MixSdk.shared.ble.sendToSdk(voiceData: characteristic.value)
+        }
+        else if characteristic.uuid.uuidString == kDeviceInfoUUID{
+            MixSdk.shared.ble.sendToSdk(infoData: characteristic.value)
+        }
+    }
+    
+    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
+        // sdk将回调的数据传给ble设备后,告知sdk写入的情况
+//        MixSdk.shared.ble.didWriteToDevice(characteristic: characteristic, error: error)
+    }
+}
index 577ece6..c426ed0 100644 (file)
@@ -8,10 +8,6 @@
 import Foundation
 
 
-
-let gptKey = "sk-proj-04ogvrZQqyaiv1OWw8OZT3BlbkFJG9Gyr8oFvRdmr3oXQNbX"
-let gptModel = "gpt-4-turbo"
-
 //废弃
 //let deviceId = "281106AFB0F7"
 //let productId = "pafOCKLiUbeVHbdZlddH"
@@ -12,20 +12,21 @@ import Toast_Swift
 
 
 
-class VoiceChatViewController: UIViewController{
-    
+class PhoneChatVC: UIViewController{
     let recordMamager =  AiMouseRecordManager()
     var voiceData:NSMutableData = NSMutableData()
     
     let tableView = UITableView(frame:.zero, style: .plain)
-    let stateLabel = UILabel()
+    let stateLabel = UIButton(type: .custom)
     var dataArray = [CellModel]()
     
+    
     deinit {
         print("销毁")
-        
+        PlayerManager.shared.stop()
         recordMamager.stop()
         MixSdk.shared.voiceChat.finish()
+        
     }
     
     override func viewDidLoad() {
@@ -38,7 +39,7 @@ class VoiceChatViewController: UIViewController{
     }
     
     func setupViews(){
-        self.title = "VoiceChat"
+        self.title = "phone chat"
         self.view.backgroundColor = .white
         
         
@@ -50,12 +51,12 @@ class VoiceChatViewController: UIViewController{
         tableView.snp.makeConstraints { make in
             make.top.equalToSuperview().inset(80)
             make.left.right.equalToSuperview()
-            make.bottom.equalToSuperview().offset(80)
+            make.bottom.equalToSuperview().inset(80)
         }
         
         stateLabel.backgroundColor = .blue
-        stateLabel.textAlignment = .center
-        stateLabel.textColor = .white
+        stateLabel.setTitle("", for: .normal)
+        stateLabel.addTarget(self, action: #selector(requestToSpeak), for: .touchUpInside)
         view.addSubview(stateLabel)
         stateLabel.snp.makeConstraints { make in
             make.height.equalTo(80)
@@ -69,34 +70,30 @@ class VoiceChatViewController: UIViewController{
         }
         recordMamager.start()
     }
-    
     func setupVoiceChat(){
         MixSdk.shared.voiceChat.start()
-        MixSdk.shared.voiceChat.eventCallBack = { [weak self] model in
-            guard let self = self else {
-                return
-            }
+        MixSdk.shared.voiceChat.eventCallBack = { [unowned self] model in
             if model.event == "RespondingEnded"{
                 self.saveToFile()
-                self.voiceData = NSMutableData()
             }
             if model.payload?.state == "Listening"{
-                self.stateLabel.text = "正在聆听..."
+                self.stateLabel.setTitle("正在聆听...", for: .normal)
             }
             if model.payload?.state == "Thinking"{
-                self.stateLabel.text = "正在思考..."
+                self.stateLabel.setTitle("正在思考...", for: .normal)
             }
             if model.payload?.state == "Responding"{
-                self.stateLabel.text = "正在回复..."
+                self.stateLabel.setTitle("正在回复...", for: .normal)
+                self.voiceData = NSMutableData()
             }
             
             if  model.event == "SpeechContent",let payload = model.payload,let text = payload.text,let isFinished = payload.finished,isFinished == true{
                 let cellModel = CellModel()
                 cellModel.text = text
-                cellModel.isSend = false
+                cellModel.isSend = true
                 dataArray.append(cellModel)
                 tableView.reloadData()
-                tableView.scrollToRow(at: IndexPath(row: dataArray.count, section: 0), at: .none, animated: true)
+                tableView.scrollToRow(at: IndexPath(row: dataArray.count - 1, section: 0), at: .none, animated: true)
             }
             if  model.event == "RespondingContent",let payload = model.payload,let text = payload.text,let isFinished = payload.finished,isFinished == true{
                 let cellModel = CellModel()
@@ -104,24 +101,29 @@ class VoiceChatViewController: UIViewController{
                 cellModel.isSend = false
                 dataArray.append(cellModel)
                 tableView.reloadData()
-                tableView.scrollToRow(at: IndexPath(row: dataArray.count, section: 0), at: .none, animated: true)
+                tableView.scrollToRow(at: IndexPath(row: dataArray.count - 1, section: 0), at: .none, animated: true)
             }
-            MixSdk.shared.voiceChat.replyCallBack = {[weak self] data in
-                self?.voiceData.append(data)
+            MixSdk.shared.voiceChat.replyCallBack = {[unowned self] data in
+                self.voiceData.append(data)
             }
         }
     }
+    
+    @objc func requestToSpeak(){
+        PlayerManager.shared.stop()
+        MixSdk.shared.voiceChat.requestToSpeak()
+    }
     func saveToFile(){
-        let mp3Path = NSHomeDirectory() + "/Documents/1.mp3"
+        let mp3Path = NSHomeDirectory() + "/Documents/1111\(Int(Date().timeIntervalSince1970)).mp3"
         
         let url = URL(fileURLWithPath: mp3Path)
         do {
             try self.voiceData.write(to: url, options: .atomic)
             print(mp3Path)
-            self.stateLabel.text = "正在播放..."
+            self.stateLabel.setTitle("正在播放...", for: .normal)
             MixSdk.shared.voiceChat.startPlay()
-            PlayerManager.shared.play(filePath: mp3Path) { success in
-                self.stateLabel.text = "结束播放..."
+            PlayerManager.shared.play(filePath: mp3Path) {[unowned self] success in
+                self.stateLabel.setTitle("结束播放...", for: .normal)
                 MixSdk.shared.voiceChat.endPlay()
             }
         }
@@ -130,7 +132,7 @@ class VoiceChatViewController: UIViewController{
         }
     }
 }
-extension VoiceChatViewController:UITableViewDelegate,UITableViewDataSource {
+extension PhoneChatVC:UITableViewDelegate,UITableViewDataSource {
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
         return dataArray.count
     }
index 16dc85b..4c447f5 100644 (file)
@@ -4,3 +4,4 @@
 
 #import "AiMouseAudioRecord.h"
 #import "AiMouseRecordManager.h"
+#import "AiMouseAudioRecord.h"
diff --git a/MixSdk/Example/Example/Resource/1.pcm b/MixSdk/Example/Example/Resource/1.pcm
deleted file mode 100644 (file)
index 53b3ea9..0000000
Binary files a/MixSdk/Example/Example/Resource/1.pcm and /dev/null differ
diff --git a/MixSdk/Example/Example/Resource/2.pcm b/MixSdk/Example/Example/Resource/2.pcm
deleted file mode 100644 (file)
index 6c888f9..0000000
Binary files a/MixSdk/Example/Example/Resource/2.pcm and /dev/null differ
diff --git a/MixSdk/Example/Example/Resource/AIAudio.sbc b/MixSdk/Example/Example/Resource/AIAudio.sbc
deleted file mode 100644 (file)
index d96322c..0000000
Binary files a/MixSdk/Example/Example/Resource/AIAudio.sbc and /dev/null differ
diff --git a/MixSdk/Example/Example/Resource/AIAudio_2.sbc b/MixSdk/Example/Example/Resource/AIAudio_2.sbc
deleted file mode 100644 (file)
index d15112d..0000000
Binary files a/MixSdk/Example/Example/Resource/AIAudio_2.sbc and /dev/null differ
diff --git a/MixSdk/Example/Example/Resource/cat.png b/MixSdk/Example/Example/Resource/cat.png
deleted file mode 100644 (file)
index 83c5a57..0000000
Binary files a/MixSdk/Example/Example/Resource/cat.png and /dev/null differ
diff --git a/MixSdk/Example/Example/Resource/tts.mp3 b/MixSdk/Example/Example/Resource/tts.mp3
deleted file mode 100644 (file)
index c9e45e0..0000000
Binary files a/MixSdk/Example/Example/Resource/tts.mp3 and /dev/null differ
index b8b71d7..743386c 100644 (file)
+////
+////  Date.swift
+////  Example
+////
+////  Created by incar on 2024/7/31.
+////
 //
-//  Date.swift
-//  Example
+//import Foundation
 //
-//  Created by incar on 2024/7/31.
-//
-
-import Foundation
-
-// MARK: - Extension Date
-extension Date {
-    
-    static let formatter = DateFormatter()
-    
-    struct Format {
-        
-        let rawValue: String
-        init(_ rawValue: String) {
-            self.rawValue = rawValue
-        }
-        
-        /// e.g. 01:01:01
-        static let time: Format = .init("HH:mm:ss")
-        
-        /// e.g. 01:01
-        static let shortTime: Format = .init("HH:mm")
-        
-        /// e.g. 01-01
-        static let date: Format = .init("MM-dd")
-        
-        /// e.g. 01月01日
-        static let date1: Format = .init("MM月dd日")
-        
-        /// e.g. 2020-01-01
-        static let short: Format = .init("yyyy-MM-dd")
-        
-        /// e.g. 2020年01月01日
-        static let short1: Format = .init("yyy年MM月dd日")
-        
-        /// e.g. 2020-01-01 01:01
-        static let middle: Format = .init("yyyy-MM-dd HH:mm")
-        
-        /// e.g. 2020年01月01日 01:01
-        static let middle1: Format = .init("yyy年MM月dd日 HH:mm")
-        
-        /// e.g. 2020-01-01 01:01:01
-        static let long: Format = .init("yyyy-MM-dd HH:mm:ss")
-        
-        /// e.g. 2020年01月01日 01:01:01
-        static let long1: Format = .init("yyyy年MM月dd日 HH:mm:ss")
-        
-        /// e.g. 2020-01-01 01:01:01.088
-        static let full: Format = .init("yyyy-MM-dd HH:mm:ss.SSS")
-        
-        /// e.g. 2020年01月01日 01:01:01.088
-        static let full1: Format = .init("yyyy年MM月dd日 HH:mm:ss.SSS")
-    }
-    
-    /// 格式化成本地时间
-    func format(_ format: Date.Format = .middle) -> String {
-        Self.formatter.dateFormat = format.rawValue
-        return Self.formatter.string(from: self)
-    }
-    
-    enum Unit {
-        case minute(Int)
-        case hour(Int)
-        case day(Int)
-        
-        /// 转换成秒
-        var second: TimeInterval {
-            switch self {
-            case .minute(let minute): return 60.0 * Double(minute)
-            case .hour(let hour): return 60.0 * 60.0 * Double(hour)
-            case .day(let day): return 60.0 * 60.0 * 24.0 * Double(day)
-            }
-        }
-    }
-}
-
-let today: Date = .init()
-
-/// 本地化
-extension Date {
-    /// 星期几
-    enum Week: Int {
-        /// 星期日
-        case sun = 1
-        /// 星期一
-        case mon
-        /// 星期二
-        case tue
-        /// 星期三
-        case wed
-        /// 星期四
-        case thur
-        /// 星期五
-        case fri
-        /// 星期六
-        case sat
-    }
-    
-//    /// 当天的第一秒
-//    var fromTimeInterval: TimeInterval {
-//        let today = format(.short)
-//        return today.timeIntervalSince1970(.short)
+//// MARK: - Extension Date
+//extension Date {
+//    
+//    static let formatter = DateFormatter()
+//    
+//    struct Format {
+//        
+//        let rawValue: String
+//        init(_ rawValue: String) {
+//            self.rawValue = rawValue
+//        }
+//        
+//        /// e.g. 01:01:01
+//        static let time: Format = .init("HH:mm:ss")
+//        
+//        /// e.g. 01:01
+//        static let shortTime: Format = .init("HH:mm")
+//        
+//        /// e.g. 01-01
+//        static let date: Format = .init("MM-dd")
+//        
+//        /// e.g. 01月01日
+//        static let date1: Format = .init("MM月dd日")
+//        
+//        /// e.g. 2020-01-01
+//        static let short: Format = .init("yyyy-MM-dd")
+//        
+//        /// e.g. 2020年01月01日
+//        static let short1: Format = .init("yyy年MM月dd日")
+//        
+//        /// e.g. 2020-01-01 01:01
+//        static let middle: Format = .init("yyyy-MM-dd HH:mm")
+//        
+//        /// e.g. 2020年01月01日 01:01
+//        static let middle1: Format = .init("yyy年MM月dd日 HH:mm")
+//        
+//        /// e.g. 2020-01-01 01:01:01
+//        static let long: Format = .init("yyyy-MM-dd HH:mm:ss")
+//        
+//        /// e.g. 2020年01月01日 01:01:01
+//        static let long1: Format = .init("yyyy年MM月dd日 HH:mm:ss")
+//        
+//        /// e.g. 2020-01-01 01:01:01.088
+//        static let full: Format = .init("yyyy-MM-dd HH:mm:ss.SSS")
+//        
+//        /// e.g. 2020年01月01日 01:01:01.088
+//        static let full1: Format = .init("yyyy年MM月dd日 HH:mm:ss.SSS")
+//    }
+//    
+//    /// 格式化成本地时间
+//    func format(_ format: Date.Format = .middle) -> String {
+//        Self.formatter.dateFormat = format.rawValue
+//        return Self.formatter.string(from: self)
+//    }
+//    
+//    enum Unit {
+//        case minute(Int)
+//        case hour(Int)
+//        case day(Int)
+//        
+//        /// 转换成秒
+//        var second: TimeInterval {
+//            switch self {
+//            case .minute(let minute): return 60.0 * Double(minute)
+//            case .hour(let hour): return 60.0 * 60.0 * Double(hour)
+//            case .day(let day): return 60.0 * 60.0 * 24.0 * Double(day)
+//            }
+//        }
 //    }
+//}
 //
-//    /// 当天最后一秒
-//    var toTimeInterval: TimeInterval { fromTimeInterval + Unit.day(1).second }
-    
-    /// 哪一年
-    var year: Int { component(.year) }
-    
-    /// 哪个月
-    var month: Int { component(.month) }
-    
-    /// 当月的第几天
-    var day: Int { component(.day) }
-    
-    /// 当月的第几天
-    var hour: Int { component(.hour)}
-    
-    /// 当月的第几天
-    var minute: Int { component(.minute)}
-    
-    /// 当月的第几天
-    var second: Int { component(.second)}
-    
-    /// 当月的第几周
-    var  weekOfMonth: Int { component(.weekOfMonth) }
-    
-    /// 今年的第几周
-    var  weekOfYear: Int { component(.weekOfYear) }
-    
-    /// 星期几
-    var weekDay: Week { Week(rawValue: component(.weekday))! }
-    
-    /// 假设当天是周五,表示本月的第几个周五
-    var weekdayOrdinal: Int { component(.weekdayOrdinal) }
-    
-    private func component(_ component: Calendar.Component) -> Int {
-        return Calendar.autoupdatingCurrent.component(component, from: self)
-    }
-}
+//let today: Date = .init()
+//
+///// 本地化
+//extension Date {
+//    /// 星期几
+//    enum Week: Int {
+//        /// 星期日
+//        case sun = 1
+//        /// 星期一
+//        case mon
+//        /// 星期二
+//        case tue
+//        /// 星期三
+//        case wed
+//        /// 星期四
+//        case thur
+//        /// 星期五
+//        case fri
+//        /// 星期六
+//        case sat
+//    }
+//    
+////    /// 当天的第一秒
+////    var fromTimeInterval: TimeInterval {
+////        let today = format(.short)
+////        return today.timeIntervalSince1970(.short)
+////    }
+////
+////    /// 当天最后一秒
+////    var toTimeInterval: TimeInterval { fromTimeInterval + Unit.day(1).second }
+//    
+//    /// 哪一年
+//    var year: Int { component(.year) }
+//    
+//    /// 哪个月
+//    var month: Int { component(.month) }
+//    
+//    /// 当月的第几天
+//    var day: Int { component(.day) }
+//    
+//    /// 当月的第几天
+//    var hour: Int { component(.hour)}
+//    
+//    /// 当月的第几天
+//    var minute: Int { component(.minute)}
+//    
+//    /// 当月的第几天
+//    var second: Int { component(.second)}
+//    
+//    /// 当月的第几周
+//    var  weekOfMonth: Int { component(.weekOfMonth) }
+//    
+//    /// 今年的第几周
+//    var  weekOfYear: Int { component(.weekOfYear) }
+//    
+//    /// 星期几
+//    var weekDay: Week { Week(rawValue: component(.weekday))! }
+//    
+//    /// 假设当天是周五,表示本月的第几个周五
+//    var weekdayOrdinal: Int { component(.weekdayOrdinal) }
+//    
+//    private func component(_ component: Calendar.Component) -> Int {
+//        return Calendar.autoupdatingCurrent.component(component, from: self)
+//    }
+//}
index 5854b34..acb3a06 100644 (file)
@@ -1,52 +1,44 @@
-//import AVFoundation
 //
-//class PcmPlayer {
-//    private var audioEngine: AVAudioEngine!
-//    private var audioPlayerNode: AVAudioPlayerNode!
-//    private var audioFormat: AVAudioFormat!
-//
-//    init(sampleRate: Double, channels: AVAudioChannelCount, bitsPerSample: Int) {
-//        audioEngine = AVAudioEngine()
-//        audioPlayerNode = AVAudioPlayerNode()
-//
-//        let formatSettings = [
-//            AVFormatIDKey: kAudioFormatLinearPCM,
-//            AVSampleRateKey: sampleRate,
-//            AVNumberOfChannelsKey: channels,
-//            AVLinearPCMBitDepthKey: bitsPerSample,
-//            AVLinearPCMIsBigEndianKey: false,
-//            AVLinearPCMIsFloatKey: false
-//        ] as [String : Any]
-//
-//        audioFormat = AVAudioFormat(settings: formatSettings)!
-//    }
 //
-//    func startPlaying() {
-//        audioEngine.attach(audioPlayerNode)
-//        audioEngine.connect(audioPlayerNode, to: audioEngine.mainMixerNode, format: audioFormat)
 //
-//        do {
-//            try audioEngine.start()
-//        } catch {
-//            print("Failed to start audio engine: \(error)")
-//            return
-//        }
+//import SwiftAudio
 //
-//        audioPlayerNode.play()
 //
-//        DispatchQueue.global(qos:.default).async {
-//            while true {
-//                // 模拟接收 PCM 数据的逻辑
-//                let pcmData = [UInt8](repeating: 0xAA, count: 1024)  // 这里只是一个简单的示例数据,您需要替换为实际接收到的数据
-//                let audioBuffer = AVAudioPCMBuffer(pcmFormat: self.audioFormat, frameCapacity: AVAudioFrameCount(pcmData.count / self.audioFormat.streamDescription.pointee.mBytesPerFrame))
-//                audioBuffer.frameLength = audioBuffer.frameCapacity
-//                memcpy(audioBuffer.int16ChannelData![0], pcmData, pcmData.count)
-//                self.audioPlayerNode.scheduleBuffer(audioBuffer, at: nil, options: [], completionHandler: nil)
+//class PcmPlayer {
+//    public static let shared = PcmPlayer()
+//    private var finishCallback:((_ success:Bool) -> Void)?
+//    
+//    let player = QueuedAudioPlayer()
+//    
+//    init(){
+//        player.event.stateChange.addListener(self, handleAudioPlayerStateChange)
+//        //        player.remoteCommandController.handlePlayCommand = { (event) in
+//        //
+//        //        }
+//    }
+//    
+//    
+//    
+//    func handleAudioPlayerStateChange(state: AudioPlayerState) {
+//        DispatchQueue.main.sync {
+//            print("state = \(state)")
+//            if state == .paused {
+//                self.finishCallback?(true)
 //            }
 //        }
 //    }
 //}
 //
-//// 使用示例
-////let pcmPlayer = PcmPlayer(sampleRate: 44100.0, channels: 2, bitsPerSample: 16)
-////pcmPlayer.startPlaying()
+//extension PcmPlayer{
+//    
+//    //       func playFile(pcmData: Data) {
+//    
+//    func playFile(filePath: String,finished: ((_ success: Bool) -> Void)? = nil) {
+//        print("声音播放: \(filePath)")
+//        self.finishCallback = finished
+//        
+//        let audioItem = DefaultAudioItem(audioUrl: filePath, sourceType: .file)
+//        try? player.add(item: audioItem, playWhenReady: true)
+//        try? player.next()
+//    }
+//}
index 3a96d17..26860cd 100644 (file)
@@ -8,7 +8,7 @@
 import Foundation
 import AVFoundation
 
-class PlayerManager:NSObject,AVAudioPlayerDelegate{
+class PlayerManager:NSObject{
     public static let shared = PlayerManager()
     private var finishCallback:((_ success:Bool) -> Void)?
     
@@ -37,6 +37,14 @@ class PlayerManager:NSObject,AVAudioPlayerDelegate{
         }
     }
     
+    func stop(){
+        if player.isPlaying{
+            player.stop()
+        }
+    }
+}
+
+extension PlayerManager:AVAudioPlayerDelegate{
     func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
         // 播放结束时调用
         print("播放结束 \(flag)")
index 9599a1d..ab4e995 100644 (file)
 //  ViewController.swift
 //  Example
 //
-//  Created by incar on 2024/6/12.
-//
 
 import UIKit
-import MixSdk
-import SnapKit
+import CoreBluetooth
 import Toast_Swift
+import MixSdk
 
-class ViewController: UIViewController{
-    let list:[(name:String,action:String)] = [
-        ("注册","registerAction"),
-        ("登录","loginAction"),
-
-        ("VoiceChat","voiceChatAction"),
-    ]
+class ViewController: UIViewController {
     
+    let tableView = UITableView(frame:.zero, style: .plain)
+    var dataArray = [CBPeripheral]()
+        
     override func viewDidLoad() {
         super.viewDidLoad()
-        // Do any additional setup after loading the view.
-        self.title = "MixSdk"
-        let tableView = UITableView(frame: CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height), style: .plain)
+        title = "扫描"
+        setupViews()
+        setupBle()
+    }
+    
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        scanAction()
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+        BluetoothManager.shared.stopScan()
+    }
+    
+    func setupViews(){
+        self.title = "VoiceChat"
+        self.view.backgroundColor = .white
+        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "扫描", style: .done, target: self, action: #selector(scanAction))
+        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "注册", style: .done, target: self, action: #selector(loginAction))
+        
         tableView.delegate = self
         tableView.dataSource = self
-        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
+        tableView.estimatedRowHeight = 40
+        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UITableViewCell")
         view.addSubview(tableView)
-        
+        tableView.snp.makeConstraints { make in
+            make.top.equalToSuperview().inset(80)
+            make.left.right.equalToSuperview()
+            make.bottom.equalToSuperview().offset(80)
+        }
+    }
+    
+    func setupBle() {
+        BluetoothManager.shared.discoverCallBack = { [unowned self] peripheral in
+            if dataArray.contains(peripheral){
+                return
+            }
+            if peripheral.name == nil || (peripheral.name!.isEmpty){
+                return
+            }
+            print(peripheral.name)
+            dataArray.append(peripheral)
+            tableView.reloadData()
+        }
+        BluetoothManager.shared.didConnectedCallBack = {[unowned self] peripheral in
+            self.view.hideToastActivity()
+//            let vc = PhoneChatVC()
+            let vc = BleChatVC()
+            self.navigationController?.pushViewController(vc, animated: true)
+        }
+        BluetoothManager.shared.disConnectedCallBack = { [unowned self] peripheral in
+            print("蓝牙已经断开!!!!!!!!!!!")
+            self.view.makeToast("蓝牙已经断开!")
+        }
+    }
+    
+    @objc func scanAction(){
+        BluetoothManager.shared.startScan()
     }
 }
 
-extension ViewController:UITableViewDelegate,UITableViewDataSource {
-    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-        return 60
-    }
+extension ViewController: UITableViewDelegate, UITableViewDataSource {
+    
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return list.count
+        return dataArray.count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
-        cell.textLabel?.text = list[indexPath.row].name
-        
+        let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
+
+        cell.textLabel?.text = dataArray[indexPath.row].name ?? ""
         return cell
     }
+    
     func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let data = list[indexPath.row]
-        let action = Selector(data.action)
-        self.perform(action)
+        view.makeToastActivity(.center)
+        let device = dataArray[indexPath.row]
+        BluetoothManager.shared.connect(device)
+        
+//        let vc = PhoneChatVC()
+//            let vc = BleChatVC()
+//        self.navigationController?.pushViewController(vc, animated: true)
     }
 }
-
 extension ViewController{
     @objc func registerAction() {
         view.makeToastActivity(.center)
@@ -95,15 +143,3 @@ extension ViewController{
         }
     }
 }
-extension ViewController{
-    @objc func voiceChatAction() {
-        if !MixSdk.shared.auth.authorized {
-            self.view.makeToast("unAuthorized!")
-            return;
-        }
-        let vc = VoiceChatViewController()
-        self.navigationController?.pushViewController(vc, animated: true)
-    }
-    
-}
-
index b5e9796..08f8561 100644 (file)
                18A2DD382C40D94900A8731B /* BigInt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A2DCC62C40D94700A8731B /* BigInt.swift */; };
                18A2DD392C40D94900A8731B /* FloatingPointConversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A2DCC72C40D94700A8731B /* FloatingPointConversion.swift */; };
                18A2DD3A2C40D94900A8731B /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A2DCC82C40D94700A8731B /* Array+Extension.swift */; };
+               18AA04692CE444D40008A345 /* MSBleSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AA04682CE444D40008A345 /* MSBleSender.swift */; };
+               18AA046C2CE45E800008A345 /* MSBleInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AA046B2CE45E800008A345 /* MSBleInfo.swift */; };
+               18B1A38C2CE3547F00DD4A7C /* MSBle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B1A38B2CE3547F00DD4A7C /* MSBle.swift */; };
                18C42B4B2C23DA29000D8860 /* SbcToPcm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18C42B4A2C23DA29000D8860 /* SbcToPcm.swift */; };
                18C42BE72C242202000D8860 /* version.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C42B5E2C242202000D8860 /* version.h */; };
                18C42BE82C242202000D8860 /* buffersrc.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C42B5F2C242202000D8860 /* buffersrc.h */; };
                18A2DCC62C40D94700A8731B /* BigInt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BigInt.swift; sourceTree = "<group>"; };
                18A2DCC72C40D94700A8731B /* FloatingPointConversion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingPointConversion.swift; sourceTree = "<group>"; };
                18A2DCC82C40D94700A8731B /* Array+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = "<group>"; };
+               18AA04682CE444D40008A345 /* MSBleSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSBleSender.swift; sourceTree = "<group>"; };
+               18AA046B2CE45E800008A345 /* MSBleInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSBleInfo.swift; sourceTree = "<group>"; };
+               18B1A38B2CE3547F00DD4A7C /* MSBle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSBle.swift; sourceTree = "<group>"; };
                18C42B4A2C23DA29000D8860 /* SbcToPcm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SbcToPcm.swift; sourceTree = "<group>"; };
                18C42B5E2C242202000D8860 /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = "<group>"; };
                18C42B5F2C242202000D8860 /* buffersrc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = buffersrc.h; sourceTree = "<group>"; };
                                18C42C762C24241A000D8860 /* MixSdk-Bridging-Header.h */,
                                18204BEF2C19457600F94E69 /* MSHttpManager.swift */,
                                1811882E2C1A89E600C23FAB /* MixSdk.swift */,
+                               18B1A38B2CE3547F00DD4A7C /* MSBle.swift */,
+                               18AA046B2CE45E800008A345 /* MSBleInfo.swift */,
+                               18AA04682CE444D40008A345 /* MSBleSender.swift */,
                                18204BC52C193E4B00F94E69 /* MSAuth.swift */,
                                18314A882C3CD55E0028CE50 /* MSAuthModel.swift */,
                                186BFD482C57915500673D8C /* MSVoiceChat.swift */,
                                18A2DCFE2C40D94800A8731B /* Authenticator.swift in Sources */,
                                18A2DD172C40D94900A8731B /* OCB.swift in Sources */,
                                18C42C7B2C24270D000D8860 /* MSAudioConvert.swift in Sources */,
+                               18B1A38C2CE3547F00DD4A7C /* MSBle.swift in Sources */,
                                18A2DCCC2C40D94800A8731B /* ASN1Decoder.swift in Sources */,
                                18A2DCDE2C40D94800A8731B /* CMAC.swift in Sources */,
                                18A2DD002C40D94800A8731B /* Operators.swift in Sources */,
                                18A2DD1D2C40D94900A8731B /* BlockMode.swift in Sources */,
                                18A2DD282C40D94900A8731B /* Addition.swift in Sources */,
                                18A2DCD92C40D94800A8731B /* CBCMAC.swift in Sources */,
+                               18AA046C2CE45E800008A345 /* MSBleInfo.swift in Sources */,
                                18A2DD132C40D94900A8731B /* PKCS1v15.swift in Sources */,
                                18A2DCCE2C40D94800A8731B /* StreamDecryptor.swift in Sources */,
                                18A2DD0F2C40D94900A8731B /* Padding.swift in Sources */,
                                18A2DD182C40D94900A8731B /* CFB.swift in Sources */,
                                18A2DCD12C40D94800A8731B /* UInt64+Extension.swift in Sources */,
                                18A2DD292C40D94900A8731B /* StringConversion.swift in Sources */,
+                               18AA04692CE444D40008A345 /* MSBleSender.swift in Sources */,
                                18A2DCFC2C40D94800A8731B /* ZeroPadding.swift in Sources */,
                                18A2DCDC2C40D94800A8731B /* Bit.swift in Sources */,
                                18A2DD0B2C40D94800A8731B /* Utils.swift in Sources */,
index 288a94d..2964ca2 100644 (file)
@@ -6,18 +6,7 @@
 //
 
 import Foundation
-///// 注册返回模型
-//public class MSRegisterModel:Codable{
-//    /// 是否成功
-//    public var success:Bool = false
-//    /// 提示语
-//    public var msg:String = "请求失败"
-//    /// 状态码
-//    public var code:Int = 400
-//    /// 内容
-//    public var data:MSRegisterData?
-//    
-//}
+
 /// 注册返回模型
 public class MSRegisterModel{
     /// 设备ID
diff --git a/MixSdk/MixSdk/MixSdk/MSBle.swift b/MixSdk/MixSdk/MixSdk/MSBle.swift
new file mode 100644 (file)
index 0000000..f09006d
--- /dev/null
@@ -0,0 +1,341 @@
+//
+//  MSBle.swift
+//  MixSdk
+//
+//  Created by incar on 2024/11/12.
+//
+
+import Foundation
+
+
+
+/// SDK 管理类
+public class MSBle {
+    public var authCallBack:(()->Void)?
+    public var didAuth:Bool{
+        return MixSdk.shared.auth.authorized
+    }//是否正在鉴权
+    
+    var lastSender:MSBleSender?
+    var deviceInfo:MSBleInfo?
+    var isAuthing = false//是否正在鉴权
+    
+    
+    init() {
+
+    }
+
+    /// sdk发送数据回调,app将此数据转发给蓝牙设备,写入设备成功后,需要调didWriteToDevice(characteristic:, error:)告知sdk是否写入成功
+    public var sdkToDeviceCallback:((_ data: Data) -> Void)?
+    
+    /// 收到蓝牙设备传过来的数据,转发给sdk处理
+    public func sendToSdk(voiceData data: Data?){
+        guard let data = data else{
+            Logger.log("[recv][voice]")
+            return
+        }
+//        Logger.log("[recv][voice] \(data)")
+//        if self.didAuth == false{
+//            
+//            DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+//                self.startAuth()
+//            }
+//            return
+//        }
+        if(data.count > 16){
+            MSAudioConvert.sbcTopcm(data){ pcm in
+                MixSdk.shared.voiceChat.sendVoice(pcmData: pcm)
+            }
+        }
+    }
+    /// 收到蓝牙设备传过来的数据,转发给sdk处理
+    public func sendToSdk(infoData data: Data?){
+        guard let data = data else{
+            Logger.log("[recv][info]")
+            return
+        }
+        Logger.log("[recv][info] " + data.joined("") + MSUtils.data(toUtf8String: data))
+//        if MSUtils.data(toHexString: data) == "7b2272737369223a2230227d"{
+//            return
+//        }
+//        if let sender = lastSender{
+//            lastSender = nil
+//            sender.completion?(.response.code(.success).value(data))
+//            sender.remove()
+//            
+//            deinitTimer()
+//        }
+    }
+    /// sdk传给ble设备后,告知sdk写入的情况
+//    public func didWriteToDevice(characteristic: CBCharacteristic, error: Error?){
+//        print(characteristic.uuid.uuidString, "write success")
+//        if let error = error {
+//            print("发送失败 ",characteristic.uuid.uuidString,error.localizedDescription)
+//        }
+//        else{
+//            if characteristic.uuid.uuidString == writeFileCharacteristicUUID{
+//                IBBluetooth.shared.didWriteToDevice()
+//            }
+//        }
+//    }
+}
+
+
+extension MSBle{
+    public func startAuth(){
+        if isAuthing {
+            return
+        }
+        isAuthing = true
+        requestDeviceInfo {[weak self] info in
+            guard let self = self else {return}
+            guard let info = info else {
+                Logger.log("获取设备信息失败!!!")
+                isAuthing = false
+                return
+            }
+            Logger.log("设备信息 = \(info.description)")
+            deviceInfo = info
+            
+            deviceInfo!.deviceId = "28110687C456"
+            deviceInfo!.productId = "WTxAKo2qD2pXOZZLW3zz"
+            deviceInfo!.productKey = "YqggeKCxfEOVQGjUYrpQQdEstGpvojzn"
+            deviceInfo!.password = ""
+            
+            self.startLogin {[weak self] token, error in
+                guard let self = self else {return}
+                if let _ = token {
+                    self.authCallBack?()
+                }
+                else{
+//                    self.authCallBack?(false)
+                }
+                isAuthing = false
+            }
+        }
+    }
+    func requestDeviceInfo(finished: @escaping (MSBleInfo?) -> Void){
+        let deviceInfo = MSBleInfo()
+        self.requestDeviceId {[weak self] value in
+            guard let self = self else {return}
+            if let value = value{
+                deviceInfo.deviceId = value
+                
+                self.requestProductKey { value in
+                    if let value = value{
+                        deviceInfo.productKey = value
+                        
+                        self.requestSecretKey { value in
+                            if let value = value{
+                                deviceInfo.productId = value
+                                
+                                self.requestDeviceSecret { value in
+                                    if let value = value{
+                                        deviceInfo.password = value
+                                        finished(deviceInfo)
+                                        return
+                                    }
+                                    else{
+                                        finished(nil)
+                                    }
+                                }
+                            }
+                            else{
+                                finished(nil)
+                            }
+                        }
+                    }
+                    else{
+                        finished(nil)
+                    }
+                }
+            }
+            else{
+                finished(nil)
+            }
+        }
+    }
+    
+    func requestDeviceId(finished: @escaping (String?) -> Void){
+        let sender = dictionaryToSender(["cmd":"get_MAC"])
+        sender.completion = { result in
+            Logger.log("\(result.description)")
+            if result.isSuccess,let value = MSUtils.data(toDictionary: result.data)["MAC"] as? String{
+                finished(value)
+            }
+            else{
+                finished(nil)
+            }
+        }
+        sendToBle(sender)
+    }
+    func requestProductKey(finished: @escaping (String?) -> Void){
+        let sender = dictionaryToSender(["cmd":"get_productKey"])
+        sender.completion = { result in
+            Logger.log("\(result.description)")
+            if result.isSuccess,let value = MSUtils.data(toDictionary: result.data)["productKey"] as? String{
+                finished(value)
+            }
+            else{
+                finished(nil)
+            }
+        }
+        sendToBle(sender)
+    }
+    func requestSecretKey(finished: @escaping (String?) -> Void){
+        let sender = dictionaryToSender(["cmd":"get_secretKey"])
+        sender.completion = { result in
+            Logger.log("\(result.description)")
+            if result.isSuccess,let value = MSUtils.data(toDictionary: result.data)["secretKey"] as? String{
+                finished(value)
+            }
+            else{
+                finished(nil)
+            }
+        }
+        sendToBle(sender)
+    }
+    func requestDeviceSecret(finished: @escaping (String?) -> Void){
+        let sender = dictionaryToSender(["cmd":"get_deviceSecret"])
+        sender.completion = { result in
+            Logger.log("\(result.description)")
+            if result.isSuccess,let value = MSUtils.data(toDictionary: result.data)["deviceSecret"] as? String{
+                finished(value)
+            }
+            else{
+                finished(nil)
+            }
+        }
+        sendToBle(sender)
+    }
+    
+    
+    func sendToBle(_ sender:MSBleSender){
+        if let _ = lastSender {
+            sender.completion(.response.code(.failure))
+            sender.remove()
+            Logger.log("有命令正在执行中...")
+            if Self.timer == nil{
+                startTimer()
+            }
+            return
+        }
+        Thread.sleep(forTimeInterval:0.02)
+        lastSender = sender
+        startTimer()
+        for data in sender.list{
+            if let sendToBleBlock = self.sdkToDeviceCallback {
+                sendToBleBlock(data)
+            }
+        }
+    }
+}
+
+
+extension MSBle{
+    static var timer: Timer?
+    func startTimer() {
+        deinitTimer()
+        
+        Self.timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { [unowned self] _ in
+            deinitTimer()
+            if let sender = lastSender{
+                lastSender = nil
+                sender.completion?(.response.code(.timeout))
+                sender.remove()
+            }
+        })
+//        Logger.log("创建定时器")
+    }
+    
+    func deinitTimer() {
+        guard let _ = Self.timer else { return }
+        Self.timer?.invalidate()
+        Self.timer = nil
+//        Logger.log("销毁定时器")
+    }
+}
+
+
+extension MSBle{
+    func dictionaryToSender(_ dict: [String: Any]) ->MSBleSender{
+        let sender = MSBleSender()
+        let data = MSUtils.dictionary(toData: dict)
+        let str = MSUtils.data(toUtf8String: data)
+        let list = splitCMD(str: str)
+        Logger.log("[send][info][all][string] \(str)")
+        if list.count > 1 {
+            // 开始标志
+            sender.list.append(stringToData(cmd: "-$$\0"))
+            // 指令
+            for i in 0..<list.count {
+                sender.list.append(stringToData(cmd: "\(list[i])\0"))
+            }
+            // 结束标志
+            sender.list.append(stringToData(cmd: "$$-\0"))
+        } else {
+            let data = stringToData(cmd: "-$$\(str)$$-\n")
+            sender.list.append(data)
+        }
+        
+        return sender
+    }
+
+    func stringToData(cmd: String) ->Data{
+        let data = cmd.data(using:.utf8)!
+        return data
+    }
+
+    func splitCMD(str: String) -> [String] {
+        var list = [String]()
+        var str = str
+        let len = 30
+        while str.count > 0 {
+            if str.count > len {
+                let sub = String(str.prefix(len))
+                list.append(sub)
+                str = String(str.suffix(str.count - len))
+            } else {
+                list.append(str)
+                break
+            }
+        }
+        return list
+    }
+}
+
+extension MSBle{
+    func registerAction(completeHandler: @escaping MSLoginCallBack) {
+        MixSdk.shared.auth.register(productId: deviceInfo!.productId, productKey: deviceInfo!.productKey, deviceId: deviceInfo!.deviceId){[weak self] password,error in
+            if let password = password{
+                self?.deviceInfo?.password = password
+                self?.startLogin(completeHandler: completeHandler)
+            }
+            else{
+                completeHandler(nil,error)
+            }
+        }
+    }
+
+    func startLogin(completeHandler: @escaping MSLoginCallBack){
+        if let info = deviceInfo,info.password.isEmpty{
+            registerAction(completeHandler: completeHandler)
+            return
+        }
+        
+        MixSdk.shared.auth.login(productKey:deviceInfo!.productKey,deviceId: deviceInfo!.deviceId, password: deviceInfo!.password){[weak self]  token,error in
+            if let err = error{
+                if case  .reponseError(let code,_) = err,code == 50021{
+                    //50021是账号或密码错误.可以在这里调用注册,重新注册后,之前的密码会被覆盖,APP更新保存的新密码
+                    self?.registerAction(completeHandler: completeHandler)
+                }
+                else{
+                    completeHandler(nil,err)
+                }
+            }
+            else{
+                completeHandler(token,nil)
+            }
+        }
+    }
+}
diff --git a/MixSdk/MixSdk/MixSdk/MSBleInfo.swift b/MixSdk/MixSdk/MixSdk/MSBleInfo.swift
new file mode 100644 (file)
index 0000000..5bb32fc
--- /dev/null
@@ -0,0 +1,19 @@
+//
+//  MSBleInfo.swift
+//  MixSdk
+//
+//  Created by incar on 2024/11/13.
+//
+
+import Foundation
+class MSBleInfo{
+    var deviceId = ""
+    var productId = ""
+    var productKey = ""
+    var password = ""
+    
+    public var description: String{
+        return "MSBleInfo(deviceId:\(deviceId), productId:\(productId), productKey:\(productKey), password:\(password))"
+    }
+}
+
diff --git a/MixSdk/MixSdk/MixSdk/MSBleSender.swift b/MixSdk/MixSdk/MixSdk/MSBleSender.swift
new file mode 100644 (file)
index 0000000..e0446d7
--- /dev/null
@@ -0,0 +1,84 @@
+//
+//  MSBleSender.swift
+//  MixSdk
+//
+//  Created by incar on 2024/11/13.
+//
+
+import Foundation
+
+class MSBleSender{
+    var completion: MSCompletionHandler!
+    var list = [Data]()
+    
+    static var all: [MSBleSender] = []
+    
+    init() {
+        Self.all.append(self)
+    }
+
+    /// 从缓存实例集合中移除该实例
+    func remove() {
+        Self.all.removeAll { $0 === self }
+    }
+    
+}
+
+public typealias MSCompletionHandler = (_ result: MSResponse) -> Void
+
+/// 状态码
+public enum MSCode: Int,CustomStringConvertible {
+    
+    public var description: String{
+        return "MSCode(rawValue:\(rawValue), message:\(message))"
+    }
+    
+    /// 操作成功
+    case success = 1000
+    /// 操作失败
+    case failure = 1001
+    /// 请求超时
+    case timeout = 1004
+    /// 未知错误
+    case unknow = 1005
+    
+    var message:String{
+        switch self {
+        case .failure:
+            return "失败"
+        case .success:
+            return "成功"
+        case .timeout:
+            return "超时"
+        default:
+            return "未知错误"
+        }
+    }
+}
+
+public class MSResponse {
+    public private(set) var code: MSCode = .success
+    public private(set) var data: Data = Data()
+    static var response: MSResponse { MSResponse() }
+    
+    
+    var isSuccess:Bool{
+        return code == .success
+    }
+    
+    public var description: String{        
+        return code.description + "data = " +  data.joined()
+    }
+    
+    @discardableResult
+    func code(_ value: MSCode) -> Self {
+        code = value
+        return self
+    }
+    
+    @discardableResult
+    func value(_ value: Data) -> Self {
+        self.data = value
+        return self
+    }
+}
index 87fa6b3..ecdc87d 100644 (file)
@@ -76,10 +76,10 @@ public class MSVoiceChat {
     
     /// 开始说话,在发送语音数据前,一定要先调用该方法
     public func startSpeedch(){
-        if !MixSdk.shared.auth.authorized {
-            Logger.log("还未鉴权")
-            return
-        }
+//        if !MixSdk.shared.auth.authorized {
+//            Logger.log("还未鉴权")
+//            return
+//        }
         let params:[String:Any] =
         [
             "header":[
@@ -97,10 +97,10 @@ public class MSVoiceChat {
     
     /// 结束说话
     public func endSpeedch(){
-        if !MixSdk.shared.auth.authorized {
-            Logger.log("还未鉴权")
-            return
-        }
+//        if !MixSdk.shared.auth.authorized {
+//            Logger.log("还未鉴权")
+//            return
+//        }
         let params:[String:Any] =
         [
             "header":[
@@ -119,26 +119,26 @@ public class MSVoiceChat {
     /// 发送语音数据,format pcm,采样率 16000
     /// - Parameter data: 语音数据
     public func sendVoice(pcmData:Data){
-        if !MixSdk.shared.auth.authorized {
-            Logger.log("还未鉴权")
-            return
-        }
+//        if !MixSdk.shared.auth.authorized {
+//            Logger.log("还未鉴权")
+//            return
+//        }
         if currentEvent?.payload?.state == "Listening"{
             Logger.log("send \(pcmData)")
             socket.send(data: pcmData)
         }
         else{
-            Logger.log("当前状态不可以发送语音")
+//            Logger.log("当前状态不可以发送语音")
         }
     }
     
     /// 发送语音数据,format sbc,采样率 16000
     /// - Parameter data: 语音数据
     public func sendVoice(sbcData:Data){
-        if !MixSdk.shared.auth.authorized {
-            Logger.log("还未鉴权")
-            return
-        }
+//        if !MixSdk.shared.auth.authorized {
+//            Logger.log("还未鉴权")
+//            return
+//        }
         MSAudioConvert.sbcTopcm(sbcData){[weak self] pcmData in
             Logger.log("send \(pcmData)")
             self?.socket.send(data: pcmData)
@@ -224,18 +224,30 @@ extension MSVoiceChat{
     private func startTask(){
         let taskId = "\(Int(Date().timeIntervalSince1970)*1000)".md5().lowercased();
         self.taskId = taskId
+//        let payload:[String : Any] = [
+//            "device_id":"test_device_id",
+//            "user_agent":"python",
+//            "in_format":"pcm",
+//            "in_sample_rate":16000,
+//            "out_format":"mp3",
+//            "sample_rate":16000,// 返回音频采样率
+//            "space_time":100,
+//            "volume":"10",
+//            "voice": "huoshan::zh_female_shuangkuaisisi_moon_bigtts", // 音色配置 默认使用阿里云的音色 longzhe 规则 aliyun::[音色] 或者 huoshan::[音色] 例如 aliyun::longzhe 或者 huoshan::zh_female_shuangkuaisisi_moon_bigtts
+//            "voice_detection": "false" // 开启自动断句 enabled/disable
+//        ]
         let payload:[String : Any] = [
             "device_id":"test_device_id",
             "user_agent":"python",
             "in_format":"pcm",
             "in_sample_rate":16000,
             "out_format":"mp3",
-            "sample_rate":16000,
-            "space_time":100,
-            "volume":"2",
-            "voice":"longhua"
+            "sample_rate":16000,// 返回音频采样率
+            "space_time":0,
+            "volume":"10",
+            "voice": "huoshan::zh male dongfanghaoran moon bigtts", // 音色配置 默认使用阿里云的音色 longzhe 规则 aliyun::[音色] 或者 huoshan::[音色] 例如 aliyun::longzhe 或者 huoshan::zh_female_shuangkuaisisi_moon_bigtts
+            "voice_detection": "false" // 开启自动断句 enabled/disable
         ]
-        
         let params:[String:Any] =
         [
             "header":[
@@ -248,6 +260,12 @@ extension MSVoiceChat{
         Logger.log("send" ,cmd)
         socket.send(text: cmd)
     }
+    
+    func closeWebSocket(){
+        if connectState == .connected {
+            socket?.close(10100,reason: "任务失败")
+        }
+    }
 }
 
 
@@ -272,12 +290,17 @@ extension MSVoiceChat:WebSocketDelegate{
         guard let data = text.data,let model = JSON.decode(MSVoiceChatEvent.self, from: data) else{
             return
         }
-        if model.event == "",let dialogId = model.payload?.dialog_id{
+        if model.event == "TaskFailed"{
+            closeWebSocket()
+            return
+        }
+        else if model.event == "Started",let dialogId = model.payload?.dialog_id{
             self.dialogId = dialogId
         }
         else if model.event == "DialogStateChanged"{
             currentEvent = model
         }
+        
         if model.payload?.state == "Listening"{
             startSpeedch()
         }
index a298e0a..b9649a6 100644 (file)
 //  Created by incar on 2024/6/13.
 //
 
-//根据需求, AISDK 还需提供以下三个方法:
-//1.国内模型切换的调用方法(国内模型需求定为文心一言、KIMI、豆包)
-//2.ChatGPT Key 的合规可用的校验方法,返回校验结果
-//3.ChatGPT 模型单独切换的调用方法(gpt3.5-turbo、gpt-4-turbo、gpt-4o)
-
-import Foundation
-
-//    https://apifox.com/apidoc/shared-054b7e2c-cf4b-4e2b-875c-79fda14ddbbf?pwd=xRzzNg7l
-//    访问密码: xRzzNg7l
-//let wsBaseURL = "ws://121.40.67.151:9099/"
-//let httpBaseURL = "http://121.40.67.151:9099/"
-
-//let wsBaseURL = "ws://118.145.158.207:80/"
-//let httpBaseURL = "http://118.145.158.207:80/"
-
-
-
-//Asr、TTS、翻译、OCR、AIGC
-//platform 参数支持
-//
-//1.
-//aliyun
-//
-//2.
-//huoshan
-//
-//Chat 大模型对话
-//platform 参数支持
-//
-//1.
-//baidu (文心一言 模型:ernie-3.5-8k-0329)
-//
-//2.
-//moonshot (kimi 模型:moonshot-v1-8k)
-//
-//3.
-//aliyun (通义千问 模型:qwen-plus)
-//
-//4.
-//huoshan (豆包 模型:ep-20240611101421-5qk8s)
-
-
 class MSConfig{
     static let wsBaseURL = "ws://amp.raisound.com/"
     static let httpBaseURL = "https://amp.raisound.com/"
-//    static let authBaseURL = "http://180.184.206.142/bz-api/"
     static let authBaseURL = "http://dmp.raisound.com/bz-api/"
-//    static let authBaseURL = "http://180.184.206.142/bz-api/"
     static let wsHeartBeatTime = 5.0
+
+//    static let wsBaseURL = "ws://118.145.158.207:22080/"
+//    static let httpBaseURL = "https://amp.raisound.com/"
+//    static let authBaseURL = "http://dmp.raisound.com/bz-api/"
+//    static let wsHeartBeatTime = 5.0
+    
     
-//    static func http(api:String)->String{
-//    return httpBaseURL + api + "?platform=\(MixSdk.shared.aiPlatform.value)&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
-//        return httpBaseURL + api + "?platform=\(MixSdk.shared.aiPlatform.value)&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
-//       return httpBaseURL + api + ""
-//    }
     static func authUrl(api:String)->String{
         return authBaseURL + api
      }
-    static func aigcUrl(api:String)->String{
-        return httpBaseURL + api + "?platform=aliyun&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
- //       return httpBaseURL + api + ""
-     }
-    static func translateUrl(api:String)->String{
-        return httpBaseURL + api + "?platform=aliyun&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
- //        return httpBaseURL + api + "?platform=\(MixSdk.shared.aiPlatform.value)&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
-     }
-   static func asr(api:String)->String{
-       return httpBaseURL + api + "?platform=aliyun&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
-    }
-    static func webSocketUrl(api:String,addPlatform:Bool)->String{
-        return wsBaseURL + api + "?platform=aliyun&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
-    }
-}
-
-
-
-/// Ai平台类型
-public enum MSPlatform {
-    /// 通义千问
-    case aliyun
-    /// 讯飞星火
-    case xunfei
-    /// 文心一言
-    case yiyan
-    /// 豆包
-    case doubao
-    /// Kimi
-    case kimi
-    /// chatGPT
-    case chatGPT
-    
-    internal var value:String{
-        switch self {
-        case .aliyun:
-            return "aliyun"
-        case .xunfei:
-            return "xunfei"
-        case .yiyan:
-            return "baidu"
-        case .doubao:
-            return "huoshan"
-        case .kimi:
-            return "moonshot"
-        case .chatGPT:
-            return "chatGPT"
 
-        }
-    }
-    
-    public var title:String{
-        switch self {
-        case .aliyun:
-            return "通义千问"
-        case .xunfei:
-            return "讯飞星火"
-        case .yiyan:
-            return "文心一言"
-        case .doubao:
-            return "豆包"
-        case .kimi:
-            return "kimi"
-        case .chatGPT:
-            return "ChatGPT"
-        }
+    static func webSocketUrl(api:String,addPlatform:Bool)->String{
+//        return wsBaseURL + api + "?platform=aliyun&is_auth=1&dt=\(MixSdk.shared.auth.deviceToken)"
+        return "ws://118.145.158.207:22080/" + api + "?is_auth=0"
     }
 }
 
 
 
-
 /// SDK 管理类
 public class MixSdk:NSObject {
     public static let shared = MixSdk()
@@ -146,32 +40,34 @@ public class MixSdk:NSObject {
     
     /// 初始化工作
     public func initSDK() {
-        Logger.log("MixSdk version:",MixSdk.version)
+        Logger.log("MixSdk version:",MixSdk.shared.version)
     }
     
     /// 日志
     public var logs: [String] { Logger.logs }
     
     /// SDK 版本号
-    public let version: String = "1.0.15"
+    public let version: String = "1.0.0"
     /// 默认为 false
     public var isDebug: Bool = false {
         didSet { Logger.isDebug = isDebug }
     }
     
-    
     /// 鉴权
     public let auth = MSAuth()
     
+    /// 蓝牙管理
+    public let ble = MSBle()
+    
     /// 语音聊天
     public let voiceChat = MSVoiceChat()
 
     func setupWebsoket(){
     }
     
-    
     /// 清空日志
     public func cleanLog(){
         Logger.logs.removeAll()
     }
 }
+
index 9690266..985a9ec 100644 (file)
@@ -19,6 +19,6 @@ SPEC CHECKSUMS:
   SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
   Toast-Swift: dd369e68ee529f542c65bd0d0c6c50cd743cf6f2
 
-PODFILE CHECKSUM: 1bf708b0b20ea5799d088a09a56f74d1537472c4
+PODFILE CHECKSUM: 9895aef7274631d7896464b4e401f72df13e34f4
 
-COCOAPODS: 1.14.3
+COCOAPODS: 1.15.2
index 9690266..985a9ec 100644 (file)
@@ -19,6 +19,6 @@ SPEC CHECKSUMS:
   SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a
   Toast-Swift: dd369e68ee529f542c65bd0d0c6c50cd743cf6f2
 
-PODFILE CHECKSUM: 1bf708b0b20ea5799d088a09a56f74d1537472c4
+PODFILE CHECKSUM: 9895aef7274631d7896464b4e401f72df13e34f4
 
-COCOAPODS: 1.14.3
+COCOAPODS: 1.15.2
index 6cd91a1..7eaea03 100644 (file)
@@ -10,4 +10,5 @@ target 'Example' do
     pod 'SnapKit', '~> 5.7.0'
     pod 'Toast-Swift'
     pod 'IQKeyboardManagerSwift'
+#    pod 'SwiftAudio'
 end